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中的三个概念
Fiber:react15的更新是同步的,因为它不能将工作宰割,所以须要一套数据结构让它既能对应实在的dom又能作为分隔的单元,这就是Fiber。
let firstFiberlet nextFiber = firstFiberlet shouldYield = false//firstFiber->firstChild->siblingfunction performUnitOfWork(nextFiber){ //... return nextFiber.next}function workLoop(deadline){ while(nextFiber && !shouldYield){ nextFiber = performUnitOfWork(nextFiber) shouldYield = deadline.timeReaming < 1 } requestIdleCallback(workLoop)}requestIdleCallback(workLoop)
- Scheduler:有了Fiber,咱们就须要用浏览器的工夫片异步执行这些Fiber的工作单元,咱们晓得浏览器有一个api叫做requestIdleCallback,它能够在浏览器闲暇的时候执行一些工作,咱们用这个api执行react的更新,让高优先级的工作优先响应不就能够了吗,但事实是requestIdleCallback存在着浏览器的兼容性和触发不稳固的问题,所以咱们须要用js实现一套工夫片运行的机制,在react中这部分叫做scheduler。
- 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的呈现。