共计 5622 个字符,预计需要花费 15 分钟才能阅读完成。
一 引沿
Fiber 架构是 React16 中引入的新概念,目标就是解决大型 React 利用卡顿,React 在遍历更新每一个节点的时候都不是用的实在 DOM,都是采纳虚构 DOM,所以能够了解成 fiber 就是 React 的虚构 DOM,更新 Fiber 的过程叫做和谐,每一个 fiber 都能够作为一个执行单元来解决,所以每一个 fiber 能够依据本身的过期工夫 expirationTime,来判断是否还有空间工夫执行更新,如果没有工夫更新,就要把主动权交给浏览器去渲染,做一些动画,重排(reflow),重绘 repaints 之类的事件,这样就能给用户感觉不是很卡。
二 什么是和谐
和谐是一种算法,就是 React 比照新老虚构 DOM 的过程,以决定须要更新哪一部分。
三 什么是 Filber
Fiber 的目标是为了让 React 充分利用调度,以便做到如下几点:
- 暂停工作,稍后再回来
- 优先思考不同类型的工作
- 重用以前实现的工作
- 如果不再须要,则停止工作
为了实现下面的要求,咱们须要把工作拆分成一个个可执行的单元,这些可执行的单元就叫做一个 Fiber,一个 Fiber 就代表一个可执行的单元。
一个 Fiber 就是一个一般的 JS 对象,蕴含一些组件的相干信息。
function FiberNode(){ | |
this.tag = tag; // fiber 标签 证实是什么类型 fiber。this.key = key; // key 和谐子节点时候用到。this.type = null; // dom 元素是对应的元素类型,比方 div,组件指向组件对应的类或者函数。this.stateNode = null; // 指向对应的实在 dom 元素,类组件指向组件实例,能够被 ref 获取。this.return = null; // 指向父级 fiber | |
this.child = null; // 指向子级 fiber | |
this.sibling = null; // 指向兄弟 fiber | |
this.index = 0; // 索引 | |
this.ref = null; // ref 指向,ref 函数,或者 ref 对象。this.pendingProps = pendingProps;// 在一次更新中,代表 element 创立 | |
this.memoizedProps = null; // 记录上一次更新结束后的 props | |
this.updateQueue = null; // 类组件寄存 setState 更新队列,函数组件寄存 | |
this.memoizedState = null; // 类组件保留 state 信息,函数组件保留 hooks 信息,dom 元素为 null | |
this.dependencies = null; // context 或是工夫的依赖项 | |
this.mode = mode; // 形容 fiber 树的模式,比方 ConcurrentMode 模式 | |
this.effectTag = NoEffect; // effect 标签,用于收集 effectList | |
this.nextEffect = null; // 指向下一个 effect | |
this.firstEffect = null; // 第一个 effect | |
this.lastEffect = null; // 最初一个 effect | |
this.expirationTime = NoWork; // 通过不同过期工夫,判断工作是否过期,在 v17 版本用 lane 示意。this.alternate = null; // 双缓存树,指向缓存的 fiber。更新阶段,两颗树相互交替。} |
type 就是 react 的元素类型
export const FunctionComponent = 0; // 对应函数组件 | |
export const ClassComponent = 1; // 对应的类组件 | |
export const IndeterminateComponent = 2; // 初始化的时候不晓得是函数组件还是类组件 | |
export const HostRoot = 3; // Root Fiber 能够了解为跟元素,通过 reactDom.render() 产生的根元素 | |
export const HostPortal = 4; // 对应 ReactDOM.createPortal 产生的 Portal | |
export const HostComponent = 5; // dom 元素 比方 <div> | |
export const HostText = 6; // 文本节点 | |
export const Fragment = 7; // 对应 <React.Fragment> | |
export const Mode = 8; // 对应 <React.StrictMode> | |
export const ContextConsumer = 9; // 对应 <Context.Consumer> | |
export const ContextProvider = 10; // 对应 <Context.Provider> | |
export const ForwardRef = 11; // 对应 React.ForwardRef | |
export const Profiler = 12; // 对应 <Profiler/ > | |
export const SuspenseComponent = 13; // 对应 <Suspense> | |
export const MemoComponent = 14; // 对应 React.memo 返回的组件 |
比方元素构造如下:
export default class Parent extends React.Component{render(){ | |
return <div> | |
<h1>hello,world</h1> | |
<Child /> | |
</div> | |
} | |
} | |
function Child() {return <p>child</p>} |
对应的 Filber 构造如下:
有了下面的概念后咱们就本人实现一个 Fiber 的更新机制
四 实现和谐的过程
咱们通过渲染一段 jsx 来阐明 React 的和谐过程,也就是咱们要手写实现 ReactDOM.render()
const jsx = ( | |
<div className="border"> | |
<h1>hello</h1> | |
<a href="https://www.reactjs.org/">React</a> | |
</div> | |
) | |
ReactDOM.render( | |
jsx, | |
document.getElementById('root') | |
); |
1. 创立 FiberRoot
react-dom.js
function createFiberRoot(element, container){ | |
return {type: container.nodeName.toLocaleLowerCase(), | |
props: {children: element}, | |
stateNode: container | |
} | |
} | |
function render(element, container) {const FibreRoot = createFiberRoot(element, container) | |
scheduleUpdateOnFiber(FibreRoot) | |
} | |
export default {render} |
参考 React 实战视频解说:进入学习
2. render 阶段
和谐的外围是 render 和 commit,本文不讲调度过程,咱们会简略的用 requestIdleCallback 代替 React 的调度过程。
ReactFiberWorkloop.js
let wipRoot = null // work in progress | |
let nextUnitOfwork = null // 下一个 fiber 节点 | |
export function scheduleUpdateOnFiber(fiber) { | |
wipRoot = fiber | |
nextUnitOfwork = fiber | |
} | |
function workLoop(IdleDeadline) {while(nextUnitOfwork && IdleDeadline.timeRemaining() > 0) {nextUnitOfwork = performUnitOfWork(nextUnitOfwork) | |
} | |
} | |
function performUnitOfWork() {} | |
requestIdleCallback(workLoop) |
每一个 fiber 能够看作一个执行的单元,在和谐过程中,每一个产生更新的 fiber 都会作为一次 workInProgress。那么 workLoop 就是执行每一个单元的调度器,如果渲染没有被中断,那么 workLoop 会遍历一遍 fiber 树
performUnitOfWork 包含两个阶段:
- 是向下和谐的过程,就是由 fiberRoot 依照 child 指针逐层向下和谐,期间会执行函数组件,实例类组件,diff 和谐子节点
- 是向上归并的过程,如果有兄弟节点,会返回 sibling 兄弟,没有返回 return 父级,始终返回到 fiebrRoot
这么一上一下,形成了整个 fiber 树的和谐。
import {updateHostComponent} from './ReactFiberReconciler' | |
function performUnitOfWork(wip) { | |
// 1. 更新 wip | |
const {type} = wip | |
if (isStr(type)) { | |
// type 是 string,更新一般元素节点 | |
updateHostComponent(wip) | |
} else if (isFn(type)) {// ...} | |
// 2. 返回下一个要更新的工作 深度优先遍历 | |
if (wip.child) {return wip.child} | |
let next = wip | |
while(next) {if (next.sibling) {return next.sibling} | |
next = next.return | |
} | |
return null | |
} |
依据 type 类型辨别是 FunctionComponent/ClassComponent/HostComponent/…
本文中只解决 HostComponent 类型,其余类型的解决能够看文末的残缺代码链接。
ReactFiberReconciler.js
import {createFiber} from './createFiber' | |
export function updateHostComponent(wip) {if (!wip.stateNode) {wip.stateNode = document.createElement(wip.type); | |
updateNode(wip.stateNode, wip.props); | |
} | |
// 和谐子节点 | |
reconcileChildren(wip, wip.props.children); | |
} | |
function reconcileChildren(returnFiber, children) {if (isStr(children)) {return} | |
const newChildren = isArray(children) ? children : [children]; | |
let previousNewFiber = null | |
for(let i = 0; i < newChildren.length; i++) {const newChild = newChildren[i]; | |
const newFiber = createFiber(newChild, returnFiber) | |
if (previousNewFiber === null) {returnFiber.child = newFiber} else {previousNewFiber.sibling = newFiber} | |
previousNewFiber = newFiber | |
} | |
} | |
function updateNode(node, nextVal) {Object.keys(nextVal).forEach((k) => {if (k === "children") {if (isStringOrNumber(nextVal[k])) {node.textContent = nextVal[k]; | |
} | |
} else {node[k] = nextVal[k]; | |
} | |
}); | |
} |
createFiber.js
export function createFiber(vnode, returnFiber) { | |
const newFiber = { | |
type: vnode.type, // 标记节点类型 | |
key: vnode.key, // 标记节点在以后层级下的唯一性 | |
props: vnode.props, // 属性 | |
stateNode: null, // 如果组件是原生标签则是 dom 节点,如果是类组件则是类实例 | |
child: null, // 第一个子节点 | |
return: returnFiber,// 父节点 | |
sibling: null, // 下一个兄弟节点 | |
}; | |
return newFiber; | |
} |
至此曾经实现了 render 阶段,上面是 commit 阶段,commit 阶段就是根据 Fiber 构造操作 DOM
function workLoop(IdleDeadline) {while(nextUnitOfwork && IdleDeadline.timeRemaining() > 0) {nextUnitOfwork = performUnitOfWork(nextUnitOfwork) | |
} | |
// commit | |
if (!nextUnitOfwork && wipRoot) {commitRoot(); | |
} | |
} | |
function commitRoot() {commitWorker(wipRoot.child) | |
wipRoot = null; | |
} | |
function commitWorker(wip) {if (!wip) {return} | |
// 1. 提交本人 | |
const {stateNode} = wip | |
let parentNode = wip.return.stateNode | |
if (stateNode) {parentNode.appendChild(stateNode); | |
} | |
// 2. 提交子节点 | |
commitWorker(wip.child); | |
// 3. 提交兄弟节点 | |
commitWorker(wip.sibling); | |
} |
五 总结
- Fiber 构造,Fiber 的生成过程。
- 和谐过程,以及 render 和 commit 两大阶段。