Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Analysis of React source code - Basic architecture #41

Open
mominger opened this issue Oct 7, 2022 · 1 comment
Open

Analysis of React source code - Basic architecture #41

mominger opened this issue Oct 7, 2022 · 1 comment

Comments

@mominger
Copy link
Owner

mominger commented Oct 7, 2022

bash_arch

The upper part is the English version, and the lower part is the Chinese version, with the same content. If there are any wrong, or you have anything hard to understand, pls feel free to let me know.many thx.

React does little at compile stage, only converts jsx into createElement method. So it is mainly a runtime framework, it needs to optimize performance from both CPU and IO sides to achieve the purpose of fast response
This article is based on React 17.0.2
React official website
React source code

1 Directory

directory_1

directory_2

Export API for react directory
react_directory_api

scripts: git、jest、prettier、eslint etc.
Most important modules are react react-dom react-reconciler scheduler
Different platforms use different modules, such as react-dom for browsers, react-art for canvas, and react-native-renderer for mobile

For web side, it requires the following 3 modules
directory_3_web

2 Entrance

2.1 Entrance

entrance_mode

Use Legacy Mode defaultly

mode_diff

Refer to the article on offical websit Adopting Concurrent Mode

2.2 Compilation of JSX

After compiling jsx by babel, pass it as a parameter to createElement
babel compile test address

2.2.1 Old compilation

old_compile

2.2.2 New compilation

new_compile

This is why React 17 no longer needs to import React, as it was changed to jsx()

3 How to debug

git clone https://github.com/facebook/react.git

npm install

npm run build react/index,react/jsx,react-dom/index,scheduler --type=NODE

Type the command npm link in build/node_modules/react and build/node_modules/react-dom, then type npm link react, npm link react-dom in business projects
Or directly debug by chrome devtool
Or set react, react-dom, shared, and react-reconciler pointing to the source code in webpack.config.js. The advantage of using this method is that it can directly debug the source code instead of the compiled code. The disadvantage is that some issues caused by uncomplied need to be fixed, such as the configuration of environment variables.

4 Core issues

core_issues

Async interruptible updates are the goal of React performance optimizations. Scheduler (requestIdleCallback polyfill) and Fiber are key technologies to achieve it. Fiber maps every React node, it is the unit of work.
For the CPU issue, I personally think that using Web Worker + MessageChannel to optimize React rendering, in particularly at multi-core CPU environment.

What is Suspense
suspense_img
What is useDeferredValue
It like debounce, but it can only be used in React 18

5 Explanation of Architecture Diagram

  • Explain every point for the architecture diagram on the top

5.1 jsx

viturl_dom

After jsx is compiled by babel, it is converted to createElement() or jsx(), then it returns a json object that describe dom structure. That json object is also called virtual dom.

5.2 Scheduler

5.2.1 Schedule
5.2.1.1 requestIdleCallback polyfill

schedule_core

Scheduler(requestIdleCallback polyfill) = requestAnimationFrame + MessageChannel

5.2.1.2 Defects of requestIdleCallback

FPS out of sync
requestAnimationFrame_issue

The FPS of requestIdleCallback is not necessarily 60, it may be 20

It's browser compatibility is not as good as requestAnimationFrame
ric_1

ric_2

requestIdleCallback compatibility
reqeustAnimationFrame compatibility
Based on browser compatibility, and the FPS of requestIdleCallback is 20, React polyfills it

5.2.2 Priority

schedule_priority

Schedule priorities
old_priority

Lane priorities
new_priority

The more bits of 1, the lower the priority
Schedule priority is used in Scheduler, Lane priority is used in React

5.2.3 Other
  • time slice: In Concurrent Mode, multiple tasks run in the same JS thread, and every Task can be switched between interrupted or running states, giving users the illusion of task concurrency. This is similar to the concurrency of a single-core CPU, so it is called time slicing
  • task scheduling: means that the tasks can be interrupted and resumed, and every task is setted a corresponding priority.
  • interruptible rendering: there are 2 Fiber trees in a rendering task. A Fiber tree has n Fiber nodes. One Fiber corresponds to a React node. When rendering to a Fiber node, it can be stopped, and in the future it can continue rendering from this Fiber node. That means interrupting and resuming rendering.

5.3 Reconciler

5.3.1 Fiber

workInProgress(Fiber)
fiber_node

//source code: https://github.com/facebook/react/blob/17.0.2/packages/react-reconciler/src/ReactInternalTypes.js

