【React源码】1.设计理念

256 阅读4分钟

v16.0之前

  • 在Fiber出现之前(16.0),React使用的是virtual dom tree 的形式
  • 遍历这个tree的时候,是利用递归、回调的方式来遍历tree。是一个同步的过程(也叫stack reconciler
  • 因为js是单线程的,tree的遍历的过程中,无法中断。导致更高优先级的操作得不到相应(如input框输入)
  • 所以页面出现卡顿

解决方案

我们在日常的开发中,如果遇到一个耗时的单线程,我们可能会选择把任务拆分

所以,针对以上的痛点,应该解决三个问题:任务分割可中断(让出执行权)、异步

实现

伪代码

// firstFiber -> firstChild -> sibling

let firstFiber // 顶格节点
let nextFiber = firstFiber // 下一个处理的节点 ----------------------> 任务分割
let shouldYield = false; // 是否继续,控制中断 ----------------------> 可中断

// 处理单个Fiber任务
function performUnitOfWork(fiber) {
    // 处理一些工作
    return fiber.next;
}

// 任务处理循环
function workLoop(deadline) {
    while(nextFiber && !shouldYield) {
        nextFiber = performUnitOfWork(nextFiber)
        shouldYield = deadline.remaining() < 1;
    }
    requestIdleCallback(wookLoop)
}

requestIdleCallback(wookLoop) // -----------------------------------> 异步

  • Fiber:实现了任务分割
  • Scheduler:有了Fiber,我们就需要用浏览器的时间片异步执行这些Fiber的工作单元,我们知道浏览器有一个api叫做requestIdleCallback,它可以在浏览器空闲的时候执行一些任务,我们用这个api执行react的更新,让高优先级的任务优先响应不就可以了吗,但事实是requestIdleCallback存在着浏览器的兼容性和触发不稳定的问题,所以我们需要用js实现一套时间片运行的机制,在react中这部分叫做Scheduler
  • Lane:有了异步调度,我们还需要细粒度的管理各个任务的优先级,让高优先级的任务优先执行,各个Fiber工作单元还能比较优先级,相同优先级的任务可以一起更新,想想是不是更cool呢

代数效应

  • 2022.3.16:一种概念,暂时理解是一种思想,主要是观察副作用的内容

上面讨论的是和CPU计算相关的一些问题,还有一类问题是和副作用相关的问题(数据获取、文件操作),React需要处理这些副作用,需要React有分离副作用的能力。为什么要分离副作用?因为要解耦,这就是代数效应

分离副作用

如果获取数据前展示loading,数据获取后,取消loading。加入设备网络都很好,可以很快获取到数据。就没有必要一开始的时候展示loading。如何才能有更好的体验呢?

async await
// 异步获取数据的方法
function getPrice(id) {
    return fetch().then(res => {
        return res.price
    })
}

async function getTotalPrice(id1, id2) {
    const p1 = await getPrice(id1)
    const p2 = await getPrice(id2)
    return p1 + p2;
}

async functin run() {
    await getTotalPrice("1","2")
}

上面的getPrice是一个获取数据的函数,可以使用 async await的方式获取数据,但是这会导致调用getTotalPrice的run方法也会变成异步函数,这就是async的传染性。所以无法分离副作用

虚拟代码
function getPrice(id) {
    const price = perform id;
    return price
}

function getTotalPrice(id1, id2) {
    const p1 = getPrice(id1)
    const p2 = getPrice(id2)
    return p1 + p2;
}

try {
    getTotalPrice("1", "2")
} handle(productid) {
    fetch().then(res => {
        resume with res.price
    })
}

上面这个伪代码

  • perform:暂停函数的执行
  • handle:获取函数执行权
  • resume:交出执行权,并回到上次perform暂停的地方往下执行 有点抽象,下面的hooks
// usePrice => getPrice
function usePrice(id) {
  useEffect((id)=>{
      // 这里处理副作用,从TotalPrice函数中分离出来了
      fetch(`xxx.com?id=${productId}`).then((res)=>{
        return res.price
  	})
  }, [])
}

// TotalPrice => getTotalPrice
function TotalPirce({id1, id2}) {
  const p1 = usePrice(id1);
  const p2 = usePrice(id2);

  return <TotalPirce props={...}>
}
generator

我们知道 async和await 就是generator的语法糖.async = \*;wait = yield

  • 可以实现中断和继续
  • 但是不能计算优先级和排序优先级

解耦副作用场景

函数式编程中非常常见,如redux-saga,将副作用从sage中分离,自己不处理副作用

下面这个例子,副作用的获取是放在call方法中的

function* fetchUser(action) {
   try {
      const user = yield call(Api.fetchUser, action.payload.userId);
      yield put({type: "USER_FETCH_SUCCEEDED", user: user});
   } catch (e) {
      yield put({type: "USER_FETCH_FAILED", message: e.message});
   }
}

总结

严格意义上说React是不支持代数效应的,但是React有Fiber,执行完这个Fiber的更新后,交换执行权给浏览器,让浏览器决定后面怎么调度