极简前端史
了解 react 的诞生
-
1994 年,Netscape(网景公司) 公开发行了 Netscape 浏览器。这个版本的浏览器只能用来浏览静态页面,不具备与访问者互动的能力;
-
1995 年,微软获得了旧版 Mosaic 的代码授权,建立 IE;
-
1995 年 5 月,艾克只用了 10 天,就设计完成了 javascript 的第一版,但是他并不喜欢这个产物;
-
2009 年,jQuery 通过 Sizzle 选择器引擎,取得了压倒性的优势;
-
随着前端代码复杂性的提升
-
三大框架出现,Angular(2009)、React(2011)、Vue(2014);
历史的启发:
1、发现问题-解决问题是前进的动力;
2、分治是解决问题的重要方法,比如:前端三剑客、MVC/MVVM、模块化、组件化;
3、后发优势,少走弯路,比如前端借鉴后端成熟的框架设计;
4、未来是难以预测的,否则艾克也不会消极对待 javascript;
React 底层机制
解决了什么问题?
react 解决了两个问题:快速构建和快速响应。快速构建,是从开发者视角来说的,提升生产效率;
快速响应,是从用户视角来说的,提升用户体验。
如何才能构建快速响应的应用?这里有两类制约因素:CPU 瓶颈和 IO 瓶颈。
-
CPU 瓶颈:CPU 密集型操作,如果在 16.6 ms 中无法完成 js 执行和页面渲染就会导致掉帧,这部分是可以从框架层面去优化的,优化思路有两个:减少 cpu 计算和将 cpu 计算拆分成多个任务。
-
IO 瓶颈:比如网络请求、文件读写,这部分是不可控的,但是可以从交互的角度去优化;
如何解决这 IO 和 CPU 瓶颈呢?IO 瓶颈是不可控的部分,框架层面无法解决,这里先不考虑,那 CPU 瓶颈如何解决呢?
react 采用的策略是异步可中断更新,它并没有减少 cpu 计算,而是将 cpu 计算拆分成多个任务去调度。react 的渲染分成 render 阶段和 commit 阶段,render 阶段主要是构建 fiber 树、打 effectTag,commit 阶段根据 effectTag 做 DOM 变更和渲染。
其中 render 阶段是比较耗时,所以 react 把 render 阶段设计成可中断的结构,通过时间片的方式调度。
渲染机制
首先看一下 react 的虚拟 DOM 结构(Fiber 树)
react 的渲染逻辑
Hooks 出现的原因
-
困难:难以复用带状态的逻辑
-
renderProps/HOC 存在问题
-
需要改写原有组件结构
-
wrapper hell
-
-
-
困境:复杂组件变得难以理解
- 无关逻辑混合在一起或者状态散落在各地,不能拆分也不容易测试
-
class 的原罪:让人和机器都很困惑
-
AOT 编译优化经常会失效;
-
让热更新变得不可预测;
-
写一个 useState
假设有一个组件 App
定义 useState 以及相关的数据结构
处理 fiber 的 memoizedState( hook 链表)
计算 hook 的最新状态值
实现 dispatch 函数
实现调度函数
完整代码:code.byted.org/biz-platfor…
let isMounted = false;
// 在 App 渲染前
let workInProgressHook = null;
let isFlushing = false;
let fiber = {
render: App,
memoizeState: null,
}
function scheduleUpdateOnFiber() {
workInProgressHook = fiber.memoizeState;
const app = fiber.render();
isMounted = true;
return app;
}
function dispatchSetState(queue, action) {
const update = {
action,
next: null,
};
if (queue.pending == null) {
// 1 -> 1
// 1 -> 2 -> 1
update.next = update;
} else {
update.next = queue.pending.next;
queue.pending.next = update;
}
queue.pending = update;
if (!isFlushing) {
isFlushing = true;
queueMicrotask(() => {
scheduleUpdateOnFiber();
isFlushing = false;
});
}
}
function useState(initState) {
// 1. 创建 hook 对象,拼接到 fiber.memoizedState
let hook = null;
if (!isMounted) {
hook = {
memoizeState: initState,
queue: {
// update 对象链表
pending: null,
},
next: null,
};
if (fiber.memoizeState === null) {
fiber.memoizeState = hook;
} else {
workInProgressHook.next = hook;
}
workInProgressHook = hook;
} else {
hook = workInProgressHook;
workInProgressHook = workInProgressHook.next;
}
// 2. 计算最新的 baseState
let baseState = hook.memoizeState;
const queue = hook.queue;
if (queue.pending !== null) {
let curUpdate = queue.pending.next;
do {
baseState = curUpdate.action(baseState);
curUpdate = curUpdate.next;
} while(curUpdate && curUpdate !== queue.pending.next);
queue.pending = null;
hook.memoizeState = baseState;
}
// 3. return [baseState, dispatch];
return [baseState, dispatchSetState.bind(null, queue)];
}
function App() {
const [counter, setCounter] = useState(0);
const [counter1, setCounter1] = useState(0);
console.log('render ->', counter);
return {
onClick() {
setCounter(c => c + 1);
setCounter(c => c + 1);
setCounter(c => c + 1);
setCounter(c => c + 1);
}
}
}
window.app = scheduleUpdateOnFiber();