Fiber = {
    // Consistent with vitual dom
    tag:WorkTag, // the type of component
    Key:null | string, // key property of component
    elementType: any, // element type, ReactElement.type, is also the first parameter of `createElement`
    type: any, // usually `function` or `class` or an element name such as 'p'
 
    // used to build fiber tree, linked list structure
    return: Fiber | null, // point to the parent node
    child: Fiber | null, // points to the first child node
    sibling: Fiber | null, // point to the next sibling node
    index: number,
    
    ref:null,

    //Save the state, calculate the state
    pendingProps: any, // new props brought by the update
    memoizedProps:any //last rendered props
    updateQueue:UpdateQueue<any> | null; //update queue of the Fiber
    memoizedState: any // last rendered state

    dependencies: Dependencies | null; //contexts and events

    mode: TypeOfMode; // ConcurrentMode or LegacyMode
        
    //effect related, linked list structure
    flags: Flags, // effect Tag
    nextEffect: Fiber | null, // Used to quickly find the next effect
    firstEffect: Fiber | null, // first effect in subtree
    lastEffect: Fiber | null, // last sibling effect in the subtree

    // Priority related properties
    lanes: Lanes,
    childLanes: Lanes,

    //Pointer to current or workInProgress
    //The  fiber node in the WorkInProgress tree will switch positions after rendering is complete
    this.alternate = null;
}
5.3.2 Fiber tree

Fiber maps every react node and maintains node information, formed based on virtual node processing. Fiber mainly saves the properties, dom, type, etc. of this node, and using child (child node), sibling (sibling node), return (parent node), and updateQueue (for updating state), effect tag (for committing to dom) etc. properties to builds a Fiber tree

5.3.3 Two Fiber trees

React uses Double Buffering to maintain two Fiber trees. WorkInProgress Fiber is the Fiber tree that is being updated, both updated Fiber nodes and unupdated Fiber nodes in it. After workInProgress Fiber constructed, it will be switch to current Fiber, and finally committed to dom. WorkInProgress Fiber is just an intermediate variable in memory.current Fiber and workInProgress Fiber refer to each other by alertnate. The switch is performed by a property named 'current' of fiberRootNode, when current points to workInProgress Fiber, which means the switch is completed.
During mount, build a workInProgress Fiber based on jsx object, then switch workInProgress Fiber to current Fiber, finally commit to dom; during update, build a new workInProgress Fiber based on state-changed jsx object and current Fiber, and then switch it to current Fiber, finally commit to dom

5.4 Renderer

  • commit to dom: Iterate effect list built on render stage, every Fiber node in effect list saves the own changes of properties, then commit to real dom.

The following is the Chinese version, the same content as above

bash_arch

React在编译时做的事很少,仅将jsx转换成createElement方法。所以它主要是运行时的框架,它需要从CPU 和 IO两方面去优化性能,达到快速响应的目的
本文基于 React 17.0.2
React官网
React源码

1 目录

directory_1

directory_2

react目录的API
react_directory_api

scripts: git、jest、prettier、eslint等
最重要的是 react react-dom react-reconciler scheduler
不同平台用不同的包,比如浏览器用 react-dom,canvas 用react-art, mobile 用 react-native-renderer

web端需要的核心就3个包

directory_3_web

2 入口

2.1 入口

entrance_mode

默认是使用 Legacy Mode

mode_diff

参考官网Adopting Concurrent Mode

2.2 JSX的编译

由babel编译jsx后,作为参数传给createElement
babel编译 测试地址

2.2.1 旧的编译

old_compile

2.2.2 新的编译

new_compile

这也是React17不再需要 import React的原因,因为改成了jsx()

3 如何调试

git clone https://github.com/facebook/react.git

npm install

npm run build react/index,react/jsx,react-dom/index,scheduler --type=NODE

在 build/node_modules/react和build/node_modules/react-dom 执行 npm link,在调试项目执行 npm link react, npm link react-dom
或直接通过 chrome devtool 调试
或在webpack.config.js 将 react,react-dom,shared,react-reconciler指向源码,此方法的优点是能直接调试源码,而非编译后的代码。缺点是需解决一些因未经过打包而产生的问题比如环境变量的配置。

4 核心问题

core_issues

异步可中断的更新是React性能优化的目标。Scheduler(requestIdleCallback polyfill) 和 Fiber是实现这目标的技术。Fiber映射了每个React节点,它是工作单元。
对于CPU的问题,我个人认为,应该用Web Worker + MessageChannel来优化React的渲染,尤其在在多核CPU环境下。

What is Suspense
suspense_img
What is useDeferredValue
It like debounce, but it can only be used in React 18

5 架构图的解释

  • 针对开头的架构图逐个解释

5.1 jsx

viturl_dom

jsx经过babel编译后,转成createElement(),createElement()执行后返回一个描述dom结构的json对象,此json对象也称为virtual Dom.

5.2 Scheduler

5.2.1 调度
5.2.1.1 requestIdleCallback polyfill

