Build your own React_3 并发模式
前端工程师的要求越来越高,仅懂得“三大马车”和调用框架API,已经远不能满足岗位的能力要求。因此增强自身的底层能力,了解框架的内部原理非常重要。本系列文章,翻译自Rodrigo Pombo的《Build your own React》一文,同时每篇文章最后,都会加入自己的理解,一方面记录自己初探React框架原理的过程,另一方面也是想与各位大牛多多交流,以出真知。
我们打算从零开始重写一个React框架,在遵循源码架构的基础上,省略了一些优化和非核心功能代码。
假设你阅读过我之前的文章《build your own React》,那篇文章是基于React 16.8版本,当时还不能使用hooks来替代class。
你可以在Didact仓库找到那篇文章和对应的代码。这里还有个相同主题的视频,跟文章的内容有些区别的,但是可以参考观看。
从零开始重写React框架,我们需要遵循以下步骤:
- 步骤一:createElement函数
- 步骤二:render函数
- 步骤三:并发模式
- 步骤四:理解React纤维
- 步骤五:渲染和提交阶段
- 步骤六:调解器
- 步骤七:函数组件
- 步骤八:Hooks
步骤三:并发模式
在新增功能代码之前,我们需要对之前的代码重构,因为原来的代码有个问题。
function render(element, container){// const dom = element.type = "TEXT_ELEMENT"// ? document.createTextNode("")// : document.createElement(element.type)// const isProperty = key => key !== "children"// Object.keys(elemnet.props)// .filter(isProperty)// .forEach(name => {// dom[name] = element.props[name]// })element.props.children.forEach(child=>render(child, dom))// container.appendChild(dom)
}
问题就是:一旦开始渲染就停不下来,除非整个元素树渲染完成。如果元素树太大,渲染过程会阻塞主进程太多时间。而如果此时浏览器有更高优先级的任务,例如响应用户输入或是保证开场动画的流畅度,均需要等待所有元素树渲染完成。
因此我们的思路是:将渲染任务分成小的单元,每完成一个单元,如果此时有更高优先级的任务,浏览器就会暂停渲染过程。
let nextUnitOfWork = nullfunction workLoop(deadline){let sholdYield = falsewhile(nextUnitOfWork && !shouldYield){nextUnitOfWork = performUnitOfWork(nextUnitOfWork)shouldYield = deadline.timeRemaining() < 1}requestIdleCallback(workLoop)
}requestIdleCallback(workLoop)function performUnitOfWork(nextUnitOfWork){// TODO
}
我们使用requestIdleCallback来执行循环。可以把requestIdleCallback类似于setTimeout函数,但区别在于我们无需告诉它什么时候运行,浏览器在主线程闲置的时候会自动执行传入的回调参数。
值得一提的是:React已经不再使用requestIdleCallback,它使用的是scheduler包,针对这个功能,两者本质上是一样的。
// let UnitOfWork = nullfunction workLoop(deadline){// let shouldYield = false// while(nextUnitOfWork && !shouldYield){// nextUnitOfWork = preformUnitOfWork(nextUnitOfWork)// shouldYield = deadline.timeRemaining() < 1// }requestIdleCallback(workLoop)
}requestIdleCallback(workLoop)
requestIdleCallback同时会提供一个表示dealine的参数,我们可以利用它判断浏览器多久需要收回控制权。
function workLoop(deadline){let shouldYield = falsewhile(nextUnitOfWork && !shouldYield){// nextUnitOfWork = performUnitOfWork(nextUnitOfWork)shouldYield = deadline.timeRemaining() < 1}// requestIdleCallback(workLoop)
}
截止到2019年11月份,并发模式在React中不再属于稳定版,渲染循环任务的稳定版代码类似于下:
while (nextUnitOfWork){nextUnitOfWork = performUnitOfWork(nextUnitOfWork)
}
为了开始整个渲染工作,我们需要手动设置第一个单元渲染任务,然后通过编写performUnitOfWork函数来执行后续的单元渲染任务,函数的作用不仅是执行当前单元渲染任务,还能返回下个单元渲染任务。
let nextUnitOfWork = nullfunction workLoop(deadline){// let shouldYield = false// while(nextUnitOfWork && !shouldYield){nextUnitOfWork = performUnitOfWork(nextUnitOfWork)// shouldYield = deadline.timeRemaining() < 1//}//reuestIdleCallback(workLoop)
}// requestIdleCallback(workLoop)function performUnitOfWork(nextUnitOfWork){// TODO
}
总结
步骤二中,作者详细介绍了每个React元素是如何被转化为实际的DOM节点。
步骤三则指出了步骤二中递归渲染节点的问题:无法中止渲染,造成的后果是用户体验很差。同时提出了解决方案,将整棵DOM树的渲染拆分成小的单元任务,每完成单元渲染,如果此时有更高优先级任务,浏览器会暂停渲染。至此,我们开始深入了解React的渲染细节,大任务拆小体现了”分而治之“的思想。
上一篇传送门:Build your own React_2 render函数
下一篇传送门:Build your own React_4 理解React纤维
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
