Computer >> Hướng Dẫn Máy Tính >  >> Phần Mềm >> Các Trình Duyệt

Deep Dive React Cache, Time Slicing, Và Synchronous API Fetching

Bởi Marvin Frachet

Năm nay dường như là năm của React. Bạn có thể nghe killer feature mới trong 16.7-alpha.0 — Hooks. Có thể cũng nghe Time Slicing hoặc Suspense.

Bài này KHÔNG nhằm mô tả cách dùng tính năng mới mà chứng minh chúng có thể được xây dựng như thế nào. Chỉ để hiểu cái chúng ta chơi với.

Cũng viết theo cách tôi khám phá feature. Có thể không phải cách nó được thiết kế, nhưng đây là cách tôi hiểu.

Đọc bài bạn sẽ thấy:

  • Async JavaScript và event loop
  • Algebraic Effects trong React, kèm ví dụ
  • Fiber và React Phases

Tại Sao Tôi Viết Bài Này?

Điều khiến tôi muốn viết là tính năng đặc biệt, experimental, cho phép dùng operation asynchronous với API synchronous:

const bulbasaur = ApiResource.read()?… Cái gì? Synchronous?!

Thư viện react-cache tạo khả năng dùng async operation với synchronous API. Tính năng này khiến tôi muốn học React hoạt động under the hood như thế nào. Đây là presentation bởi Dan Abramov và Andrew Clark về thư viện này:

Làm sao điều đó possible? Làm sao lấy remote data dùng synchronous call?

Hãy deep dive vào ví dụ này và cố gắng hiểu react-cache implement chức năng này như thế nào và khám phá cách nó hoạt động. Câu chuyện bắt đầu với kiến trúc fiber.

Kiểm Soát Operation JavaScript

Kiến trúc Fiber cho phép React kiểm soát thực thi task. Được xây để giải quyết nhiều vấn đề React gặp. Hai vấn đề thu hút tôi:

  • ưu tiên event cụ thể, như user input hơn data fetching
  • tách bất đồng bộ React computation để giữ main thread available và tránh block nó trong render process lâu

Mọi thứ trigger state change — không chỉ React — trong JS application do async operation. Bao gồm setTimeout, fetch, event listener.

Async operation quản lý qua nhiều khái niệm core JS:

  • task (micro, macro, render...)
  • event loop
  • callstack

Nếu không quen khái niệm này, xem video Jake Archibald:

Nhờ fiber, user input được resolved trước async operation khác như fetch call.

Làm sao possible?

Talk Archibald trên là viên gạch đầu tiên con đường học của tôi về event loop. Anh nói micro task — sinh qua Promise API — executed và flushed trước macro task tiếp theo. Process này dùng callback-based method như setTimeout.

Vậy, nhớ so sánh "user input vs fetching data", team làm sao làm fetch resolution sau onChange resolution?

Không khái niệm nào fit cùng spec, WhatWG / HTML5 / Ecma-262, từ nơi khác nhau như browser hoặc JS engine.

Tôi nói, làm sao resolve Promise sau setTimeout?

Nghe crazy tuyệt đối và khó hình dung cách hoạt động. Thực tế nó diễn ra ở higher level.

Sau đó, tôi xem talk tuyệt vời Brandon Dail tại React Rally. Trình bày Time Slicing và Suspense features mới nhờ kiến trúc React fiber:

Theo Dail, fiber giống như JS callstack thường nơi mỗi item trong stack gọi là fiber. Khác callstack dựa trên frames đại diện functions (+ metadata). Thay vào đó, fiber đại diện component (+ metadata). Hãy xem fiber như hộp khổng lồ quanh component biết mọi thứ về nó.

Có khác biệt quan trọng giữa hai khái niệm.

Thứ nhất, callstack là functionality xây trên native part driving JavaScript Code. Mục đích stack mọi JS function call, chạy chúng riêng. Mỗi lần gọi function nó thêm vào stack. Không có callstack, không có error stacktraces rõ ràng chi tiết. Vì callstack không truy cập từ JS code, rất khó thậm chí impossible kiểm soát nó.

Thứ hai, fiber — như stack của fiber — đại diện cùng khái niệm, nhưng xây bằng JavaScript code. Unit nhỏ nhất không phải function, mà component. Nó chạy trong universe JavaScript.

Thực tế fiber architecture hoàn toàn xây bằng JavaScript nghĩa là ta dùng, truy cập, sửa đổi nó. Làm việc với nó dùng JS chuẩn.

Điều dẫn tôi sai hướng là nghĩ React dùng workaround cắt internal way JS hoạt động. Không phải vậy. Fiber đơn giản là JS object chứa info về React component và tương tác với lifecycle. Chỉ act trên React internal functionality.

Ý tưởng KHÔNG phải redefine JS nên hoạt động như thế nào, như bảo fetch microtask resolution execute trước callback task. Nó về React method nào nên gọi hoặc không trong context cụ thể, như interrupt lifecycle method call khác nhau.

Hey wait! Bạn nói fiber kiểm soát mọi thứ React App? Nhưng component làm sao bảo React dừng làm gì?