schedule_core

Scheduler(requestIdleCallback polyfill) = requestAnimationFrame + MessageChannel

5.2.1.2 requestIdleCallback 的缺陷

FPS不同步
requestAnimationFrame_issue

requestIdleCallback的 FPS并不一定是60,可能是20

浏览器兼容性不如requestAnimationFrame
ric_1

ric_2

requestIdleCallback 兼容性
reqeustAnimationFrame 兼容性
基于浏览器兼容性,以及reactIdleCallback的FPS为20等原因,React polyfill 了它

5.2.2 优先级

schedule_priority

Schedule优先级
old_priority

Lane优先级
new_priority

1 的bits越多,优先级越低
Schedule优先级在 Scheduler使用,Lane优先级在 React里使用

5.2.3 其他
  • time slice: Concurrent Mode下,多个Task跑在同一个JS线程里,每个Task都可以在中断或运行两种状态中不停切换,给用户造成一种Task并发的错觉。这类似单核 CPU的并发,所以称为时间分片
  • task scheduling: 就是指Task可中断可恢复运行,且任务有优先级。
  • interruptible rendering: 一个渲染任务里有2棵Fiber树,Fiber树有n个Fiber节点,一个Fiber对应一个react节点,渲染到某个Fiber节点时,可以停止。将来可以再从这个Fiber节点继续渲染。即实现了中断,恢复渲染的效果

5.3 Reconciler

5.3.1 Fiber

workInProgress(Fiber)
fiber_node

//代码地址: https://github.com/facebook/react/blob/17.0.2/packages/react-reconciler/src/ReactInternalTypes.js

Fiber = {
    // 和vitual dom 一致
    tag:WorkTag, // 对应组件的类型
    Key:null | string, // 组件的key属性
    elementType: any, // 元素类型,ReactElement.type,也是`createElement`的第一个参数
    type: any,  // 一般是`function`或者`class` 或 元素名称例如'p'
 
    // 用于形成fiber树,链表结构
    return: Fiber | null,  // 指向父节点
    child: Fiber | null, // 指向第一个子节点
    sibling: Fiber | null, // 指向下一个兄弟节点
    index: number,
    
    ref:null,

    //保存状态,计算状态
    pendingProps: any, // 更新带来的新的props
    memoizedProps:any 上一次渲染的props
    updateQueue:UpdateQueue<any> | null; //该Fiber对应的组件的update队列
    memoizedState: any // 上一次渲染的state

    dependencies: Dependencies | null; //contexts 和 events

    mode: TypeOfMode; // ConcurrentMode 或 LegacyMode
        
    //effect相关,链表结构
    flags: Flags, // effect Tag
    nextEffect: Fiber | null, // 用来快速查找下一个 effect
    firstEffect: Fiber | null, // 子树中第一个 effect
    lastEffect: Fiber | null, // 子树中最后一个 sibling effect

    //优先级相关的属性
    lanes: Lanes,
    childLanes: Lanes,

    //current和workInProgress的指针
    //WorkInProgress 树中对应的fiber节点,渲染完成后会交换位置
    this.alternate = null;
}
5.3.2 Fiber tree

Fiber 对应每一个react节点,维护节点的信息。通过vitual 节点 加工而成。Fiber上主要保存了这个节点的属性、dom、type等,并通过child(子节点)、sibling(兄弟节点)、return(父节点)形成Fiber树,以及updateQueue(用于更新state),effect tag(用于commit到dom)等属性

5.3.3 Two Fiber trees

React使用了Double Buffering的方式,维护了两棵Fiber tree。workInProgress Fiber是正在更新的Fiber树,同时存在更新完毕的Fiber节点和未更新的Fiber节点。在workInProgress Fiber构建完后会切换成current Fiber,最后commit 到 dom上。workInProgress Fiber仅仅只是内存里的一个中间变量。current Fiber 和 workInProgress Fiber通过 alertnate相互引用。通过Fiber根节点fiberRootNode的current属性进行切换,current指向workInProgress Fiber即代表切换完成。
在mount时,根据jsx对象构建 workInProgress Fiber,然后将 workInProgress Fiber 切换成 current Fiber commit 到 dom上;在update时,根据状态改变后的jsx对象和current Fiber做对比构建新的workInProgress Fiber,构建完后会切换成current Fiber,最后commit 到 dom上

5.4 Renderer

  • commit to dom: 遍历render阶段构建的effectList,effectList上的Fiber节点保存着属性的变化,更新到真实的dom上
@mominger
Copy link
Owner Author

mominger commented Oct 7, 2022

update Scheduler

@mominger mominger changed the title Basic architecture Analysis of React source code - Basic architecture Oct 14, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant