前言
这篇文章循序渐进地介绍实现以下几个概念,遵循本篇文章根本就能搞懂为啥须要fiber,为啥须要commit和phases、reconciliation阶段等原理。本篇文章又不齐全和原文统一,这里会退出我本人的一些思考,比方通过performUnitOfWork
解决后fiber tree和element tree的分割等。
- createElement函数
- render函数
- Concurrent Mode
- Fibers
- Render and Commit Phases
- Reconciliation
- Function Components
- Hooks
第一章 基本概念
以上面代码为例
// 1.jsx语法不是非法的js语法// const element = <h1 title="foo">Hello</h1>// 2.经babel等编译工具将jsx转换成js,将jsx转换成createElement函数调用的形式// const element = React.createElement(// "h1",// { title: "foo" },// "Hello"// )// 3.React.createElement返回的最终对象大抵如下:const element = { type: "h1", props: { title: "foo", children: "Hello", },}const container = document.getElementById("root")// ReactDOM.render(element, container)// 4.替换ReactDOM.render函数的逻辑,ReactDOM.render大抵解决逻辑:const node = document.createElement(element.type)node['title'] = element.props.titleconst text = document.createTextNode("")text["nodeValue"] = element.props.childrennode.appendChild(text)container.appendChild(node)
为了防止歧义,这里应用 element
示意 React elements
,node
示意实在的DOM元素节点。
至此,这段代码无需通过任何编译曾经可能在浏览器上跑起来了,不信你能够复制到浏览器控制台试试
这里有几点须要留神:
- 先通过
node.appendChild(text)
将子元素增加到父元素,而后再通过container.appendChild(node)
将父元素增加到容器container
中触发浏览器渲染页面。这个程序不能反过来,也就是说只有整个实在dom树构建实现能力增加到容器中。假如这个程序反过来,比方先执行container.appendChild(node)
,则触发浏览器回流。再执行node.appendChild(text)
又触发浏览器回流。性能极差 React.createElement
返回的最终的对象就是virtual dom
树,ReactDOM.render
依据这个virtual dom
创立实在的dom树
第二章 createElement 函数
以上面的代码为例
React.createElement
接管的children有可能是原子值,比方字符串或者数字等,React.createElement('h1', {title: 'foo'}, 'Hello')
。为了简化咱们的代码,创立一个非凡的TEXT_ELEMENT
类型将其转换成对象
React.createElement = (type, props, ...children) => { return { type, props: { ...props, children: children.map(child => { if(typeof child === 'object'){ return child } return { type: 'TEXT_ELEMENT', props: { nodeValue: child, children: [], } } }) } }}// const element = (// <div id="foo">// <a>bar</a>// <b />// </div>// )// 将jsx转换成js语法const element = React.createElement( "div", { id: "foo" }, React.createElement("a", null, "bar"), React.createElement("b"))const container = document.getElementById("root")ReactDOM.render(element, container)
好了,当初咱们曾经实现了一个简略的createElement
函数,咱们能够通过一段非凡的正文来通知babel在将jsx转换成js时应用咱们本人的createElement
函数:
const MiniReact = { createElement: (type, props, ...children) => { return { type, props: { ...props, children: children.map(child => { if(typeof child === 'object'){ return child } return { type: 'TEXT_ELEMENT', props: { nodeValue: child, children: [], } } }) } } }}/** @jsx MiniReact.createElement */const element = ( <div id="foo"> <a>bar</a> <b /> </div>)console.log('element======', element)const container = document.getElementById("root")ReactDOM.render(element, container)
第三章 render函数
import React from 'react';function render(element, container) { const dom = element.type === 'TEXT_ELEMENT' ? document.createTextNode("") : document.createElement(element.type) const isProperty = key => key !== 'children' Object.keys(element.props) .filter(isProperty) .forEach(name => { dom[name] = element.props[name] }) element.props.children.forEach(child => { render(child, dom) }); container.appendChild(dom)}const MiniReact = { createElement: (type, props, ...children) => { return { type, props: { ...props, children: children.map(child => { if(typeof child === 'object'){ return child } return { type: 'TEXT_ELEMENT', props: { nodeValue: child, children: [], } } }) } } }, render}/** @jsx MiniReact.createElement */const element = ( <div id="foo"> <a>bar</a> <b /> </div>)console.log('element======', element)const container = document.getElementById("root")MiniReact.render(element, container)
render
函数递归创立实在的dom元素,而后将各个元素append到其父元素中,最初整个dom树append到root container中,渲染实现,这个过程一旦开始,两头是无奈打断的,直到整个利用渲染实现。这也是React16
版本以前的渲染过程
留神,只有当整个dom树append到root container中时,页面才会显示
第四章 Concurrent Mode
在第三章中能够看到,以后版本的render
函数是递归构建dom树,最初才append到root container,最终页面才渲染进去。这里有个问题,如果dom节点数量宏大,递归层级过深,这个过程其实是很耗时的,导致render
函数长时间占用主线程,浏览器无奈响应用户输出等事件,造成卡顿的景象。参考React实战视频解说:进入学习
因而咱们须要将render
过程拆分成小的工作单元,每执行完一个单元,都容许浏览器打断render
过程并执行高优先级的工作,等浏览器无暇再继续执行render
过程
如果对requestIdleCallback
不相熟的,能够自行理解一下。实在React代码中并没有应用这个api,因为有兼容性问题。因而React应用scheduler package
模仿这个调度过程
let nextUnitOfWork = nullfunction workLoop(deadline) { let shouldYield = false while (nextUnitOfWork && !shouldYield) { nextUnitOfWork = performUnitOfWork( nextUnitOfWork ) shouldYield = deadline.timeRemaining() < 1 } requestIdleCallback(workLoop)}requestIdleCallback(workLoop)function performUnitOfWork(nextUnitOfWork) { // TODO}
performUnitOfWork
接管当前工作单元,并返回下一个工作单元。工作单元能够了解为就是一个fiber对象节点
workLoop
循环里会循环调用performUnitOfWork
,直到所有工作单元都曾经处理完毕,或者以后帧浏览器曾经没有闲暇工夫,则循环终止。等下次浏览器闲暇工夫再接着继续执行
因而咱们须要一种数据结构,可能反对工作打断并且能够接着继续执行,很显然,链表就非常适合
第五章 Fibers
Fibers就是一种数据结构,反对将渲染过程拆分成工作单元,实质上就是一个双向链表。这种数据结构的益处就是不便找到下一个工作单元
Fiber蕴含三层含意:
- 作为架构来说,之前
React 15
的Reconciler
采纳递归的形式执行,数据保留在递归调用栈中,所以被称为stack Reconciler
。React 16
的Reconciler
基于Fiber节点
实现,被称为Fiber Reconciler
- 作为动态的数据结构来说,每个
Fiber节点
对应一个React Element
,保留了该组件的类型(函数组件/类组件/html标签)、对应的DOM节点信息等 - 作为动静的工作单元来说,每个
Fiber节点
保留了本次更新中该组件扭转的状态、要执行的工作等
Fiber的几点冷常识:
- 一个Fiber节点对应一个React Element节点,同时也是一个工作单元
- 每个fiber节点都有指向第一个子元素,下一个兄弟元素,父元素的指针**
以上面代码为例:
MiniReact.render( <div> <h1> <p /> <a /> </h1> <h2 /> </div>, container)
对应的fiber tree如下:
import React from 'react';// 依据fiber节点创立实在的dom节点function createDom(fiber) { const dom = fiber.type === 'TEXT_ELEMENT' ? document.createTextNode("") : document.createElement(fiber.type) const isProperty = key => key !== 'children' Object.keys(fiber.props) .filter(isProperty) .forEach(name => { dom[name] = fiber.props[name] }) return dom}let nextUnitOfWork = null// render函数次要逻辑:// 依据root container容器创立root fiber// 将nextUnitOfWork指针指向root fiber// element是react element treefunction render(element, container){ nextUnitOfWork = { dom: container, props: { children: [element], // 此时的element还只是React.createElement函数创立的virtual dom树 }, }}function workLoop(deadline) { let shouldYield = false while (nextUnitOfWork && !shouldYield) { nextUnitOfWork = performUnitOfWork(nextUnitOfWork) shouldYield = deadline.timeRemaining() < 1 } requestIdleCallback(workLoop)}requestIdleCallback(workLoop)// performUnitOfWork函数次要逻辑:// 将element元素增加到DOM// 给element的子元素创立对应的fiber节点// 返回下一个工作单元,即下一个fiber节点,查找过程:// 1.如果有子元素,则返回子元素的fiber节点// 2.如果没有子元素,则返回兄弟元素的fiber节点// 3.如果既没有子元素又没有兄弟元素,则往上查找其父节点的兄弟元素的fiber节点// 4.如果往上查找到root fiber节点,阐明render过程曾经完结function performUnitOfWork(fiber) { // 第一步 依据fiber节点创立实在的dom节点,并保留在fiber.dom属性中 if(!fiber.dom){ fiber.dom = createDom(fiber) } // 第二步 将以后fiber节点的实在dom增加到父节点中,留神,这一步是会触发浏览器回流重绘的!!! if(fiber.parent){ fiber.parent.dom.appendChild(fiber.dom) } // 第三步 给子元素创立对应的fiber节点 const children = fiber.props.children let prevSibling children.forEach((child, index) => { const newFiber = { type: child.type, props: child.props, parent: fiber, dom: null } if(index === 0){ fiber.child = newFiber } else { prevSibling.sibling = newFiber } prevSibling = newFiber }) // 第四步,查找下一个工作单元 if(fiber.child){ return fiber.child } let nextFiber = fiber while(nextFiber){ if(nextFiber.sibling){ return nextFiber.sibling } nextFiber = nextFiber.parent }}const MiniReact = { createElement: (type, props, ...children) => { return { type, props: { ...props, children: children.map(child => { if(typeof child === 'object'){ return child } return { type: 'TEXT_ELEMENT', props: { nodeValue: child, children: [], } } }) } } }, render}/** @jsx MiniReact.createElement */const element = ( <div> <h1> <p /> <a /> </h1> <h2 /> </div>)// const element = (// <div id="foo">// <a>bar</a>// <b />// </div>// )console.log('element======', element)const container = document.getElementById("root")MiniReact.render(element, container)
这里有一点值得细品,React.createElement
返回的element tree
和performUnitOfWork
创立的fiber tree
有什么分割。如下图所示:
React Element Tree
是由React.createElement
办法创立的树形构造对象Fiber Tree
是依据React Element Tree
创立来的树。每个Fiber节点保留着实在的DOM节点,并且保留着对React Element Tree
中对应的Element
节点的利用。留神,Element
节点并不会保留对Fiber
节点的利用
第六章 Render and Commit Phases
第五章的performUnitOfWork
有些问题,在第二步中咱们间接将新创建的实在dom节点挂载到了容器上,这样会带来两个问题:
- 每次执行
performUnitOfWork
都会造成浏览器回流重绘,因为实在的dom曾经被增加到浏览器上了,性能极差 - 浏览器是能够打断渲染过程的,因而可能会造成用户看到不残缺的UI界面
咱们须要革新一下咱们的代码,在performUnitOfWork
阶段不把实在的dom节点挂载到容器上。保留fiber tree根节点的援用。等到fiber tree构建实现,再一次性提交实在的dom节点并且增加到容器上。
import React from 'react';function createDom(fiber) { const dom = fiber.type === 'TEXT_ELEMENT' ? document.createTextNode("") : document.createElement(fiber.type) const isProperty = key => key !== 'children' Object.keys(fiber.props) .filter(isProperty) .forEach(name => { dom[name] = fiber.props[name] }) return dom}let nextUnitOfWork = nulllet wipRoot = nullfunction render(element, container){ wipRoot = { dom: container, props: { children: [element], // 此时的element还只是React.createElement函数创立的virtual dom树 }, } nextUnitOfWork = wipRoot}function commitRoot(){ commitWork(wipRoot.child) wipRoot = null}function commitWork(fiber){ if(!fiber){ return } const domParent = fiber.parent.dom; domParent.appendChild(fiber.dom) commitWork(fiber.child) commitWork(fiber.sibling)}function workLoop(deadline) { let shouldYield = false while (nextUnitOfWork && !shouldYield) { nextUnitOfWork = performUnitOfWork(nextUnitOfWork) shouldYield = deadline.timeRemaining() < 1 } if(!nextUnitOfWork && wipRoot){ commitRoot() } requestIdleCallback(workLoop)}requestIdleCallback(workLoop)function performUnitOfWork(fiber) { // 第一步 依据fiber节点创立实在的dom节点,并保留在fiber.dom属性中 if(!fiber.dom){ fiber.dom = createDom(fiber) } // 第二步 将以后fiber节点的实在dom增加到父节点中,留神,这一步是会触发浏览器回流重绘的!!! // if(fiber.parent){ // fiber.parent.dom.appendChild(fiber.dom) // } // 第三步 给子元素创立对应的fiber节点 const children = fiber.props.children let prevSibling children.forEach((child, index) => { const newFiber = { type: child.type, props: child.props, parent: fiber, dom: null } if(index === 0){ fiber.child = newFiber } else { prevSibling.sibling = newFiber } prevSibling = newFiber }) // 第四步,查找下一个工作单元 if(fiber.child){ return fiber.child } let nextFiber = fiber while(nextFiber){ if(nextFiber.sibling){ return nextFiber.sibling } nextFiber = nextFiber.parent }}const MiniReact = { createElement: (type, props, ...children) => { return { type, props: { ...props, children: children.map(child => { if(typeof child === 'object'){ return child } return { type: 'TEXT_ELEMENT', props: { nodeValue: child, children: [], } } }) } } }, render}/** @jsx MiniReact.createElement */const element = ( <div> <h1> <p /> <a /> </h1> <h2 /> </div>)// const element = (// <div id="foo">// <a>bar</a>// <b />// </div>// )console.log('element======', element)const container = document.getElementById("root")MiniReact.render(element, container)
第七章 Reconciliation
目前为止,咱们只思考增加dom节点到容器上这一繁多场景,更新删除还没实现。
咱们须要比照最新的React Element Tree
和最近一次的Fiber Tree
的差别
咱们须要给每个fiber节点增加一个alternate属性来保留旧的fiber节点
alternate保留的旧的fiber节点次要有以下几个用处:
- 复用旧fiber节点上的实在dom节点
- 旧fiber节点上的props和新的element节点的props比照
- 旧fiber节点上保留有更新的队列,在创立新的fiber节点时执行这些队列以获取最新的页面
const children = fiber.props.children reconcileChildren(fiber, children) function reconcileChildren(wipFiber, elements) { let index = 0 let oldFiber = wipFiber.alternate && wipFiber.alternate.child let prevSibling = null while (index < elements.length || oldFiber != null) { const element = elements[index] let newFiber = null const sameType = oldFiber && element && element.type == oldFiber.type if (sameType) { newFiber = { type: oldFiber.type, props: element.props, dom: oldFiber.dom, parent: wipFiber, alternate: oldFiber, effectTag: "UPDATE", } } if (element && !sameType) { newFiber = { type: element.type, props: element.props, dom: null, parent: wipFiber, alternate: null, effectTag: "PLACEMENT", } } if (oldFiber && !sameType) { oldFiber.effectTag = "DELETION" deletions.push(oldFiber) } if (oldFiber) { oldFiber = oldFiber.sibling } if (index === 0) { wipFiber.child = newFiber } else if (element) { prevSibling.sibling = newFiber } prevSibling = newFiber index++ }}
如上代码所示:
协调过程:
- 实质上仍然是依据
新的React Element Tree
创立新的Fiber Tree
,不过为了节俭内存开销,协调过程会判断新的fiber节点是否复用旧的fiber节点上的实在dom元素,如果能复用,就不须要再从头到尾全副从新创立一遍实在的dom元素。同时每个新fiber节点上还会保留着对旧fiber节点的援用,不便在commit阶段做新旧属性props的比照。 - 如果
old fiber.type
和new element.type
雷同,则保留旧的dom节点,只更新props属性 - 如果
type
不雷同并且有new element
,则创立一个新的实在dom节点 - 如果
type
不同并且有old fiber
节点,则删除该节点对应的实在dom节点 - 删除节点须要有个专门的数组收集须要删除的旧的fiber节点。因为新的element tree创立进去的新的fiber tree不存在对应的dom,因而须要收集旧的fiber节点,并在commit阶段删除
留神,协调过程,还是以最新的React Element Tree为主去创立一个新的fiber tree,只不过是新的fiber节点复用旧的fiber节点的实在dom元素,毕竟频繁创立实在dom是很耗费内存的。新的fiber节点还是会保留着对旧的fiber节点的援用,不便在commit阶段进行新属性和旧属性的比拟。这里会有个问题,如果新fiber节点保留旧fiber节点的援用,那么随着更新次数越来越多,旧的fiber tree是不是也会越来越多,如何销毁?
import React from 'react';function createDom(fiber) { const dom = fiber.type === 'TEXT_ELEMENT' ? document.createTextNode("") : document.createElement(fiber.type) updateDom(dom, {}, fiber.props) return dom}let nextUnitOfWork = nulllet wipRoot = null // 保留着对root fiber的援用let currentRoot = null // 保留着当前页面对应的fiber treelet deletions = nullfunction render(element, container){ wipRoot = { dom: container, props: { children: [element], // 此时的element还只是React.createElement函数创立的virtual dom树 }, alternate: currentRoot, } deletions = [] nextUnitOfWork = wipRoot}function commitRoot(){ deletions.forEach(commitWork) commitWork(wipRoot.child) currentRoot = wipRoot wipRoot = null}const isEvent = key => key.startsWith("on")const isProperty = key => key !== "children" && !isEvent(key)const isNew = (prev, next) => key => prev[key] !== next[key]const isGone = (prev, next) => key => !(key in next)function updateDom(dom, prevProps, nextProps) { //Remove old or changed event listeners Object.keys(prevProps) .filter(isEvent) .filter( key => !(key in nextProps) || isNew(prevProps, nextProps)(key) ) .forEach(name => { const eventType = name .toLowerCase() .substring(2) dom.removeEventListener( eventType, prevProps[name] ) }) // Remove old properties Object.keys(prevProps) .filter(isProperty) .filter(isGone(prevProps, nextProps)) .forEach(name => { dom[name] = "" }) // Set new or changed properties Object.keys(nextProps) .filter(isProperty) .filter(isNew(prevProps, nextProps)) .forEach(name => { dom[name] = nextProps[name] }) // Add event listeners Object.keys(nextProps) .filter(isEvent) .filter(isNew(prevProps, nextProps)) .forEach(name => { const eventType = name .toLowerCase() .substring(2) dom.addEventListener( eventType, nextProps[name] ) })}function commitWork(fiber){ if(!fiber){ return } const domParent = fiber.parent.dom; if (fiber.effectTag === "PLACEMENT" && fiber.dom != null) { domParent.appendChild(fiber.dom) } else if (fiber.effectTag === "UPDATE" && fiber.dom != null) { updateDom( fiber.dom, fiber.alternate.props, fiber.props ) } else if (fiber.effectTag === "DELETION") { domParent.removeChild(fiber.dom) } commitWork(fiber.child) commitWork(fiber.sibling)}function workLoop(deadline) { let shouldYield = false while (nextUnitOfWork && !shouldYield) { nextUnitOfWork = performUnitOfWork(nextUnitOfWork) shouldYield = deadline.timeRemaining() < 1 } if(!nextUnitOfWork && wipRoot){ commitRoot() } requestIdleCallback(workLoop)}requestIdleCallback(workLoop)function reconcileChildren(wipFiber, elements) { let index = 0 let oldFiber = wipFiber.alternate && wipFiber.alternate.child let prevSibling = null while (index < elements.length || oldFiber != null) { const element = elements[index] let newFiber = null const sameType = oldFiber && element && element.type == oldFiber.type if (sameType) { newFiber = { type: oldFiber.type, props: element.props, dom: oldFiber.dom, parent: wipFiber, alternate: oldFiber, effectTag: "UPDATE", } } if (element && !sameType) { newFiber = { type: element.type, props: element.props, dom: null, parent: wipFiber, alternate: null, effectTag: "PLACEMENT", } } if (oldFiber && !sameType) { oldFiber.effectTag = "DELETION" deletions.push(oldFiber) } if (oldFiber) { oldFiber = oldFiber.sibling } if (index === 0) { wipFiber.child = newFiber } else if (element) { prevSibling.sibling = newFiber } prevSibling = newFiber index++ }}function performUnitOfWork(fiber) { // 第一步 依据fiber节点创立实在的dom节点,并保留在fiber.dom属性中 if(!fiber.dom){ fiber.dom = createDom(fiber) } // 第二步 将以后fiber节点的实在dom增加到父节点中,留神,这一步是会触发浏览器回流重绘的!!! // if(fiber.parent){ // fiber.parent.dom.appendChild(fiber.dom) // } // 第三步 给子元素创立对应的fiber节点 const children = fiber.props.children // let prevSibling // children.forEach((child, index) => { // const newFiber = { // type: child.type, // props: child.props, // parent: fiber, // dom: null // } // if(index === 0){ // fiber.child = newFiber // } else { // prevSibling.sibling = newFiber // } // prevSibling = newFiber // }) reconcileChildren(fiber, children) // 第四步,查找下一个工作单元 if(fiber.child){ return fiber.child } let nextFiber = fiber while(nextFiber){ if(nextFiber.sibling){ return nextFiber.sibling } nextFiber = nextFiber.parent }}const MiniReact = { createElement: (type, props, ...children) => { return { type, props: { ...props, children: children.map(child => { if(typeof child === 'object'){ return child } return { type: 'TEXT_ELEMENT', props: { nodeValue: child, children: [], } } }) } } }, render}/** @jsx MiniReact.createElement */const container = document.getElementById("root")const updateValue = e => { rerender(e.target.value)}const rerender = value => { const element = ( <div> <input onInput={updateValue} value={value} /> <h2>Hello {value}</h2> </div> ) MiniReact.render(element, container)}rerender("World")
第八章 Function Components
本章以上面的代码为例:
/** @jsx MiniReact.createElement */const container = document.getElementById("root")function App(props){ return <h1>Hi { props.name }</h1>}const element = <App name="foo" />MiniReact.render(element, container)
jsx通过babel编译后:
function App(props) { return MiniReact.createElement("h1", null, "Hi ", props.name);}const element = MiniReact.createElement(App, { name: "foo"});
函数组件有两点不同的中央:
- 函数组件对应的fiber节点没有对应的实在dom元素
- 须要执行函数能力获取对应的children节点,而不是间接从
props.children
获取
因为函数组件没有对应的fiber节点,因而在commit阶段在找父fiber节点对应的dom时,须要判断是否存在该dom元素
本章残缺代码:
import React from 'react';function createDom(fiber) { const dom = fiber.type === 'TEXT_ELEMENT' ? document.createTextNode("") : document.createElement(fiber.type) updateDom(dom, {}, fiber.props) return dom}let nextUnitOfWork = nulllet wipRoot = null // 保留着对root fiber的援用let currentRoot = null // 保留着当前页面对应的fiber treelet deletions = nullfunction render(element, container){ wipRoot = { dom: container, props: { children: [element], // 此时的element还只是React.createElement函数创立的virtual dom树 }, alternate: currentRoot, } deletions = [] nextUnitOfWork = wipRoot}function commitRoot(){ deletions.forEach(commitWork) commitWork(wipRoot.child) currentRoot = wipRoot wipRoot = null}const isEvent = key => key.startsWith("on")const isProperty = key => key !== "children" && !isEvent(key)const isNew = (prev, next) => key => prev[key] !== next[key]const isGone = (prev, next) => key => !(key in next)function updateDom(dom, prevProps, nextProps) { //Remove old or changed event listeners Object.keys(prevProps) .filter(isEvent) .filter( key => !(key in nextProps) || isNew(prevProps, nextProps)(key) ) .forEach(name => { const eventType = name .toLowerCase() .substring(2) dom.removeEventListener( eventType, prevProps[name] ) }) // Remove old properties Object.keys(prevProps) .filter(isProperty) .filter(isGone(prevProps, nextProps)) .forEach(name => { dom[name] = "" }) // Set new or changed properties Object.keys(nextProps) .filter(isProperty) .filter(isNew(prevProps, nextProps)) .forEach(name => { dom[name] = nextProps[name] }) // Add event listeners Object.keys(nextProps) .filter(isEvent) .filter(isNew(prevProps, nextProps)) .forEach(name => { const eventType = name .toLowerCase() .substring(2) dom.addEventListener( eventType, nextProps[name] ) })}function commitWork(fiber){ if(!fiber){ return } let domParentFiber = fiber.parent while(!domParentFiber.dom){ domParentFiber = domParentFiber.parent } const domParent = domParentFiber.dom; if (fiber.effectTag === "PLACEMENT" && fiber.dom != null) { domParent.appendChild(fiber.dom) } else if (fiber.effectTag === "UPDATE" && fiber.dom != null) { updateDom(fiber.dom, fiber.alternate.props, fiber.props) } else if (fiber.effectTag === "DELETION") { // domParent.removeChild(fiber.dom) commitDeletion(fiber, domParent) } commitWork(fiber.child) commitWork(fiber.sibling)}function commitDeletion(fiber, domParent){ if(fiber.dom){ domParent.removeChild(fiber.dom) } else { commitDeletion(fiber.child, domParent) }}function workLoop(deadline) { let shouldYield = false while (nextUnitOfWork && !shouldYield) { nextUnitOfWork = performUnitOfWork(nextUnitOfWork) shouldYield = deadline.timeRemaining() < 1 } if(!nextUnitOfWork && wipRoot){ commitRoot() } requestIdleCallback(workLoop)}requestIdleCallback(workLoop)function reconcileChildren(wipFiber, elements) { let index = 0 let oldFiber = wipFiber.alternate && wipFiber.alternate.child let prevSibling = null while (index < elements.length || oldFiber != null) { const element = elements[index] let newFiber = null const sameType = oldFiber && element && element.type == oldFiber.type if (sameType) { newFiber = { type: oldFiber.type, props: element.props, dom: oldFiber.dom, parent: wipFiber, alternate: oldFiber, effectTag: "UPDATE", } } if (element && !sameType) { newFiber = { type: element.type, props: element.props, dom: null, parent: wipFiber, alternate: null, effectTag: "PLACEMENT", } } if (oldFiber && !sameType) { oldFiber.effectTag = "DELETION" deletions.push(oldFiber) } if (oldFiber) { oldFiber = oldFiber.sibling } if (index === 0) { wipFiber.child = newFiber } else if (element) { prevSibling.sibling = newFiber } prevSibling = newFiber index++ }}function performUnitOfWork(fiber) { // 1.函数组件对应的fiber节点没有实在dom元素 // 2.函数组件须要运行函数获取children const isFunctionComponent = fiber.type instanceof Function if(!isFunctionComponent && !fiber.dom){ fiber.dom = createDom(fiber) } const children = isFunctionComponent ? [fiber.type(fiber.props)] : fiber.props.children // 第二步 为每一个新的react element节点创立对应的fiber节点,并判断旧的fiber节点上的实在dom元素是否能够复用。 // 节俭创立实在dom元素的开销 reconcileChildren(fiber, children) // 第三步,查找下一个工作单元 if(fiber.child){ return fiber.child } let nextFiber = fiber while(nextFiber){ if(nextFiber.sibling){ return nextFiber.sibling } nextFiber = nextFiber.parent }}const MiniReact = { createElement: (type, props, ...children) => { return { type, props: { ...props, children: children.map(child => { if(typeof child === 'object'){ return child } return { type: 'TEXT_ELEMENT', props: { nodeValue: child, children: [], } } }) } } }, render}/** @jsx MiniReact.createElement */const container = document.getElementById("root")function App(props){ return <h1>Hi { props.name }</h1>}const element = <App name="foo" />MiniReact.render(element, container)
第九章 Hooks
本章残缺代码
import React from 'react';function createDom(fiber) { const dom = fiber.type === 'TEXT_ELEMENT' ? document.createTextNode("") : document.createElement(fiber.type) updateDom(dom, {}, fiber.props) return dom}let nextUnitOfWork = nulllet wipRoot = null // 保留着对root fiber的援用let currentRoot = null // 保留着当前页面对应的fiber treelet deletions = nullfunction render(element, container){ wipRoot = { dom: container, props: { children: [element], // 此时的element还只是React.createElement函数创立的virtual dom树 }, alternate: currentRoot, } deletions = [] nextUnitOfWork = wipRoot}function commitRoot(){ deletions.forEach(commitWork) commitWork(wipRoot.child) currentRoot = wipRoot wipRoot = null}const isEvent = key => key.startsWith("on")const isProperty = key => key !== "children" && !isEvent(key)const isNew = (prev, next) => key => prev[key] !== next[key]const isGone = (prev, next) => key => !(key in next)function updateDom(dom, prevProps, nextProps) { //Remove old or changed event listeners Object.keys(prevProps) .filter(isEvent) .filter( key => !(key in nextProps) || isNew(prevProps, nextProps)(key) ) .forEach(name => { const eventType = name .toLowerCase() .substring(2) dom.removeEventListener( eventType, prevProps[name] ) }) // Remove old properties Object.keys(prevProps) .filter(isProperty) .filter(isGone(prevProps, nextProps)) .forEach(name => { dom[name] = "" }) // Set new or changed properties Object.keys(nextProps) .filter(isProperty) .filter(isNew(prevProps, nextProps)) .forEach(name => { dom[name] = nextProps[name] }) // Add event listeners Object.keys(nextProps) .filter(isEvent) .filter(isNew(prevProps, nextProps)) .forEach(name => { const eventType = name .toLowerCase() .substring(2) dom.addEventListener( eventType, nextProps[name] ) })}function commitWork(fiber){ if(!fiber){ return } let domParentFiber = fiber.parent while(!domParentFiber.dom){ domParentFiber = domParentFiber.parent } const domParent = domParentFiber.dom; if (fiber.effectTag === "PLACEMENT" && fiber.dom != null) { domParent.appendChild(fiber.dom) } else if (fiber.effectTag === "UPDATE" && fiber.dom != null) { updateDom(fiber.dom, fiber.alternate.props, fiber.props) } else if (fiber.effectTag === "DELETION") { // domParent.removeChild(fiber.dom) commitDeletion(fiber, domParent) } commitWork(fiber.child) commitWork(fiber.sibling)}function commitDeletion(fiber, domParent){ if(fiber.dom){ domParent.removeChild(fiber.dom) } else { commitDeletion(fiber.child, domParent) }}function workLoop(deadline) { let shouldYield = false while (nextUnitOfWork && !shouldYield) { nextUnitOfWork = performUnitOfWork(nextUnitOfWork) shouldYield = deadline.timeRemaining() < 1 } if(!nextUnitOfWork && wipRoot){ commitRoot() } requestIdleCallback(workLoop)}requestIdleCallback(workLoop)function reconcileChildren(wipFiber, elements) { let index = 0 let oldFiber = wipFiber.alternate && wipFiber.alternate.child let prevSibling = null while (index < elements.length || oldFiber != null) { const element = elements[index] let newFiber = null const sameType = oldFiber && element && element.type == oldFiber.type if (sameType) { newFiber = { type: oldFiber.type, props: element.props, dom: oldFiber.dom, parent: wipFiber, alternate: oldFiber, effectTag: "UPDATE", } } if (element && !sameType) { newFiber = { type: element.type, props: element.props, dom: null, parent: wipFiber, alternate: null, effectTag: "PLACEMENT", } } if (oldFiber && !sameType) { oldFiber.effectTag = "DELETION" deletions.push(oldFiber) } if (oldFiber) { oldFiber = oldFiber.sibling } if (index === 0) { wipFiber.child = newFiber } else if (element) { prevSibling.sibling = newFiber } prevSibling = newFiber index++ }}function performUnitOfWork(fiber) { // 1.函数组件对应的fiber节点没有实在dom元素 // 2.函数组件须要运行函数获取children const isFunctionComponent = fiber.type instanceof Function if(!isFunctionComponent && !fiber.dom){ fiber.dom = createDom(fiber) } const children = isFunctionComponent ? updateFunctionComponent(fiber) : fiber.props.children // 第二步 为每一个新的react element节点创立对应的fiber节点,并判断旧的fiber节点上的实在dom元素是否能够复用。 // 节俭创立实在dom元素的开销 reconcileChildren(fiber, children) // 第三步,查找下一个工作单元 if(fiber.child){ return fiber.child } let nextFiber = fiber while(nextFiber){ if(nextFiber.sibling){ return nextFiber.sibling } nextFiber = nextFiber.parent }}let wipFiber = nulllet hookIndex = nullfunction updateFunctionComponent(fiber){ wipFiber = fiber hookIndex = 0 wipFiber.hooks = [] return [fiber.type(fiber.props)]}function useState(initial){ const oldHook = wipFiber.alternate && wipFiber.alternate.hooks && wipFiber.alternate.hooks[hookIndex] const hook = { state: oldHook ? oldHook.state : initial, queue: [], } const actions = oldHook ? oldHook.queue : [] actions.forEach(action => { hook.state = action(hook.state) }) const setState = action => { hook.queue.push(action) wipRoot = { dom: currentRoot.dom, props: currentRoot.props, alternate: currentRoot, } nextUnitOfWork = wipRoot deletions = [] } wipFiber.hooks.push(hook) hookIndex++ return [hook.state, setState]}const MiniReact = { createElement: (type, props, ...children) => { return { type, props: { ...props, children: children.map(child => { if(typeof child === 'object'){ return child } return { type: 'TEXT_ELEMENT', props: { nodeValue: child, children: [], } } }) } } }, render, useState,}/** @jsx MiniReact.createElement */const container = document.getElementById("root")function Counter(){ const [state, setState] = MiniReact.useState(1) return ( <h1 onClick={() => setState(c => c + 1)}> Count: { state } </h1> )}const element = <Counter />MiniReact.render(element, container)