在讲hook原理之前,我们需要先简单了解一下Fiber。
一.Fiber
React16推出的,用于提高当前浏览器显示界面性能的东西,减少卡顿等待。
我们电脑屏幕都有一个刷新率(Hz),电脑屏幕上的东西,CPU提供数据,GPU将其绘制出来;
电脑屏幕按固定的频率,从缓存中取出(帧)并显示;缓存防止屏幕出现撕裂 ;
浏览器刷新频率一般与电脑一致,浏览器根据电脑传来的vsync(同步信号)来刷新。
- GUI渲染和JS代码执行(还有用户事件响应,键盘事件响应,requestAnimationFrame(),layout,paint)是在同一个线程的,互斥,最好在一帧中将所有操作做完。若一直执行JS代码,那GUI就一直得不到渲染,然后就会保持上一帧的图像。耗费时间长,也就意味着明显的卡顿;
- 早期React中,点击按钮后,有大量组件生成或更新,会大量占用浏览器刷新时间;这些耗时操作,都是在上一步中,且在一帧时间内,JS线程完成后才执行的;
- 为了解决上述问题, 现在React把我们需要reconciliation协同的东西(diff,更新),切分成很多的Fiber;
- Fiber为执行单元(执行碎片)。Fiber形成一个Fiber树,与ReactElement树结构一一对应;
- 现在控制权交给浏览器,让浏览器先做一些必须执行的用户/键盘/js代码响应等,若有剩余时间再做requestIdleCallback()=>Fiber碎片)来执行Fiber碎片。
Fiber源码去react-reconciler中,东西太多,先打个标记,在开发过程如果有需求再做深入。
二.useState Hook原理
现在举个useState Hook的例子。
- useState的本质是dispatcher.useState(initialState)
,也就是resolveDispatcher();
- resolveDispatch方法返回的dispatcher来自于ReactCurrentDispatcher.current。
- ReactCurrentDispatcher中的Dispatcher又来自于'react-reconciler/src/ReactFiberHooks'中。
- 具体Dispatcher如下:
export type Dispatcher = {|
readContext<T>(
context: ReactContext<T>,
observedBits: void | number | boolean,
): T,
useState<S>(initialState: (() => S) | S): [S, Dispatch<BasicStateAction<S>>],
useReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: (I) => S,
): [S, Dispatch<A>],
useContext<T>(
context: ReactContext<T>,
observedBits: void | number | boolean,
): T,
useRef<T>(initialValue: T): {|current: T|},
useEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void,
useLayoutEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void,
useCallback<T>(callback: T, deps: Array<mixed> | void | null): T,
useMemo<T>(nextCreate: () => T, deps: Array<mixed> | void | null): T,
useImperativeHandle<T>(
ref: {|current: T | null|} | ((inst: T | null) => mixed) | null | void,
create: () => T,
deps: Array<mixed> | void | null,
): void,
useDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void,
useResponder<E, C>(
responder: ReactEventResponder<E, C>,
props: Object,
): ReactEventResponderListener<E, C>,
useDeferredValue<T>(value: T, config: TimeoutConfig | void | null): T,
useTransition(
config: SuspenseConfig | void | null,
): [(() => void) => void, boolean],
|};
-
当组件使用了hooks进行渲染的时候,就会调用renderWithHooks函数。
- 真实环境下:如图所示,分为挂载时,和更新时的HooksDispatcher。
- 使用useState,第一次运行时,如上图所示。 若workInProgressHook为空则初始化,给它赋值hook。
- mountState函数中,若useState传入的为函数,多了一步执行过程。
hook的memorizedState和baseState都为initialState。
如果我调用多次useState,就如mountWorkInPrograssHook函数中的一样,判断workInProgressHook不为null,则创建一个新的hook,并将其拼接到workInProgressHook的next中。最终形成一个链表。
等到执行setState时,才会用到queue这个值。
在dispatchAction中 ,Fiber和queue的值,都来自刚刚mountState函数中的currentlyRenderingFiber和queue。
- 在执行setState时,就是进行一个dispatchAction的操作。
其中创建了一个update,代表了本次要更新的东西,更新的东西存放在action中,然后再放到update中。
若执行多次,则放入链表中。
- 在NoWork没有工作的状态下,useState本质上用的是useReducer;若有工作,在空闲的时候再执行update。
若有多个hook同时使用,本质上是以链表的形式,一个个保存hooks的。
不能在if中使用hook,因为其是个链表,可能数据会错乱。
东西太多了,可能看的有点乱,以后有需求再反复深入。