likes
comments
collection
share

自己造轮子系列——React框架并发模型探讨

作者站长头像
站长
· 阅读数 18

近来工作忙了一阵,加上自己精力不足,终于造轮子系列搁置了许久。废话不多说,接着上次的轮子,继续造。

写在前面

React框架具体的实现细节十分庞杂,这里就挑几个最具代表性的特性——JSXFunction ComponentHooks,当然每一个特性的实现细节也十分繁琐,我们的目标是实现核心功能理解原理就好。另外对于React最新的Fibers树结构并发模型,也简单探讨下。

总结下来,具体要实现的轮子:

  1. React.createElement 函数——核心功能,解析JSX;
  2. ReactDOM.render 函数——核心功能;
  3. Concurrent Mode并发模型——探讨;
  4. Fibers Tree——探讨;
  5. Function Component;
  6. Hooks;
  7. 对比源码

正文

前一篇中,我们一起实现了React.createElementReactDOM.render两个函数,今天我们来探讨一下第三个主题:Concurrent Mode并发模型。

为什么这一节我们要讲并发模型呢?其实是因为上一章中,我们在处理DOM树时使用了递归,而递归会导致一个问题:就是一旦我们开始了就不能停止,这将会浪费不少性能和资源,当DOM树很大时,可能会阻塞主线程,从而导致页面卡顿等不好的用户体验。

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]
        })
    // 递归整个DOM树
    element.props.children.forEach(child =>render(child, dom))
    container.appendChild(dom)
}

那怎么解决这个问题呢?有一个不错的办法是:将整个任务分割成若干个小任务,每个小任务完结时,浏览器可以根据需要拦截下个小任务,从而可以执行优先级更高的比如用户输入之类的任务,在浏览器执行完优先级更高的任务之后,可以接着执行下个小任务,这样就很大程度上减少了卡顿发生的概率。

那么具体的代码要怎么实现呢?

let nextUnitOfWork = null

function workLoop(deadline) {
    let shouldYield = false
    while (nextUnitOfWork && !shouldYield) {
        nextUnitOfWork = performUnitOfWork(nextUnitOfWork)
        shouldYield = deadline.timeRemaining() < 1
    }
    requestIdleCallback(workLoop)
}

requestIdleCallback(workLoop)

function performUnitOfWork(nextUnitOfWork) {
    // TODO
}

这里我们使用requestIdleCallback作为队列,可以把它想象成一个setTimeOut,但是不用设置执行时间,requestIdleCallback会在主线程空闲时执行。

需要说明的是,React已经不再使用requestIdleCallback,现在使用的是调度程序包,但是对于我们现在这个轮子来讲,作用一样,区别并不大。

前面的例子中,requestIdleCallback也能传递一个deadline参数,通过这个时间,我们可以知道浏览器再次控制之前我们还有多少时间。

写在后面

上面的requestIdleCallback,精简一下就是这样:

while (nextUnitOfWork) {    
  nextUnitOfWork = performUnitOfWork(   
    nextUnitOfWork  
  ) 
}

如果要开始使用队列,我们需要设置第一个任务单元,然后编写一个performUnitOfWork函数,它不仅执行当前任务,还返回下一个任务。

那么具体要怎么把整个递归任务分割成小任务呢?

嗯,这是个问题。

React现在是怎么做的呢?知道的同学可以在评论区留言,不知道的同学可以看我下一篇讲解。