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中这部分叫做SchedulerLane:有了异步调度,我们还需要细粒度的管理各个任务的优先级,让高优先级的任务优先执行,各个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的更新后,交换执行权给浏览器,让浏览器决定后面怎么调度