前言
Facebook 的研发能力真是惊人, Fiber
架构给 React 带来了新视野的同时,将调度一词介绍给了前端,然而这个架构实在不好懂,比起以前的 Vdom
树,新的 Fiber
树就麻烦太多。
可以说,React 16 和 React 15 已经是技巧上的分水岭,但是得益于 React 16 的 Fiber
架构,使得 React 即使在没有开启异步的情况下,性能依旧是得到了提高。
经过两个星期的痛苦研究,终于将 React 16 的渲染脉络摸得比较清晰,可以写文章来记录、回顾一下。
如果你已经稍微理解了 Fiber
架构,可以直接看代码:仓库地址
什么是 React Fiber ?
React Fiber
并不是所谓的纤程(微线程、协程),而是一种基于浏览器的单线程调度算法,背后的支持 API 是大名鼎鼎的: requestIdleCallback
,得到了这个 API 的支持,我们便可以将 React 中最耗时的部分放入其中。
回顾 React 历年来的算法都知道,reconcilation
算法实际上是一个大递归,大递归一旦进行,想要中断还是比较不好操作的,加上头大尾大的 React 15 代码已经膨胀到了不可思议的地步,在重重压力之下,React 使用了大循环来代替之前的大递归,虽然代码变得比递归难懂了几个梯度,但是实际上,代码量比原来少了非常多(开发版本 3W 行压缩到了 1.3W 行)
那问题就来了,什么是 Fiber
:一种将 recocilation
(递归 diff
),拆分成无数个小任务的算法;它随时能够停止,恢复。停止恢复的时机取决于当前的一帧( 16ms
)内,还有没有足够的时间允许计算。
React 异步渲染流程图
- 用户调用
ReactDOM.render
方法,传入例如
组件,React 开始运作 -
在内部会被转换成RootFiber
节点,一个特殊的节点,并记录在一个全局变量中,TopTree
- 拿到
的RootFiber
,首先创建一个
对应的 Fiber ,然后加上 Fiber 信息,以便之后回溯。随后,赋值给之前的全局变量 TopTree - 使用
requestIdleCallback
重复第三个步骤,直到循环到树的所有节点 - 最后完成了
diff
阶段,一次性将变化更新到真实DOM
中,以防止 UI 展示的不连续性
其中,重点就是 3
和 4
阶段,这两个阶段将创建真实 DOM 和组件渲染 ( render
)拆分为无数的小碎块,使用 requestIdleCallback
连续进行。在 React 15 的时候,渲染、创建、插入、删除等操作是最费时的,在 React 16 中将渲染、创建抽离出来分片,这样性能就得到了极大的提升。
那为什么更新到真实 DOM 中不能拆分呢?理论上来说,是可以拆分的,但是这会造成 UI 的不连续性,极大的影响体验。
递归变成了循环
以简单的组件为例子:
- 从顶端的
div#root
向下走,先走左子树 -
div
有两个孩子span
,继续走左边的 - 来到
span
,之下只有一个hello
,到此,不再继续往下,而是往上回到span
- 因为
span
有一个兄弟,因此往兄弟span
走去 - 兄弟
span
有孩子luy
,到此,不继续往下,而是回到luy
的老爹span
-
luy
的老爹span
右边没有兄弟了,因此回到其老爹div
-
div
没有任何的兄弟,因此回到顶端的div#root
每经过一个 Fiber
节点,执行 render
或者 document.createElement
(或者更新 DOM
)的操作
Fiber 数据结构
一个 Fiber
数据结构比较复杂
const Fiber = {
tag: HOST_COMPONENT,
type: 'div',
return: parentFiber,
child: childFiber,
sibling: null,
alternate: currentFiber,
stateNode: document.createElement('div') | instance,
props: { children: [], className: 'foo' },
partialState: null,
effectTag: PLACEMENT,
effects: []
}
这是一个比较完整的 Fiber object
,他复杂的原因是因为一个 Fiber
就代表了一个「正在执行或者执行完毕」的操作单元。这个概念不是那么好理解,如果要说得简单一点就是:以前的 VDOM
树节点的升级版。让我们介绍几个关键属性:
- 由「 递归改循环 」我们可以得知,当我们循环的遍历树到达底部时,需要回到其父节点,那么对应的就是
Fiber
中的return
属性(以前叫parent
)。child
和sibling
类似,代表这个Fiber
的子Fiber
和兄弟Fiber
-
stateNode
这个属性比较特殊,用于记录当前Fiber
所对应的真实DOM
节点 或者 当前虚拟组件的实例,这么做的原因第一是为了实现Ref
,第二是为了实现DOM
的跟踪 -
tag
属性在新版的React
中一共有 14 种值,分别代表了不同的JSX
类型。 -
effectTag
和effects
这两个属性为的是记录每个节点Diff
后需要变更的状态,比如删除,移动,插入,替换,更新等...
alternate
属性我想拿出来单独说一下,这个属性是 Fiber
架构新加入的属性。我们都知道,VDOM
算法是在更新的时候生成一颗新的 VDOM
树,去和旧的进行对比。在 Fiber
架构中,当我们调用 ReactDOM.render
或者 setState
之后,会生成一颗树叫做:work-in-progress tree
,这一颗树就是我们所谓的新树用来与我们的旧树进行对比,新的树和旧的树的 Fiber
是完全不一样的,此时,我们就需要 alternate
属性去链接新树和旧树。
司徒正美的研究中,一个 Fiber
和它的 alternate
属性构成了一个联婴体,他们有共同的 tag
,type
,stateNode
属性,这些属性在错误边界自爆时,用于恢复当前节点。
开始写代码:Component 构造函数
讲了那么多的理论,大家一定是晕了,但是没办法,Fiber
架构已经比之前的简单 React 要复杂太多了,因此不可能指望一次性把 Fiber
的内容全部理解,需要反复多看。
当然,结合代码来梳理,思路旧更加清晰了。我们在构建新的架构时,老的 Luy 代码大部分都要进行重构了,先来看看几个主要重构的地方:
export class Component {
constructor(props, context) {
this.props = props
this.context = context
this.state = this.state || {}
this.refs = {}
this.updater = {}
}
setState(updater) {
scheduleWork(this, updater)
}
render() {
throw 'should implement `render()` function'
}
}
Component.prototype.isReactComponent = true
- 这就是
React.Component
的代码 - 构造函数中,我们都进两个参数,一个是外部的
props
,一个是context
- 内部有
state
,refs
,updater
,updater
用于收集setState
的信息,便于之后更新用。当然,在这个版本之中,我并没有使用。 -
setState
函数也并没有做队列处理,只是调用了scheduleWork
这个函数 -
Component.prototype.isReactComponent = true
,这段代码表饰着,如果一个组件的类型为function
且拥有isReactComponent
,那么他就是一个有状态组件,在创建实例时需要用new
,而无状态组件只需要fn(props,context)
调用
const tag = {
HostComponent: 'host',
ClassComponent: 'class',
HostRoot: 'root',
HostText: 6,
FunctionalComponent: 1
}
const updateQueue = []
export function render(Vnode, Container, callback) {
updateQueue.push({
fromTag: tag.HostRoot,
stateNode: Container,
props: { children: Vnode }
})
requestIdleCallback(performWork) //开始干活
}
export function scheduleWork(instance, partialState) {
updateQueue.push({
fromTag: tag.ClassComponent,
stateNode: instance,
partialState: partialState
})
requestIdleCallback(performWork) //开始干活
}
我们定义了一个全局变量 updateQueue
来记录我们所有的更新操作,每当 render
和 scheduleWork (setState)
触发时,我们都会往 updateQueue
中 push
一个状态,然后,进而调用大名鼎鼎的 requestIdleCallback
进行更新。在这里与之前的 react 15 最大不同是,更新阶段和首次渲染阶段得到了统一,都是使用了 updateQueue
进行更新。
实际上这里还有优化的空间,就是多次 setState
的时候,应该合并成一次再进行 requestIdleCallback
的调用,不过这并不是我们的目标,我们的目标是搞懂 Fiber
架构。requestIdleCallback
调用的是 performWork
函数,我们接下来看看
performWork 函数
const EXPIRATION_TIME = 1 // ms async 逾期时间
let nextUnitOfWork = null
let pendingCommit = null
function performWork(deadline) {
workLoop(deadline)
if (nextUnitOfWork || updateQueue.length > 0) {
requestIdleCallback(performWork) //继续干
}
}
function workLoop(deadline) {
if (!nextUnitOfWork) {
//一个周期内只创建一次
nextUnitOfWork = createWorkInProgress(updateQueue)
}
while (nextUnitOfWork && deadline.timeRemaining() > EXPIRATION_TIME) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork)
}
if (pendingCommit) {
//当全局 pendingCommit 变量被负值
commitAllwork(pendingCommit)
}
}
熟悉 requestIdleCallback
的同学一定对这两个函数并不陌生,这两个函数其实做的就是所谓的异步调度。
performWork
函数主要做了两件事,第一件事就是拿到 deadline
进入我们之前所谓的大循环,也就是正式进入处理新旧 Fiber
的 Diff
阶段,这个阶段比较的奇妙,我们叫他 workLoop
阶段。workLoop
会一次处理 1 个或者多个 Fiber
,具体处理多少个,要看每一帧具体还剩下多少时间,如果一个 Fiber
消耗太多时间,那么就会等到下一帧再处理下一个 Fiber
,如此循环,遍历整个 VDOM
树。
在这里我们注意到,如果一个
Fiber
消耗太多时间,可能会导致一帧时间的逾期,不过其实没什么问题啦,也仅仅是一帧逾期而已,对于我们视觉上并没有多大的影响。
workLoop
函数主要是三部曲:
-
createWorkInProgress
这个函数会构建一颗树的顶端,赋值给全局变量nextUnitOfWork
,通过迭代的方式,不断更新nextUnitOfWork
直到遍历完所有树的节点。 -
performUnitOfWork
函数是第二步,不断的检测当前帧是否还剩余时间,进行WorkInProgress
tree 的迭代 - 当
WorkInProgress
tree 迭代完毕以后,调用commitAllWork
,将所有的变更全部一次性的更新到DOM
中,以保证 UI 的连续性
所有的 Diff
和创建真实 DOM
的操作,都在 performUnitOfWork
之中,但是插入和删除是在 commitAllWork
之中。接下来,我们逐一分析三部曲的内部操作。
第一步:createWorkInProgress
export function createWorkInProgress(updateQueue) {
const updateTask = updateQueue.shift()
if (!updateTask) return
if (updateTask.partialState) {
// 证明这是一个setState操作
updateTask.stateNode._internalfiber.partialState = updateTask.partialState
}
const rootFiber =
updateTask.fromTag === tag.HostRoot
? updateTask.stateNode._rootContainerFiber
: getRoot(updateTask.stateNode._internalfiber)
return {
tag: tag.HostRoot,
stateNode: updateTask.stateNode,
props: updateTask.props || rootFiber.props,
alternate: rootFiber // 用于链接新旧的 VDOM
}
}
function getRoot(fiber) {
let _fiber = fiber
while (_fiber.return) {
_fiber = _fiber.return
}
return _fiber
这个函数的主要作用就是构建 workInProgress
树的顶端并赋值给全局变量 nextUnitOfWork。
首先,我们先从 updateQueue
中获取一个任务对象 updateTask
。随后,进行判断是否是更新阶段。然后获取 workInProgress
树的顶端。如果是第一次渲染, RootFiber
的值是空的,因为我们并没有构建任何的树。
最后,我们将返回一个 Fiber
对象,这个 Fiber
对象的标识符( tag
)是 HostRoot
。
第二步:performUnitOfWork
// 开始遍历
function performUnitOfWork(workInProgress) {
const nextChild = beginWork(workInProgress)
if (nextChild) return nextChild
// 没有 nextChild, 我们看看这个节点有没有 sibling
let current = workInProgress
while (current) {
//收集当前节点的effect,然后向上传递
completeWork(current)
if (current.sibling) return current.sibling
//没有 sibling,回到这个节点的父亲,看看有没有sibling
current = current.return
}
}
我们调用 performUnitOfWork
处理我们的 workInProgress
。
整个函数做的事情其实就是一个左遍历树的过程。首先,我们调用 beginWork
,获得一个当前 Fiber
下的第一个孩子,如果有直接返回出去给 nextUnitOfWork
,当作下一个处理的节点;如果没有找到任何孩子,证明我们已经到达了树的底部,通过下面的 while
循环,回到当前节点的父节点,将当前 Fiber
下拥有 Effect
的孩子全部记录下来,以便于之后更新 DOM
。
然后查找当前节点的父亲节点,是否有兄弟,有就返回,当成下一个处理的节点,如果没有,就继续回溯。
整个过程用图来表示,就是:
在讨论第三部之前,我们仍然有两个迷惑的地方:
-
beginWork
是如何创建孩子的 -
completeWork
是如何收集effect
的接下来,我们就来一起看看
beginWork
function beginWork(currentFiber) {
switch (currentFiber.tag) {
case tag.ClassComponent: {
return updateClassComponent(currentFiber)
}
case tag.FunctionalComponent: {
return updateFunctionalComponent(currentFiber)
}
default: {
return updateHostComponent(currentFiber)
}
}
}
function updateHostComponent(currentFiber) {
// 当一个 fiber 对应的 stateNode 是原生节点,那么他的 children 就放在 props 里
if (!currentFiber.stateNode) {
if (currentFiber.type === null) {
//代表这是文字节点
currentFiber.stateNode = document.createTextNode(currentFiber.props)
} else {
//代表这是真实原生 DOM 节点
currentFiber.stateNode = document.createElement(currentFiber.type)
}
}
const newChildren = currentFiber.props.children
return reconcileChildrenArray(currentFiber, newChildren)
}
function updateFunctionalComponent(currentFiber) {
let type = currentFiber.type
let props = currentFiber.props
const newChildren = currentFiber.type(props)
return reconcileChildrenArray(currentFiber, newChildren)
}
function updateClassComponent(currentFiber) {
let instance = currentFiber.stateNode
if (!instance) {
// 如果是 mount 阶段,构建一个 instance
instance = currentFiber.stateNode = createInstance(currentFiber)
}
// 将新的state,props刷给当前的instance
instance.props = currentFiber.props
instance.state = { ...instance.state, ...currentFiber.partialState }
// 清空 partialState
currentFiber.partialState = null
const newChildren = currentFiber.stateNode.render()
// currentFiber 代表老的,newChildren代表新的
// 这个函数会返回孩子队列的第一个
return reconcileChildrenArray(currentFiber, newChildren)
}
beginWork
其实是一个判断分支的函数,整个函数的意思是:
- 判断当前的
Fiber
是什么类型,是class
的走class
分支,是stateless
的走stateless,是原生节点的走原生分支
- 如果没有
stateNode
,则创建一个stateNode
- 如果是
class
,则创建实例,调用render
函数,渲染其儿子;如果是原生节点,调用DOM API
创建原生节点;如果是stateless
,就执行它,渲染出VDOM
节点 - 最后,走到最重要的函数,
recocileChildrenArray
函数,将其每一个孩子进行链表的链接,进行diff
,然后返回当前Fiber
之下的第一个孩子
我们来看看比较重要的 classComponent
的构建流程
function updateClassComponent(currentFiber) {
let instance = currentFiber.stateNode
if (!instance) {
// 如果是 mount 阶段,构建一个 instance
instance = currentFiber.stateNode = createInstance(currentFiber)
}
// 将新的state,props刷给当前的instance
instance.props = currentFiber.props
instance.state = { ...instance.state, ...currentFiber.partialState }
// 清空 partialState
currentFiber.partialState = null
const newChildren = currentFiber.stateNode.render()
// currentFiber 代表老的,newChildren代表新的
// 这个函数会返回孩子队列的第一个
return reconcileChildrenArray(currentFiber, newChildren)
}
function createInstance(fiber) {
const instance = new fiber.type(fiber.props)
instance._internalfiber = fiber
return instance
}
如果是首次渲染,那么组件并没有被实例话,此时我们调用 createInstance
实例化组件,然后将当前的 props
和 state
赋值给 props
、state
,随后我们调用 render
函数,获得了新儿子 newChildren
。
渲染出新儿子之后,来到了新架构下最重要的核心函数 reconcileChildrenArray
.
reconcileChildrenArray
const PLACEMENT = 1
const DELETION = 2
const UPDATE = 3
function placeChild(currentFiber, newChild) {
const type = newChild.type
if (typeof newChild === 'string' || typeof newChild === 'number') {
// 如果这个节点没有 type ,这个节点就可能是 number 或者 string
return createFiber(tag.HostText, null, newChild, currentFiber, PLACEMENT)
}
if (typeof type === 'string') {
// 原生节点
return createFiber(tag.HOST_COMPONENT, newChild.type, newChild.props, currentFiber, PLACEMENT)
}
if (typeof type === 'function') {
const _tag = type.prototype.isReactComponent ? tag.CLASS_COMPONENT : tag.FunctionalComponent
return {
type: newChild.type,
tag: _tag,
props: newChild.props,
return: currentFiber,
effectTag: PLACEMENT
}
}
}
function reconcileChildrenArray(currentFiber, newChildren) {
// 对比节点,相同的标记更新
// 不同的标记 替换
// 多余的标记删除,并且记录下来
const arrayfiyChildren = arrayfiy(newChildren)
let index = 0
let oldFiber = currentFiber.alternate ? currentFiber.alternate.child : null
let newFiber = null
while (index < arrayfiyChildren.length || oldFiber !== null) {
const prevFiber = newFiber
const newChild = arrayfiyChildren[index]
const isSameFiber = oldFiber && newChild && newChild.type === oldFiber.type
if (isSameFiber) {
newFiber = {
type: oldFiber.type,
tag: oldFiber.tag,
stateNode: oldFiber.stateNode,
props: newChild.props,
return: currentFiber,
alternate: oldFiber,
partialState: oldFiber.partialState,
effectTag: UPDATE
}
}
if (!isSameFiber && newChild) {
newFiber = placeChild(currentFiber, newChild)
}
if (!isSameFiber && oldFiber) {
// 这个情况的意思是新的节点比旧的节点少
// 这时候,我们要将变更的 effect 放在本节点的 list 里
oldFiber.effectTag = DELETION
currentFiber.effects = currentFiber.effects || []
currentFiber.effects.push(oldFiber)
}
if (oldFiber) {
oldFiber = oldFiber.sibling || null
}
if (index === 0) {
currentFiber.child = newFiber
} else if (prevFiber && newChild) {
// 这里不懂是干嘛的
prevFiber.sibling = newFiber
}
index++
}
return currentFiber.child
}
这个函数做了几件事
- 将孩子
array
化,这么做能够使得react
的render
函数返回数组 -
currentFiber
是新的workInProgress
上的一个节点,是属于新的VDOM
树 ,而此时,我们必须要找到旧的VDOM
树来进行比对。那么在这里,Alternate
属性就起到了关键性作用,这个属性链接了旧的VDOM
,使得我们能够获取原来的VDOM
- 接下来我们进行对比,如果新的节点的
type
与原来的相同,那么我们将新建一个Fiber
,标记这个Fiber
为UPDATE
- 如果新的节点的
type
与原来的不相同,那我们使用PALCEMENT
来标记他 - 如果旧的节点数量比新的节点少,那就证明,我们要删除旧的节点,我们把旧节点标记为
DELETION
,并构建一个effect list
记录下来 - 当前遍历的是组件的第一个孩子,那么我们将他记录在
currentFiber
的child
字段中 - 当遍历的不是第一个孩子,我们将 新建的
newFiber
用链表的形式将他们一起推入到currentFiber
中 - 返回当前
currentFiber
下的第一个孩子
看着比较啰嗦,但是实际上做的就是构建链表和 diff
孩子的过程,这个函数有很多优化的空间,使用 key
以后,在这里能提高很多的性能,为了简单,我并没有对 key
进行操作,之后的 Luy
版本一定会的。
completeWork: 收集 effectTag
// 开始遍历
function performUnitOfWork(workInProgress) {
const nextChild = beginWork(workInProgress)
if (nextChild) return nextChild
// 没有 nextChild, 我们看看这个节点有没有 sibling
let current = workInProgress
while (current) {
//收集当前节点的effect,然后向上传递
completeWork(current)
if (current.sibling) return current.sibling
//没有 sibling,回到这个节点的父亲,看看有没有sibling
current = current.return
}
}
//收集有 effecttag 的 fiber
function completeWork(currentFiber) {
if (currentFiber.tag === tag.classComponent) {
// 用于回溯最高点的 root
currentFiber.stateNode._internalfiber = currentFiber
}
if (currentFiber.return) {
const currentEffect = currentFiber.effects || [] //收集当前节点的 effect list
const currentEffectTag = currentFiber.effectTag ? [currentFiber] : []
const parentEffects = currentFiber.return.effects || []
currentFiber.return.effects = parentEffects.concat(currentEffect, currentEffectTag)
} else {
// 到达最顶端了
pendingCommit = currentFiber
}
}
这个函数做了两件事,第一件事情就是收集当前 currentFiber
的 effectTag
,将其 append
到父 Fiber
的 effectlist
中去,通过循环一层一层往上,最终到达顶端 currentFiber.return === void 666
的时候,证明我们到达了 root
,此时我们已经把所有的 effect
收集到了顶端的 currentFiber.effect
上,并把它赋值给 pendingCommit
,进入 commitAllWork
阶段。
第三步:commitAllWork
终于,我们已经通过不断不断的调用 requestIdleCallback
和 大循环,将我们的所有变更都找出来放在了 workInProgress tree
里,我们接下来就要做最后一步:将所有的变更一次性的变更到真实 DOM
中,注意,这个阶段里我们不再运行创建 DOM
和 render
,因此,虽然我们一次性变更所有的 DOM
,但是性能来说并不是太差。
function commitAllwork(topFiber) {
topFiber.effects.forEach(f => {
commitWork(f)
})
topFiber.stateNode._rootContainerFiber = topFiber
topFiber.effects = []
nextUnitOfWork = null
pendingCommit = null
}
我们直接拿到 TopFiber
中的 effects list
,遍历,将变更全部打到 DOM
中去,然后我们将全局变量清理干净。
function commitWork(effectFiber) {
if (effectFiber.tag === tag.HostRoot) {
// 代表 root 节点没什么必要操作
return
}
// 拿到parent的原因是,我们要将元素插入的点,插在父亲的下面
let domParentFiber = effectFiber.return
while (domParentFiber.tag === tag.classComponent || domParentFiber.tag === tag.FunctionalComponent) {
// 如果是 class 就直接跳过,因为 class 类型的fiber.stateNode 是其本身实例
domParentFiber = domParentFiber.return
}
//拿到父亲的真实 DOM
const domParent = domParentFiber.stateNode
if (effectFiber.effectTag === PLACEMENT) {
if (effectFiber.tag === tag.HostComponent || effectFiber.tag === tag.HostText) {
//通过 tag 检查是不是真实的节点
domParent.appendChild(effectFiber.stateNode)
}
// 其他情况
} else if (effectFiber.effectTag == UPDATE) {
// 更新逻辑 只能是没实现
} else if (effectFiber.effectTag == DELETION) {
//删除多余的旧节点
commitDeletion(effectFiber, domParent)
}
}
function commitDeletion(fiber, domParent) {
let node = fiber
while (true) {
if (node.tag == tag.classComponent) {
node = node.child
continue
}
domParent.removeChild(node.stateNode)
while (node != fiber && !node.sibling) {
node = node.return
}
if (node == fiber) {
return
}
node = node.sibling
}
}
这一部分代码是最好理解的了,就是做的是删除和插入或者更新 DOM
的操作,值得注意的是,删除操作依旧使用的链表操作。
最后来一段测试代码:
import React from './Luy/index'
import { Component } from './component'
import { render } from './vdom'
class App extends Component {
state = {
info: true
}
constructor(props) {
super(props)
setTimeout(() => {
this.setState({
info: !this.state.info
})
}, 1000)
}
render() {
return (
hello
luy
{this.state.info ? 'imasync' : 'iminfo'}
)
}
}
render( , document.getElementById('root'))
我们来看看动图吧!当节点 mount
以后,过了 1 秒,就会更新,我们简单的更新就到此结束了
再看以下调用栈,我们的 requestIdleCallback
函数已经正确的运行了。
如果你想下载代码亲自体验,可以到 Luy 仓库中:
git clone https://github.com/Foveluy/Luy.git
cd Luy
npm i --save-dev
npm run start
目前我能找到的所有资料都放在仓库中:资料
回顾本文几个重要的点
一开始我们就使用了一个数组来记录 update
的信息,通过调用 requestIdleCallback
来将更新一个一个的取出来,大部分时间队列里只有一个。
取出来以后,使用从左向右遍历的方式,用链表链接一个一个的 Fiber
,并做 diff
和创建,最后一次性的 patch
到真实 DOM
中去。
现在 react 的架构已经变得极其复杂,而本文也只是将 React 的整体架构通篇流程描述了一遍,里面的细节依旧值得我们的深究,比如,如何传递 context
,如何实现 ref
,如何实现错误边界处理,声明周期的处理,这些都是很大的话题,在接下去的文章里,我会一步一步的将这些关系讲清楚。
最后,感谢支持我的迷你框架项目:Luy ,现在正在向 Fiber
晋级!如果你喜欢,请给我一点 star 表示鼓励!谢谢
如果有什么问题,可以加入我们的学习 QQ 群: 370262116
,群里几乎所有的迷你 React
作者都在了,包括 anu
作者司徒正美, omi
作者,我等,一起来学习吧!