React source code

115 阅读3分钟

React source code

ReactElement === React.createElement 返回的结果

例子:

function App() {
  return (
    <div className="App">
          <header className="App-header">
                <img src={logo} className="App-logo" alt="logo" />
                <p>
                  Edit <code>src/App.js</code> and save to reload.
                </p>
                <a
                  className="App-link"
                  href="https://reactjs.org"
                  target="_blank"
                  rel="noopener noreferrer"
                >
                  Learn React
                </a>
          </header>
    </div>
  );
}
export default App;
​
ReactDOM.render(<App />, document.getElementById("root"));

render阶段

递 mount

采用深度优先遍历的方法,执行beginWork 方法

beginWork 流程

——beginWork

    —— current -> `FilberRootNode`  /  `workInProgress ` -> `FilberRootNode`

—— beginWork

—— current -> null / workInProgress -> App /* 有子节点,继续向下递 */

—— beginWork

—— current -> null / workInProgress -> div /* 有子节点,继续向下递 */

—— beginWork

—— current -> null / workInProgress -> header /* 有子节点,继续向下递 */

—— beginWork

—— current -> null / workInProgress -> img

/* 深度优先遍历,img 没有子节点,所以会走到归阶段 */

—— completeWork

—— current -> null / workInProgress -> img /* 发现有兄弟节点,进入兄弟节点 */

——

  1. 根据双缓存机制,首次渲染的时候,beginWork 的 current 是指向根节点的,再次进入的时候,current是null, 其他节点只存于 workInProgress

归 mount

completeWork -> createElement -> appendAllChildren -> reconcilChildren

递 阶段 更新时候的流程 update

  1. 创建 workInProgress

    主要就是创建双缓存树

归 阶段 更新时候的流程 update

diff 算法

ReactChildFiber.old.js 文件下的 reconcileChildFibers 函数

多节点和单节点 Diff 本质: 比较 current fiberjsx 返回的树。

单节点

首屏渲染:

  1. 首先判断是否存在对应DOM节点。
  2. 不存在,则直接 createFiberFromElement 创建一个 fiber 树。
  3. 渲染到页面上。

更新渲染顺序:

  1. 上一次更新存在DOM节点,接下来判断是否可复用
  2. 首先比较 key 是否相同
  3. key 相同,接下来比较 type 是否相同
  4. type 相同,则表示可以复用, 返回复用的 fiber : 代码中的方法为: useFiber
  5. type 不同,则跳出当前判断。
  6. 如果 key 相同,但是 type 不同,将该fiber 以及兄弟 fiber 标记为删除
  7. 如果 key 不同 (对应第二步) 将该 fiber 标记为删除
  8. 最后,创建新 fiber 并返回。

多节点

对于第一轮遍历的结果,我们分别讨论:

#newChildrenoldFiber同时遍历完

那就是最理想的情况:只需在第一轮遍历进行组件更新 (opens new window)。此时Diff结束。

#newChildren没遍历完,oldFiber遍历完

已有的DOM节点都复用了,这时还有新加入的节点,意味着本次更新有新节点插入,我们只需要遍历剩下的newChildren为生成的workInProgress fiber依次标记Placement

你可以在这里 (opens new window)看到这段源码逻辑

#newChildren遍历完,oldFiber没遍历完

意味着本次更新比之前的节点数量少,有节点被删除了。所以需要遍历剩下的oldFiber,依次标记Deletion

你可以在这里 (opens new window)看到这段源码逻辑

#newChildrenoldFiber都没遍历完

这意味着有节点在这次更新中改变了位置。

这是Diff算法最精髓也是最难懂的部分。我们接下来会重点讲解。

将剩余 oldFiber 遍历生成一个 map 数据结构: keykey fibervalue

然后,将 newChildren 中的 key 作为条件查找 map 中是否存在,有就复用,没有就新创建,打上

标签,完成更新

update 链表 保存在 updateQueue