Algebraic effects, yes, nhưng bằng JavaScript xin vui lòng

React kiểm soát component, biết component có chạy không nhờ fiber. Thiếu cách báo cho React điều gì thay đổi cho component cụ thể, để nó handle thay đổi.

Đây là lúc algebraic effects xuất hiện.

Algebraic effects không tồn tại trong JavaScript. Sẽ giải thích high-level.

Algebraic effects là concept cho phép gửi thông tin đâu đó, giống dispatcher. Ý tưởng: gọi function cụ thể interrupt function đang chạy tại vị trí chính xác để parent function handle computation. Khi parent computation xong, resume program về vị trí ban đầu gửi thông tin.

Ngôn ngữ như OCaml hoặc Eff hưởng lợi feature này native. Abstraction thú vị vì implementation detail chỉ dựa vào parent:

Không tuyệt vời nếu có feature này trong JavaScript?

React team tạo approach tương tự trong context React deal với JS try/catch block. Theo Dail, nó closest available concept trong JS.

Throw cái gì cho phép gửi thông tin đến parent, đâu đó. Parent đầu tiên catch thông tin xử lý và compute trên nó.

Ví dụ tốt hơn ngàn lời

Hãy tưởng tượng code sau fetch Bulbasaur dùng synchronous API:

Đoạn code này lạ vì không thường fetch data dùng synchronous API. Hãy jump vào implementation customFetch:

Ồ wait! Điều này trông hoàn toàn không giống fetch! Tôi không hiểu function này aim làm gì...

Hãy tưởng tượng thứ quanh component, ví dụ fiber trông như:

Đọc kỹ code.

Bây giờ, jump vào implementation customFetch:

Phần quan trọng trong snippet trước là block try/catch.

Tóm tắt xảy ra qua các đoạn code:

  • Component Pokemon gọi method customFetch.
  • Method customFetch thử đọc internal cache, nhưng rỗng. Nên throw cái gì đó / đâu đó — algebraic effects.
  • fiber parent catch thông tin, handle, fetch data. Sau đó populate customFetch cache với data.
  • Re-render xảy ra trong Component(args)và bây giờ, customFetch cache đầy. Data giờ available trong component dùng synchronous API.

Xem implementation detail react-cache và check các throw khác nhau.

Điều có thể thu hút attention: render gọi 2 lần. Một lần throw error — pause component — và một lần lấy data — resume component. OK với React trigger multiple render call vì nó pure function — không side effect riêng.

Wait… What? render không side effect? DOM thì sao?

React Phases (Giai Đoạn React)

Nếu làm React lâu, có thể nghe không good practice re-render nhiều lần. Trước fiber, mỗi lần gọi render function React làm internal computation rồi modify DOM. Ví dụ, xảy ra khi gọi render qua setState. Process inline:

setState →render → compare Virtual Nodes → update DOM nodes

Với fiber, process khác. Introduce concept queue và batches cho high performance DOM modification.

Ý tưởng đơn giản. Giả sử màn hình chạy ~60 frames/giây. Từ giả định này, và dùng JS function available, có thể làm computation và DOM modification chỉ mỗi ~16.7ms. Với fiber, React enqueue multiple modification và commit ~60 lần/giây.

Loại modification này cho React split thành 3 phases với advantage và particularities riêng:

Deep Dive React Cache, Time Slicing, Và Synchronous API Fetching _[Dan Abramov concerning React phases](https://twitter.com/dan_abramov/status/981712092611989509/photo/1?ref_src=twsrc%5Etfw%7Ctwcamp%5Etweetembed%7Ctwterm%5E981712092611989509&ref_url=https%3A%2F%2Fmedium.com%2Fmedia%2Fbda1c34a16e9f8a8e3eb244716a1da72%3FpostId%3D2a57dc9c2e6d" rel="noopener" target="blank" title=")

  • Render phase: pure và deterministic. Không side effect, function trong phase có thể gọi multiple lần. Render phase interruptible — không phải function render pause, mà cả phase.
  • Pre-commit phase: cung cấp truy cập DOM state thực, như vị trí scrollbar, ở read mode.
  • Commit phase: thực sự modify DOM và KHÔNG interruptible. React không pause phase đó.

Set 3 phases này introduce Time Slicing capabilities. React pause trong render phase, giữa 2 component function call, và resume phase khi cần.

Trong fiber, render chỉ aim lấy latest available representation của component dựa trên internal state để compare và biết React có phải thay DOM không. Nếu cần commit modification, thêm vào queue "work in progress".

React team làm cải tiến performance khổng nhờ React Concurrent (Time Slicing + Suspense) và fiber architecture. Tạo workaround counter browser problem như event prioritisation và concurrency.

Nếu step back, đó không phải thứ họ show? Prioritisation dường như challenge mới cho browser và front-end framework.

Team khác cũng cải tiến state of the art và propose future API. Đây là take của Google:

Học lập trình miễn phí. Chương trình mở của freeCodeCamp đã giúp hơn 40.000 người có việc làm lập trình viên. Bắt đầu ngay