mominger / blog

Tech blog
45 stars 3 forks source link

Analysis of React source code - Basic architecture #41

Open mominger opened 1 year ago

mominger commented 1 year ago

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

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

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

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 架构图的解释

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 其他

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

mominger commented 1 year ago

update Scheduler