react hooks原理
- useState 的实现原理
function render() {ReactDOM.render(<App />, document.getElementById("root"));}let state: any;export function useState<T>(initState: T): [T, (newState: T) => void] {state = state || initState;function setState(newState: T) {state = newState;// 改变新值后 renderrender();}return [state, setState];}// 首次renderrender()
- 循环下或者判断下调用
const App = () => {const [count, setCount] = useState(0);if (count == 0) {useEffect(() => {console.log(count);}, [count]);}return (<><div>{count}</div><button onClick={() => setCount(count + 1)}>+1</button></>);};
React Hook "useEffect" is called conditionally. React Hooks must be called in the exact same order in every component render react-hooks/rules-of-hooks
前面 useState 的简单实现里,初始的状态是保存在一个全局变量中的。以此类推,多个状态,应该是保存在一个专门的全局容器中。这个容器,就是一个朴实无华的 Array 对象。具体过程如下:
- 第一次渲染时候,根据 useState 顺序,逐个声明 state 并且将其放入全局 Array 中。每次声明 state,都要将 cursor 增加 1。
- 更新 state,触发再次渲染的时候。cursor 被重置为 0。按照 useState 的声明顺序,依次拿出最新的 state 的值,视图更新。
let flag = true;const App = () => {const [count, setCount] = useState(0);if (flag) {const [unusedCount] = useState(1);flag = false;}const [count2, setCount2] = useState(0);return (<><div>{count}</div><div>{count2}</div><button onClick={() => setCount(count + 1)}>+1</button><button onClick={() => setCount2(count2 + 1)}>+1</button></>);};
由于在条件判断的逻辑中,重置了flag = false,因此此后的渲染不会再进入条件判断语句。看起来好像没有问题?但是,由于 useState 是基于 Array + Cursor 来实现的,第一次渲染时候,state 和 cursor 的对应关系如下表:
| 变量名 | cursor |
|---|---|
| count | 0 |
| unusedCount | 1 |
| count2 | 2 |
当点击事件触发再次渲染,并不会进入条件判断中的 useState。所以,cursor=2 的时候对应的变量是 count2。而其实 count2 对应的 cursor 应该是 3。就会导致setCount2并不起作用。也就是说,每次渲染后通过 cursor 在查找对应的 state会出现乱序的现象
- useEffect 的实现原理
useEffect 是被称为副作用,但是正确的理解是根据一个依赖改变,会对影响组件的内部状态,其思想来源于函数式编程。
具体 useEffect 用法看参考:useEffect 完整指南
在 useEffect 的第二个参数中,我们可以指定一个数组,如果下次渲染时,数组中的元素没变,那么就不会触发这个副作用(可以类比 Class 类的关于 nextprops 和 prevProps 的生命周期)。好处显然易见,相比于直接裸写在函数组件顶层,useEffect 能根据需要,避免多余的 render
// 还是利用 Array + Cursor的思路const allDeps: any[][] = [];// effect个数,按照顺序调用let effectCursor: number = 0;function useEffect(callback: () => void, deps: any[]) {// 初次渲染:统计依赖->确定 effect 顺序->调用回调函数if (!allDeps[effectCursor]) {allDeps[effectCursor] = deps;++effectCursor;callback();return;}const currenEffectCursor = effectCursor;const rawDeps = allDeps[currenEffectCursor];// 检测依赖项是否发生变化,发生变化需要重新renderconst isChanged = rawDeps.some((dep: any, index: number) => dep !== deps[index]);if (isChanged) {callback();}++effectCursor;}function render() {ReactDOM.render(<App />, document.getElementById("root"));effectCursor = 0; // 注意将 effectCursor 重置为0}
- Class VS Hooks | Class | Hooks | | —- | —- | | 学习成本较高,需要记住生命周期一些新概念 | 更加灵活,开发者可以自定义方法 | | 代码复用可以使用HOC与renderProps | 自定义hooks需要开发对逻辑有更清晰掌握 | | 不容易发生内存泄漏 | 闭包大量使用会影响性能 | | 代码结构清晰(生命周期),心智负担小 | 需要配合变量名和注释,心智负担大 |
