【useState原理】源码调试吃透REACT-HOOKS(一)
1 导读
2022年了,用React开发不使用hook是不行的。同时一方面,由于我在日常开发中已经许久没有使用class组件,所以一直对于hook的设计理念、实现原理和相关源码有一定的兴趣。原因无他,用hook真的太爽了。
开始之前,先抛出几个问题:
- react-hook解决了什么问题?
- react中的函数是无状态的,hook是怎么做到赋予其状态的?
- 典型问题:为什么hook必须在顶层调用?-->引申:在函数组件中多个hook是怎么记录的
useMemo
和useCallback
是怎么做缓存的?- hook的调用过程,从挂载、首次渲染、二次渲染到销毁的流程?
- ······
从这里开始,我们一一解答。
2 HOOK的相关概念
协调器目录 github.com/facebook/re…
2.1 为什么要在react中引入hooks?
不知道诸位有没有使用class
组件的经历,属实是又臭又长,繁多且命名复杂的生命周期给开发者带来的体验并不好。在这之前的function
组件由于没有状态的概念,只能用来承载简单的UI,这显然不行,react的数据驱动意味着状态逻辑实际上是无处不在的。
依据官方文档的解释,引入hook解决了三个以及更多的问题
- 在组件之间复用状态逻辑很难
- 复杂组件变得难以理解
- 难以理解的class
实际体现上,我也无比认同引入hook的实际效果
- hook的引入使我们在无需修改组件结构的情况下即可复用状态逻辑,不管是在跨层级状态共享还是复杂逻辑抽象上都有了质的提高
- 我们在使用函数式组件时不再关注生命周期,只要保证hook在最顶层即可在函数中将和组件相关联的部分自由地拆分
- hook 使你在非 class 的情况下可以使用更多的 React 特性
2.2 Fiber结构
我有一篇文章讲的是Fiber结构的实现https://juejin.cn/post/7030069221342052389
,这里只给一下代码:
function FiberNode(
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode,
) {
// 实例变量,从字面意思也应该可以看出这里保存了tag、key、type、state类似这样的有很强实际意义的属性
this.tag = tag;
this.key = key;
this.elementType = null;
this.type = null;
this.stateNode = null;
this.return = null;
this.child = null;
this.sibling = null;
this.index = 0;
this.ref = null;
this.pendingProps = pendingProps;
this.memoizedProps = null;
this.updateQueue = null;
this.memoizedState = null;
this.dependencies = null;
...
}
除了Fiber结构,还需要强调的一点是,react16.8之后的Fiber架构:
- Scheduler(调度器),还没看到请忽略,请记住这个概念
- Reconciler(协调器)
- Renderer(渲染器)
3 hook是怎么赋予函数式组件状态的?
开始之前,贴一下我整个hook篇的调试代码
代码语言:javascript复制import {useState, useEffect, useRef} from 'react';
const useMockRef = init => {
const [ref] = useState({current: init});
return ref;
};
const CountButton = () => {
const [count, setCount] = useState(0);
const [tick, setTick] = useState(0);
const realRef = useRef(0);
const mockRef = useMockRef(0);
const handleClick = () => {
setCount(count 1);
mockRef.current = 1;
realRef.current = 1;
};
const handleRefCountClick = () => {
mockRef.current = 10;
realRef.current = 10;
console.log('mockRef.current', mockRef.current);
};
useEffect(() => {
setTick(count Math.random());
}, [count]);
return (
<>
<button onClick={handleClick}>{count}:Render by state</button>
<button onClick={handleRefCountClick}>Click to add ref</button>
<div style={{color: 'red'}}>{tick}</div>
<div style={{color: 'yellow'}}>{mockRef?.current}</div>
<div style={{color: 'green'}}>{realRef?.current}</div>
</>
);
};
export {CountButton};
进入到这里,我们需要进入到react源码的部分,这里我们需要关注的是FiberBeginWork
github.com/facebook/re…
调试代码
代码语言:javascript复制const CountButton = () => {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count 1);
};
return (
<>
<button onClick={handleClick}>{count}:Render by state</button>
</>
);
};
3.1 beginWork
在之前的文章中,我们其实已经对Fiber
有一定的了解,也知道了在react中一个Fiber
其实也就是对应一个虚拟DOM。那么我们现在看到function beginWork
#L3829
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
...
}
可以发现beginWork
的入参为「current、workInProgress、renderLanes」,前两者对应react架构中的两颗Fiber树,renderLanes
则是和优先级相关的参数,和Scheduler
相关,这一部分我还没仔细研究