Bởi Marvin Frachet
Chà, năm nay có vẻ là năm của React. Bạn có thể đã nghe nói về tính năng sát thủ mới đi kèm với 16.7-alpha.0 — Hooks. Bạn có thể cũng đã nghe nói về một số nội dung hay và thú vị khác như Time Slicing hoặc thậm chí là Suspense.
Bài viết này không nhằm mục đích mô tả cách sử dụng một số tính năng mới mà là chứng minh cách chúng có thể được xây dựng. Chỉ để hiểu những gì chúng ta đang chơi.
Nó cũng được viết theo cách tôi đã khám phá ra tính năng này. Có lẽ đó không phải là cách người ta nghĩ ra, nhưng đây là cách tôi đạt được điểm.
Những gì bạn sẽ tìm thấy khi đọc:
- JavaScript không đồng bộ và vòng lặp sự kiện
- Các hiệu ứng đại số trong React, với ví dụ
- Các giai đoạn sợi và phản ứng
Tại sao tôi viết bài này?
Điều khiến tôi muốn viết bài đăng này là tính năng đặc biệt và mang tính thử nghiệm cho phép sử dụng không đồng bộ hoạt động sử dụng đồng bộ API:
const bulbasaur = ApiResource.read() ?… Cái gì cơ? Đồng bộ ?!
Thư viện bộ đệm phản ứng tạo khả năng sử dụng các hoạt động không đồng bộ với API đồng bộ. Đây là tính năng khiến tôi muốn tìm hiểu cách React hoạt động một cách sâu sắc. Đây là bài thuyết trình của Dan Abramov và Andrew Clark trên thư viện này:
Làm sao điều đó có thể được? Làm cách nào chúng tôi có thể nhận được một số dữ liệu từ xa bằng lệnh gọi đồng bộ?
Hãy cùng đi sâu vào ví dụ này và cố gắng hiểu cách React-cache triển khai chức năng như vậy và khám phá cách nó có thể hoạt động. Câu chuyện này bắt đầu với kiến trúc sợi.
Kiểm soát hoạt động JavaScript
Kiến trúc sợi cho phép React kiểm soát việc thực thi tác vụ. Nó được xây dựng để giải quyết nhiều vấn đề mà React gặp phải. Đây là hai điều khiến tôi chú ý:
- ưu tiên các sự kiện cụ thể, như dữ liệu đầu vào của người dùng hơn là tìm nạp dữ liệu
- phân tách không đồng bộ tính toán React để duy trì tính khả dụng của luồng chính và tránh chặn nó trong quá trình kết xuất kéo dài
Mọi thứ kích hoạt thay đổi trạng thái - không chỉ với React - bên trong ứng dụng JavaScript đều là do hoạt động không đồng bộ. Chúng bao gồm setTimeout , fetch và người nghe sự kiện.
Hoạt động không đồng bộ được quản lý thông qua nhiều khái niệm cốt lõi của JavaScript:
- các tác vụ (vi mô, vĩ mô, kết xuất, v.v...)
- vòng lặp sự kiện
- ngăn xếp cuộc gọi
Nếu bạn chưa quen với những khái niệm này, tôi khuyên bạn nên xem video này của Jake Archibald:
Nhờ có cáp quang, thông tin đầu vào của người dùng được giải quyết trước các hoạt động không đồng bộ khác như lệnh gọi tìm nạp.
Làm sao điều này có thể xảy ra được?
Chà, bài nói chuyện của Archibald ở trên là viên đá lát đường đầu tiên cho con đường tìm hiểu của riêng tôi về cách hoạt động của vòng lặp sự kiện. Anh ấy nói rằng các tác vụ vi mô — chẳng hạn như được tạo thông qua Promise API — được thực thi và xóa trước nhiệm vụ vĩ mô tiếp theo. Quá trình này sử dụng các phương thức dựa trên cuộc gọi lại như setTimeout .
Vì vậy, nếu bạn nhớ so sánh “đầu vào của người dùng và tìm nạp dữ liệu” của tôi, nhóm đã tạo ra fetch như thế nào nghị quyết sau onChange nghị quyết?
Không có khái niệm nào trong số này phù hợp với cùng một thông số kỹ thuật, WhatWG / HTML5 / Ecma-262 và được cung cấp từ những nơi khác nhau như trình duyệt hoặc công cụ JS.
Ý tôi là, làm cách nào để giải quyết Promise sau setTimeout ?
Điều này đối với tôi nghe có vẻ hoàn toàn điên rồ và thật khó để biết nó có thể hoạt động như thế nào. Thực tế là nó diễn ra ở cấp độ cao hơn.
Sau đó, tôi xem bài nói chuyện đáng kinh ngạc của Brandon Dail tại React Rally. Phần này trình bày các tính năng Cắt thời gian và Hồi hộp mới đã được cung cấp nhờ kiến trúc sợi React:
Theo Dail, Fiber cũng giống như bảng gọi JavaScript thông thường trong đó mỗi mục trong ngăn xếp được gọi là fiber . Nó khác với callstack dựa trên khung đại diện cho hàm (+ siêu dữ liệu). Đúng hơn, sợi đại diện cho một thành phần (+ siêu dữ liệu) . Hãy xem sợi quang như một chiếc hộp khổng lồ bao quanh một thành phần biết mọi thứ về nó.
Có sự khác biệt quan trọng giữa hai khái niệm này.
Trước hết, callstack là một chức năng được xây dựng dựa trên điều khiển phần gốc Mã JavaScript . Nó nhằm mục đích xếp chồng mọi lệnh gọi hàm JavaScript và tự chạy chúng. Mỗi lần chúng ta gọi một hàm, nó sẽ được thêm vào ngăn xếp. Nếu không có callstack, chúng ta sẽ không thể có các dấu vết lỗi rõ ràng và chi tiết. Và vì không thể truy cập được ngăn xếp cuộc gọi từ mã JavaScript nên việc kiểm soát nó thực sự rất khó khăn và thậm chí là không thể.
Mặt khác, các sợi — giống như một chồng sợi — đại diện cho cùng một khái niệm nhưng được xây dựng bằng mã JavaScript. Đơn vị nhỏ nhất không phải là chức năng mà là một thành phần. Nó thực sự chạy trong thế giới JavaScript.
Thực tế là kiến trúc sợi được xây dựng hoàn toàn bằng JavaScript có nghĩa là chúng ta có thể sử dụng, truy cập và sửa đổi nó. Chúng ta có thể xử lý nó bằng cách sử dụng JavaScript tiêu chuẩn.
Điều đã khiến tôi đi sai hướng là tôi nghĩ React đang sử dụng một giải pháp thay thế để cắt đứt cách thức hoạt động nội bộ của JavaScript. Không phải vậy . Fiber đơn giản là các đối tượng JavaScript sở hữu thông tin về các thành phần React và có thể tương tác với vòng đời của chúng. Nó chỉ có thể hoạt động trên các chức năng nội bộ của React.
Ý tưởng là không để xác định lại cách hoạt động của JavaScript, chẳng hạn như nói rằng fetch độ phân giải microtask phải được thực thi trước các tác vụ gọi lại. Vấn đề quan trọng hơn là phương thức React nào nên được gọi hay không trong một ngữ cảnh cụ thể, chẳng hạn như làm gián đoạn các lệnh gọi phương thức vòng đời khác nhau.
Này chờ đã! Bạn nói rằng sợi có thể kiểm soát hoàn toàn mọi thứ trong Ứng dụng React? Nhưng làm thế nào một thành phần có thể yêu cầu React ngừng làm bất cứ điều gì?
Các hiệu ứng đại số thì có, nhưng làm ơn bằng JavaScript
React có thể kiểm soát các thành phần và biết liệu thành phần đó có đang chạy hay không nhờ vào kiến trúc sợi. Điều còn thiếu bây giờ là một cách để cho React biết rằng có gì đó đã thay đổi đối với một thành phần cụ thể, vì vậy nó sẽ xử lý thay đổi này.
Đây là nơi hiệu ứng đại số vào trò chơi.
Hiệu ứng đại số không phải là thứ tồn tại trong JavaScript. Tôi sẽ cố gắng giải thích chúng là gì bằng lời giải thích ở cấp độ cao hơn.
Hiệu ứng đại số là một khái niệm cho phép gửi một số thông tin đi đâu đó, giống như một người điều phối. Ý tưởng là gọi một hàm cụ thể sẽ ngắt hàm hiện đang chạy ở một vị trí chính xác để cho phép hàm cha xử lý tính toán. Khi quá trình tính toán gốc kết thúc, nó có thể tiếp tục chương trình về vị trí ban đầu nơi thông tin đã được gửi.
Một số ngôn ngữ như OCaml hoặc Eff được hưởng lợi từ tính năng này. Đây là một sự trừu tượng thực sự thú vị vì các chi tiết triển khai chỉ dựa vào cấp độ gốc:
Thật tuyệt vời khi có một tính năng như vậy trong JavaScript phải không?
Nhóm React đã tạo ra một cách tiếp cận tương tự trong bối cảnh React xử lý try/catch JavaScript khối. Theo Dail, đây là khái niệm gần nhất có sẵn trong JavaScript.
Ném thứ gì đó cho phép gửi thông tin đến cha mẹ ở đâu đó. Phụ huynh đầu tiên nắm bắt được thông tin có thể xử lý và tính toán thông tin đó.
Một ví dụ hơn ngàn lời nói
Hãy tưởng tượng đoạn mã sau cố gắng tìm nạp Bulbasaur bằng API đồng bộ :
Đoạn mã này có thể lạ vì việc tìm nạp dữ liệu bằng API đồng bộ không thực sự phổ biến. Hãy nhảy vào bên trong customFetch thực hiện chức năng:
Ôi chờ đã! Điều này hoàn toàn không giống như một cuộc tìm nạp! Tôi không hiểu mục đích của chức năng này là gì…
Chà, hãy tưởng tượng thứ gì đó xung quanh thành phần , giả sử một sợi trông giống như:
Hãy dành chút thời gian để đọc mã.
Bây giờ, hãy chuyển sang customFetch thực hiện:
Phần quan trọng trong đoạn mã trước là try/catch chặn.
Hãy tóm tắt những gì đang xảy ra thông qua các đoạn mã khác nhau này:
- Số
Pokemonthành phần gọicustomFetchphương pháp. - Số
customFetchphương thức cố gắng đọc bộ đệm trong của nó nhưng nó trống. Vì vậy, nó ném cái gì đó / vào đâu đó — hiệu ứng đại số. - Số
fibercha mẹ nắm bắt thông tin đó, xử lý nó và tìm nạp dữ liệu. Sau đó nó điền vàocustomFetchbộ nhớ đệm chứa dữ liệu. - Việc hiển thị lại xảy ra ở
Component(args)và bây giờ làcustomFetchbộ nhớ đệm đã đầy. Dữ liệu hiện có sẵn trong thành phần bằng API đồng bộ.
Hãy xem react-cache chi tiết triển khai và kiểm tra các lần ném khác nhau.
Điều gì đó có thể khiến bạn chú ý trong quá trình này:render đã được gọi hai lần. Một để đưa ra lỗi — tạm dừng thành phần — và một thành phần để lấy dữ liệu — tiếp tục thành phần. React kích hoạt nhiều render là được gọi vì đây chỉ là một chức năng thuần túy — bản thân nó không có bất kỳ tác dụng phụ nào.
Đợi đã… Cái gì? render không có bất kỳ tác dụng phụ nào? Còn DOM thì sao?
Các giai đoạn phản ứng
Nếu bạn đã làm việc với React trong một thời gian dài, bạn có thể đã nghe nói rằng việc kết xuất lại nhiều lần không phải là một cách hay. Trước kiến trúc sợi, mỗi khi chúng ta gọi hàm kết xuất, React đều thực hiện một số tính toán nội bộ và sau đó sửa đổi DOM cho phù hợp. Ví dụ:điều này xảy ra khi gọi hàm kết xuất thông qua setState . Quá trình này đã được nội tuyến:
setState →render → so sánh các nút ảo → cập nhật các nút DOM
Xử lý chất xơ, quá trình này hơi khác một chút. Nó đã đưa ra khái niệm về hàng đợi và lô cho phép sửa đổi DOM hiệu suất cao.
Ý tưởng này khá đơn giản. Chúng tôi giả định rằng màn hình có thể chạy ~60 khung hình mỗi giây. Từ giả định này và việc sử dụng các hàm JavaScript có sẵn, có thể thực hiện một số tính toán và sửa đổi DOM chỉ sau mỗi ~16,7 mili giây. Với Fiber, React có thể xử lý nhiều sửa đổi và thực hiện chúng khoảng 60 lần mỗi giây.
Kiểu sửa đổi này đã cho phép React chia thành ba giai đoạn với những ưu điểm và đặc thù riêng:
_[Dan Abramov liên quan đến React giai đoạn](https://twitter.com/dan_abramov/status/981712092611989509/photo/1?ref_src=twsrc%5Etfw%7Ctwcamp%5Etweetembed%7Ctwter m%5E981712092611989509&ref_url=https%3A%2F%2Fmedium.com%2Fmedia%2Fbda1c34a16e9f8a8e3eb244716a1da72%3FpostId%3D2a57dc9c2e6d" rel="noopener" target="blank" title=")
- Giai đoạn kết xuất hoàn toàn thuần túy và mang tính quyết định. Nó không có tác dụng phụ và các chức năng khác nhau mà nó bao gồm có thể được gọi nhiều lần. Giai đoạn kết xuất có thể bị gián đoạn — đó không phải là
renderchức năng ở chế độ tạm dừng, nhưng toàn bộ giai đoạn - Cụm từ cam kết trước nhằm mục đích cung cấp quyền truy cập vào trạng thái DOM thực tế, như vị trí thanh cuộn, ở chế độ đọc.
- Giai đoạn cam kết thực sự sửa đổi DOM và không bị gián đoạn . React không thể tạm dừng trong giai đoạn đó.
Bộ ba giai đoạn này đã giới thiệu khả năng Cắt thời gian. React có thể tạm dừng trong giai đoạn kết xuất, giữa hai lệnh gọi hàm thành phần và tiếp tục giai đoạn đó khi cần thiết.
Bằng sợi quang, render chỉ nhằm mục đích có được thông tin đại diện mới nhất hiện có của một thành phần dựa trên trạng thái bên trong của nó để đưa ra một số so sánh và biết liệu React có phải thay đổi DOM hay không. Nếu cần phải sửa đổi cam kết, nó sẽ thêm sửa đổi đó vào hàng đợi "đang tiến hành".
Nhóm React đã thực hiện những cải tiến hiệu suất rất lớn nhờ React Concurrent (Time Slicing + Suspense) và kiến trúc sợi. Họ đã tạo ra các giải pháp để giải quyết các vấn đề khác nhau của trình duyệt như mức độ ưu tiên của sự kiện và sự tương tranh.
Nếu chúng ta lùi lại một bước, đó chẳng phải là điều họ đã thể hiện sao? Ưu tiên dường như là thách thức mới đối với trình duyệt và các framework giao diện người dùng.
Các nhóm khác cũng đang nỗ lực cải thiện trạng thái hiện đại thực tế và thậm chí đề xuất các API trong tương lai. Đây là quan điểm của Google:
Học cách viết mã miễn phí. Chương trình giảng dạy mã nguồn mở của freeCodeCamp đã giúp hơn 40.000 người có được việc làm với tư cách là nhà phát triển. Bắt đầu