Open 6thfdwp opened 3 years ago
https://github.com/koba04/react-fiber-resources This repo has collective resources and some nice call stack charts captured in Chrome dev tool.
As it stated React Fiber reconciliation makes many features possible like Suspense
(optimising IO bound, avoid unnecessary loading state spinning around) and Concurrent Mode
(optimising CPU bound ops with interruptible rendering with sophisticated scheduling, can be paused, resumed or aborted)
These features should be coming in React 18 release. It's clear that React is pushing the boundary of performant UI runtime in a single call stack (thread)
This learning starts from inspiring series of blog posts Didact: DIY your own React, which has been updated in his new blog post with Fiber and Hooks implementations (drastically simplified but core concept remains true).
Core Concepts Overview
The React contains 3 main packages
React Core
APIs necessary to define components. like
React.createElement()
and to define and update statesReconcilers
This manages to generate next snapshot of UI (represented by element object tree) based on latest state, also be able to do diff and figure out the minimal updates (platform calls) the Renderer needs to take. It is more concerned with 'WHAT to render on screen'
Renderers
With this kind of separation, It allows different renderers to handle platform specific while reusing the same React core and reconciling algorithms. Renderers is mainly to encapsulate the 'HOW' part.
My learning and experimental code repo are focused on Reconciler algorithm and a bit of React core, to able to return the element object from the JSX
React Element
React.Element is light weight object representation of actual UI (e.g DOM in web)
Component is the definition to return the Element. It can compose other components using HTML like syntax (JSX) to create complext UI structure.
Let's say we have a list of stories (or any type of items), we can 'Like' each story and the number goes up. The component might look like this:
Before actual running, Babel plugin will recursively check the JSX and transpile each node to , it will be like:
createElement
call. ForThe returned element object tree representing
<StoryLike>
would be:So the first thing we need is to implement the simplified version of
React.createElement
, the function signature would be:Stack Reconciler
This is the reconciling algorithm before React 16. This reconciler uses recursion to walk through the element object tree to build the internal instances hierarchy. As recursion cannot be interrupted once it's started, it could block browser UI thread and user interaction suffers when it takes long time, which is common for complex UI (e.g to render long list with complex data)
Consider we have an
App
which renders only one StoryLike componentWhen
render(<App title='Stack Reconciler' />)
, it recursively builds the internal instance hierarchy corresponding to each level in the elemement object tree. There are two main types of instances for two types of element, one for primitive whose type is string (e.g div, li), one for custom component which has type 'function'It is the instance wrapper for custom component (element.type is function), it mainly runs the the function body or
render
menthod to keep 'unwrapping' the element object defined in itIt is the instance wrapper for primitive elements (type is
string
, h1, li etc). It mainly maintains the ref to DOM node, a list of children which could be other Composite/DOM internal instancesThe internal instance hierarchy can be represented as below:
Fiber reconciler
We could also call it incremental reconciler. It still needs to traverse the element object tree, just the process can be split into chunks and spread it out over multiple call stack frames. Compared to Stack Reconciler, it does not rely on recursion that has to be finished in one single call stack, avoid blocking UI thread.
In this implementation, it demonstrates a simple scheduling to split traversal via
requestIdleCallback
. It's more like building a linked list incrementally. The element object tree is transformed to fiber nodes linked together in parent → first child → sibling and back to parent fashion.The same
render(<App title='Fiber Reconciler' />)
above, its reconciliation process can be visualised as below:From 1 to 17, it can be interrupted at any time based on the priorities or time is up for browser to draw current frame in the UI thread every 16.6ms (60fps frames per second, means 1000ms/60 = 16.6ms per frame)
The final commit phase is to actually do DOM operation (place new nodes, update or deletion), which need to be done in one go, so user can see full content / style painted at once.