We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
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
本文内容参考 https://github.com/facebook/react v16.0.0 版本源码。 如 React 实际行为与本文有出入,以 react repo 的 master 分支提交的最新改动为准。
由于 v16.0.0 已经使用了 ReactDOMFiberEntry 做渲染,所以在调用 ReactDOM.render 时,实际是在调用 ReactDOMFiberEntry.render()。调用流如下(点击跳转到 github 上相关源码):
ReactDOM.render
ReactDOMFiberEntry.render()
ReactDOM.render()
一步一步看:
unbatchedUpdates
fiberUpdateQuene
在 setState() 发生的事情则简单一些:
在调用 setState() 后,fiberUpdater 会做以下几件事情:
setState()
Fiber 是一种最轻量化的线程(lightweight threads)。它是一种用户线程(user thread),让应用程序可以独立决定自己的线程要如何运作。 Fiber 是 React Fiber 的基本工作单元,简单来看,一个 Fiber 的数据结构关键字段如下:
type Fiber = { tag: TypeOfWork, // fiber 类型,FunctionalComponent,ClassComponent 之类 stateNode: any, // fiber 的局部状态 return: Fiber, // process 结束后返回的结果,指向当前正在 processing 的 parent child, // fiber 的子节点 sibling, // fiber 的兄弟节点 index, // 下标 }
fiberUpdateQueue 由一个或多个 fiber 连接而成:
fiberUpdateQueue ----------------------------------------------------------------- | index: 1 | index: 2 | index: 3 | index: 4 | | tag: [root] | tag: [root] | tag: [class] | tag: [host] | | type:null | type: null | type: <App> | type: <div> | | child: 2 | child: 3 | child: null | child: null | | sibling: null | sibling: null | sibling: 4 | sibling: null | ----------------------------------------------------------------- ^ ^ | | first last
scheduleUpdate() 会从当前触发 scheduleUpdate 的节点开始,由 return 字段找父节点,直到找到根节点。如果正在执行工作,则不做任何事。否则,判断不同的优先级,从找到的根节点开始执行不同优先级的 performWork()
scheduleUpdate()
performWork()
performWork() 主要执行 workLoop()。 workLoop 将循环执行以下逻辑:
workLoop()
createWorkInProgress()
nextUnitOfWork
performUnitOfWork()
beginWork
commitAllWork()
prepareForCommit();
commitAllHostEffects();
commitWork();
commitUpdate();
updateFiberProps();
updateProperties();
commitAllLifeCycles();
commitLifeCycles();
nextPriorityLevel
SynchronousPriority
TaskPriority
接下来结合官方提供的 react/fixtures/fiber-debugger 工具来一步一步观察 fiber 具体是如何展开、调度的。
React 官方提供了 react-noop-renderer 用于调试 React,在 react-noop-renderer 的 ReactFiberInstrumentation.debugTool 添加对应的回调,可以在 fiber 的 be ginWork、completeWork、commitWork 阶段时得到通知并记录下来。
准备好要测试的 jsx。
class World extends React.Component { render() { return <div>world</div>; } } class App extends React.Component { render() { return <div> hello <World /> </div>; } } log('Render <App />'); ReactNoop.render(<App />); ReactNoop.flush();
在 fixures/fiber-debugger 文件夹下运行
yarn install yarn start
将自动打开 localhost:3000,如下:
点击 “EDIT” 链接,在弹出的 textarea 中粘贴上面的那段 jsx,点击 “Run”,此时拖动 Slider,就可以一步一步调试 fiber 了。
执行 root 的 beginWork, 并为子节点 <App /> 创建 fiber, 添加入队列:
<App />
执行 的 beginWork,并为子节点 <div /> 创建 fiber,添加入队列:
<div />
执行 <div /> 的 beginWork, 并为子节点 "hello" 和 <World /> 分别创建 fiber,添加入队列:
<World />
执行文字节点 "hello" 的 beginWork, 处理完毕,执行 "hello" 的 commitWork。(图中标记为紫色)
执行节点 <World /> 的 beginWork,为子节点 "world" 创建 fiber,添加入队列。 !
处理文字节点 "world" 的beginWork,处理完毕,执行 "world" 的 commitWork。
从节点 "world" 回溯执行 commitWork,直到跟节点。至此,<App /> 组件加载过程的 fiber 执行完毕。
接下来将用一个例子,直观感受一下 fiber.
const length = 30000; class Foo extends React.Component { constructor() { super(); this.state = { text: 'foo' }; } componentDidMount() { setInterval(() => { this.setState(state => ({ text: state.text === 'foo' ? 'react' : 'foo' })) }, 500); } render() { return this.state.text; } } class App extends React.Component { constructor() { super(); this.state = { offset: 0 }; } add = () => { this.setState(state => ({ offset: state.offset + 1})) } render() { const result = []; for (let i = 0; i < length; i++) { result.push(<li key={i}>{i + this.state.offset}</li>) } return <ul> <Foo /> <button onClick={this.add}> click me </button> {result} </ul> } } ReactDOM.render( <App />, document.getElementById('container') );
这里创建了一个 <App /> 组件,里边有一个 <Foo /> 组件, 一个按钮和一个长列表:
<Foo />
运行这段代码,我们可以发现,由于列表非常之长,所以每次 diff 和重渲染都会耗费大量的时间。在点击 "click me " 按钮之后,整个页面陷入卡顿,等待 React 计算完全部的 diff 之后,<Foo /> 组件和列表才会更新。
在这个过程中,fiberUpdateQueue 大概长这样:
| [tag]type| ------------------------------------------------------------------------------------ | [root] | [class]<App> | ...30000个[class]<App>... | [class]<Foo> | -> null ------------------------------------------------------------------------------------- ^ ^ | | | v mount click commit
接下来使用 fiber 的特性,把这个 <App /> 组件划分优先级,让 React 优先处理 <Foo /> 组件的更新
class App extends React.Component { constructor() { super(); this.state = { offset: 0 }; } add = () => { ReactDOM.unstable_deferredUpdates(() => { this.setState(state => ({ offset: state.offset + 1})) }); } render() { const result = []; for (let i = 0; i < length; i++) { result.push(<li key={i}>{i + this.state.offset}</li>) } return <ul> <Foo /> <button onClick={this.add}> click me </button> {result} </ul> } }
add
unstable_deferredUpdates()
setInterval
此时的 fiberUpdateQueue 长这样
| [tag]type| ------------------------------------------------------------------------------------ | [root] | [class]<App> | 数个[class]<App> | [class]<Foo> | 剩下的 [class]<App> => ------------------------------------------------------------------------------------- ^ ^ ^ ^ | | | | mount click 500ms 后插入的高优先级 继续剩下的 | v commit ------------------------------------------------------------------------------------ => |..余下的 [class]<App> | -> null ------------------------------------------------------------------------------------- | v commitAll
The text was updated successfully, but these errors were encountered:
No branches or pull requests
ReactDOMFiberEntry.render() 干了些什么
由于 v16.0.0 已经使用了 ReactDOMFiberEntry 做渲染,所以在调用
ReactDOM.render
时,实际是在调用ReactDOMFiberEntry.render()
。调用流如下(点击跳转到 github 上相关源码):ReactDOM.render()
一步一步看:
unbatchedUpdates
(DOMRenderer.unbatchedUpdates()), 然后开始 updateContainer()。fiberUpdateQuene
调度队列中插入一条高优先级 (HighPriority) 的更新动作,之后开始执行 fiber 调度。在 setState() 发生的事情则简单一些:
=> enqueueSetState()
=> [addUpdate()]
=> [scheduleUpdate()]
在调用
setState()
后,fiberUpdater 会做以下几件事情:Fiber 更新队列 (fiberUpdateQueue)
Fiber 及 fiberUpdateQueue 的结构
Fiber 是一种最轻量化的线程(lightweight threads)。它是一种用户线程(user thread),让应用程序可以独立决定自己的线程要如何运作。
Fiber 是 React Fiber 的基本工作单元,简单来看,一个 Fiber 的数据结构关键字段如下:
fiberUpdateQueue 由一个或多个 fiber 连接而成:
scheduleUpdate() && performWork()
scheduleUpdate()
会从当前触发 scheduleUpdate 的节点开始,由 return 字段找父节点,直到找到根节点。如果正在执行工作,则不做任何事。否则,判断不同的优先级,从找到的根节点开始执行不同优先级的performWork()
performWork()
主要执行workLoop()
。 workLoop 将循环执行以下逻辑:createWorkInProgress()
获取当前正在执行的 fiber, 设置为nextUnitOfWork
performUnitOfWork()
nextUnitOfWork
的beginWork
阶段。beginWork
阶段将判断当前 fiber 的 tag,执行不同的生命周期函数。比如 ClassComponent 的 constructor, componentWillMount, componentWillUpdate 等就在这里执行。同时展开直接子节点,创建子节点的 fiber,回传给nextUnitOfWork
commitAllWork()
提交所有改动:prepareForCommit();
commitAllHostEffects();
commitWork();
commitUpdate();
updateFiberProps();
updateProperties();
// 更新 DOM 属性commitAllLifeCycles();
commitLifeCycles();
// componentDidMount, componentDidUpdate 在这执行。nextPriorityLevel
:SynchronousPriority
或TaskPriority
, 则表示有剩余的同步任务需要执行,则继续循环。fiberUpdateQueue 调度过程分解
接下来结合官方提供的 react/fixtures/fiber-debugger 工具来一步一步观察 fiber 具体是如何展开、调度的。
React 官方提供了 react-noop-renderer 用于调试 React,在 react-noop-renderer 的 ReactFiberInstrumentation.debugTool 添加对应的回调,可以在 fiber 的 be
ginWork、completeWork、commitWork 阶段时得到通知并记录下来。
准备工作
准备好要测试的 jsx。
在 fixures/fiber-debugger 文件夹下运行
将自动打开 localhost:3000,如下:
点击 “EDIT” 链接,在弹出的 textarea 中粘贴上面的那段 jsx,点击 “Run”,此时拖动 Slider,就可以一步一步调试 fiber 了。
组件展开为 fiber 的步骤分解
执行 root 的 beginWork, 并为子节点
<App />
创建 fiber, 添加入队列:执行 的 beginWork,并为子节点
<div />
创建 fiber,添加入队列:执行
<div />
的 beginWork, 并为子节点 "hello" 和<World />
分别创建 fiber,添加入队列:执行文字节点 "hello" 的 beginWork, 处理完毕,执行 "hello" 的 commitWork。(图中标记为紫色)
执行节点
<World />
的 beginWork,为子节点 "world" 创建 fiber,添加入队列。 !处理文字节点 "world" 的beginWork,处理完毕,执行 "world" 的 commitWork。
从节点 "world" 回溯执行 commitWork,直到跟节点。至此,
<App />
组件加载过程的 fiber 执行完毕。直观感受 fiber
接下来将用一个例子,直观感受一下 fiber.
这里创建了一个
<App />
组件,里边有一个<Foo />
组件, 一个按钮和一个长列表:<Foo />
组件每隔 500ms 就交换显示 “react” 和 "foo"运行这段代码,我们可以发现,由于列表非常之长,所以每次 diff 和重渲染都会耗费大量的时间。在点击 "click me " 按钮之后,整个页面陷入卡顿,等待 React 计算完全部的 diff 之后,
<Foo />
组件和列表才会更新。在这个过程中,fiberUpdateQueue 大概长这样:
接下来使用 fiber 的特性,把这个
<App />
组件划分优先级,让 React 优先处理<Foo />
组件的更新add
里,我们使用了unstable_deferredUpdates()
,把当前上下文的优先级设置为低 :https://github.com/facebook/react/blob/v16.0.0/src/renderers/shared/fiber/ReactFiberScheduler.js#L1553setState()
, 往 updateQueue 里添加了一条低优先级的更新 fiber(在上文 ReactDOM.render() 干了些什么 )里简述过。<Foo />
组件里定时setState()
触发的更新动作则是正常优先级。<Foo />
组件每隔 500ms 的更新并没有停止。React 在执行长列表的每个 Item 执行完毕后,会判断nextPriorityLevel
,此时优先执行setInterval
插入的正常优先级的更新动作。保证优先响应高优先级的任务。此时的 fiberUpdateQueue 长这样
其他
unstable_deferredUpdates()
来修改上下文的 fiber 优先级。REFERENCES
The text was updated successfully, but these errors were encountered: