乐趣区

关于react.js:学习react源码-征服面试官

react 源码解析 2.react 的设计理念

视频解说(高效学习):进入学习

往期文章:

1. 开篇介绍和面试题

2.react 的设计理念

3.react 源码架构

4. 源码目录构造和调试

5.jsx& 外围 api

6.legacy 和 concurrent 模式入口函数

7.Fiber 架构

8.render 阶段

9.diff 算法

10.commit 阶段

11. 生命周期

12. 状态更新流程

13.hooks 源码

14. 手写 hooks

15.scheduler&Lane

16.concurrent 模式

17.context

18 事件零碎

19. 手写迷你版 react

20. 总结 & 第一章的面试题解答

21.demo

异步可中断

  • React15 慢在哪里

在讲这部分之前,须要讲是那些因素导致了 react 变慢,并且须要重构呢。

React15 之前的协调过程是同步的,也叫 stack reconciler,又因为 js 的执行是单线程的,这就导致了在更新比拟耗时的工作时,不能及时响应一些高优先级的工作,比方用户的输出,所以页面就会卡顿,这就是 cpu 的限度。

  • 解决方案

如何解决这个问题呢,试想一下,如果咱们在日常的开发中,在单线程的环境中,遇到了比拟耗时的代码计算会怎么做呢,首先咱们可能会将工作宰割,让它可能被中断,在其余工作到来的时候让出执行权,当其余工作执行后,再从之前中断的局部开始异步执行剩下的计算。所以要害是实现一套异步可中断的计划。

  • 实现

在方才的解决方案中提到了工作宰割,和异步执行,并且能让出执行权,由此能够带出 react 中的三个概念

  1. Fiber:react15 的更新是同步的,因为它不能将工作宰割,所以须要一套数据结构让它既能对应实在的 dom 又能作为分隔的单元,这就是 Fiber。

    let firstFiber
    let nextFiber = firstFiber
    let shouldYield = false
    //firstFiber->firstChild->sibling
    function performUnitOfWork(nextFiber){
      //...
      return nextFiber.next
    }
    
    function workLoop(deadline){while(nextFiber && !shouldYield){nextFiber = performUnitOfWork(nextFiber)
              shouldYield = deadline.timeReaming < 1
            }
      requestIdleCallback(workLoop)
    }
    
    requestIdleCallback(workLoop)
  1. Scheduler:有了 Fiber,咱们就须要用浏览器的工夫片异步执行这些 Fiber 的工作单元,咱们晓得浏览器有一个 api 叫做 requestIdleCallback,它能够在浏览器闲暇的时候执行一些工作,咱们用这个 api 执行 react 的更新,让高优先级的工作优先响应不就能够了吗,但事实是 requestIdleCallback 存在着浏览器的兼容性和触发不稳固的问题,所以咱们须要用 js 实现一套工夫片运行的机制,在 react 中这部分叫做 scheduler。
  2. Lane:有了异步调度,咱们还须要细粒度的治理各个工作的优先级,让高优先级的工作优先执行,各个 Fiber 工作单元还能比拟优先级,雷同优先级的工作能够一起更新,想想是不是更 cool 呢。
  • 产生进去的下层实现

    因为有了这一套异步可中断的机制,咱们就能实现 batchedUpdates 批量更新和 Suspense

上面这两张图就是应用异步可中断更新前后的区别,能够领会一下

代数效应(Algebraic Effects)

除了 cpu 的瓶颈问题,还有一类问题是和副作用相干的问题,比方获取数据、文件操作等。不同设施性能和网络情况都不一样,react 怎么去解决这些副作用,让咱们在编码时最佳实际,运行利用时体现统一呢,这就须要 react 有拆散副作用的能力,为什么要拆散副作用呢,因为要解耦,这就是代数效应。

发问:咱们都写过获取数据的代码,在获取数据前展现 loading,数据获取之后勾销 loading,假如咱们的设施性能和网络情况都很好,数据很快就获取到了,那咱们还有必要在一开始的时候展现 loading 吗?如何能力有更好的用户体验呢?

看下上面这个例子

function getPrice(id) {return fetch(`xxx.com?id=${productId}`).then((res)=>{return res.price})
}

async function getTotalPirce(id1, id2) {const p1 = await getPrice(id1);
  const p2 = await getPrice(id2);

  return p1 + p2;
}

async function run(){await getTotalPrice('001', '002');  
}

getPrice 是一个异步获取数据的办法,咱们能够用 async+await 的形式获取数据,然而这会导致调用 getTotalPrice 的 run 办法也会变成异步函数,这就是 async 的传染性,所以没法拆散副作用。

function getPrice(id) {
  const price = perform id;
  return price;
}

function getTotalPirce(id1, id2) {const p1 = getPrice(id1);
  const p2 = getPrice(id2);

  return p1 + p2;
}

try {getTotalPrice('001', '002');
} handle (productId) {fetch(`xxx.com?id=${productId}`).then((res)=>{resume with res.price})
}

当初改成上面这段代码,其中 perform 和 handle 是虚构的语法,当代码执行到 perform 的时候会暂停以后函数的执行,并且被 handle 捕捉,handle 函数体内会拿到 productId 参数获取数据之后 resume 价格 price,resume 会回到之前 perform 暂停的中央并且返回 price,这就齐全把副作用拆散到了 getTotalPirce 和 getPrice 之外。

这里的要害流程是 perform 暂停函数的执行,handle 获取函数执行权,resume 交出函数执行权。

然而这些语法毕竟是虚构的,然而请看下上面的代码

function usePrice(id) {useEffect((id)=>{fetch(`xxx.com?id=${productId}`).then((res)=>{return res.price})
  }, [])
}

function TotalPirce({id1, id2}) {const p1 = usePrice(id1);
  const p2 = usePrice(id2);

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

如果把 getPrice 换成 usePrice,getTotalPirce 换成 TotalPirce 组件,是不是有点相熟呢,这就是 hook 拆散副作用的能力。

咱们晓得 generator 也能够做到程序的暂停和复原啊,那用 generator 不行就行了吗,然而 generator 暂停之后的复原执行,还是得把执行权替换给间接调用者,调用者会沿着调用栈持续上交,所以也是有传染性的,并且 generator 不能计算优先级,排序优先级。

function getPrice(id) {return fetch(`xxx.com?id=${productId}`).then((res)=>{return res.price})
}

function* getTotalPirce(id1, id2) {const p1 = yield getPrice(id1);
  const p2 = yield getPrice(id2);

  return p1 + p2;
}

function* run(){yield getTotalPrice('001', '002');  
}

解耦副作用在函数式编程的实际中十分常见,例如 redux-saga,将副作用从 saga 中拆散,本人不解决副作用,只负责发动申请

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 是不反对 Algebraic Effects 的,然而 react 有 Fiber 啊,执行完这个 Fiber 的更新之后交还执行权给浏览器,让浏览器决定前面怎么调度,由此可见 Fiber 得是一个链表构造能力达到这样的成果,

Suspense 也是这种概念的延长,前面看到了具体的 Suspense 的源码就有些感觉了。先看个例子

const ProductResource = createResource(fetchProduct);

const Proeuct = (props) => {
    const p = ProductResource.read( // 用同步的形式来编写异步代码!
          props.id
    );
  return <h3>{p.price}</h3>;
}

function App() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <Proeuct id={123} />
      </Suspense>
    </div>
  );
}

能够看到 ProductResource.read 齐全是同步的写法,把获取数据的局部齐全拆散出了 Proeuct 组件之外,在源码中,ProductResource.read 会在获取数据之前会 throw 一个非凡的 Promise,因为 scheduler 的存在,scheduler 能够捕捉这个 promise,暂停更新,等数据获取之后交还执行权。ProductResource 能够是 localStorage 甚至是 redis、mysql 等数据库,也就是组件即服务,可能当前会有 server Component 的呈现。

退出移动版