手写 Hooks
hooks 手写仅仅是提供理解和学习hooks的方式,思考hooks的功能原理,而 react 中还提供了一套复杂的hooks管理机制,这部分会后续梳理。React hooks 分为2类:
- 基础Hooks:useState、useEffect、useContext
- 衍生Hooks:useReducer、useMemo、useCallback、useRef、useImperativeHandle、useLayoutEffect、useDebugValue
其中高级Hooks,都可以用基础Hooks和高级Hooks实现,而基础Hooks不行。而在 React 中,为确保hooks性能最佳,上述所有hooks都是原生实现,所以该部分实现仅用来加深对hooks的理解,关于原生实现可以直接看下方第二部分 —— hooks状态管理。
deps对比功能实现
// 存在不一致返回 true,否则返回 falseconst isDepsDiff = (prev, current) => {return !prev.every((dep, index) => Object.is(dep, current[index]));}
useRef
const useRef = (initialValue) => {const [ref] = useState(Object.defineProperties({}, {_value: {value: initialValue,writable: true},current: {get: function() {return this._value;},set: function(newValue) {this._value = newValue;},writable: true,enumerable: true,}}));return ref;}
useMemo
const useMemo = (fn, deps) => {const [value, setValue] = useState(fn);const mounted = useRef(false);useEffect(() => {// 初始化不需要执行, 避免不必要的渲染if(mounted.current){const newValue = fn();setValue(newValue);} else {mounted.current = true;}}, deps);return value;}
useCallback
const useCallback = (fn, deps) => {return useMemo(() => fn, deps);}
useReducer
const useReducer = (reducer, initialState, init) => {const [state, setState] = useState(() => {return init ? init(initialState) : initialState;});// 使用ref,确保 reducer 变化时 dispatch 不变const reducerRef = useRef<T>(reducer);reducerRef.current = reducer;const dispatch = useCallback((action) => {// 用回调方式,确保dispatch不变setState(prevState => {const newState = reducerRef.current(prevState, action);return {...state, ...newState}});}, []);return [state, dispatch];}
useImperativeHandle
const useImperativeHandle = (ref, createHandle, deps) => {useEffect(() => {ref.current = {...ref.current,...createHandle()};}, deps)};
useDebugValue
const useDebugValue = (value, formatter) => {// 该 hook 的实现就是个空函数,惊不惊喜!意不意外!// https://github.com/facebook/react/blob/master/packages/react-reconciler/src/ReactFiberHooks.old.js#L1608}
useState
const useState = (function() {let state = undefined;let stateInitialized = false;// setState 定义在闭包内,反复调用 useState 是不会变的const setState = (newState) => {state = typeof newState === 'funtion' ? newState(state) : newState;}return (initialState) => {if (stateInitialized) {state = typeof initialState === 'function' ? initialState() : initialState;stateInitialized = true;}return [state, setState];}})();
useEffect/useLayoutEffect
useEffect 和 useLayoutEffect 本身实现是一样的,只是 react 调用两个 hooks 的时间不同。
const useEffect = (function() {let prevDeps = undefined; // 不要用 useState 管理 deps, 会导致不必要的渲染;let clearFn = undefined;return (effect, deps) => {// 清理前一次的调用状态clearFn && clearFn();// useEffect 一定会被调用一次if(!prevDeps) {clearFn = effect();} else if(isDepsDiff(prevDeps, deps)) { // 判断依赖是否改变clearFn = effect();}prevDeps = deps;};})();
useContext
const useContext = (ctx) => {return ctx._currentValue;}
hooks 状态管理
很明显,上述 useState、useEffect的实现是有问题的。以 useState 为例试想在使用hooks的时候,同一个组件会使用多个hooks,而上述实现会导致所有的state都共用一个变量,甚至跨页面都无法避免。为解决这个问题,React 通过链表来实现状态管理,基本结构如下:
React 把渲染分为 mount 和 update 两个阶段:
- mount 阶段:构建 hooks 链表节点,动态生成 hooks 上述钩子的方法;
- update 阶段:按链表流程更新页面中各个 hooks;
也就是说,React 所有 hooks 都会有这样两个状态,即便是 useDebugValue 也存在 mountDebugValue 和 updataDebugValue 两个方法,只是恰好这两个方法都是空函数而已。
useState
不同的hooks节点,数据是不同的,useState如下:
const hooks = {state: null, // state数据setState: null, // 修改数据的方法next: null // 指向下一个节点}
简化后的useState如下 (试一试):
const Dispatcher = (() => {let initialRender = true;let firstWorkInProgressHook = null; // 链表头let workInProgressHook = null; // 当前节点const mountWorkInProgressHook = () => {const hook = {state: null,setState: null,next: null};if (workInProgressHook === null) {firstWorkInProgressHook = workInProgressHook = hook;} else {workInProgressHook = workInProgressHook.next = hook;}return workInProgressHook;};const updateWorkInProgressHook = () => {let curHook = workInProgressHook;workInProgressHook = workInProgressHook.next;return curHook;};const useState = (initialState) => {let curHook;if (initialRender) {curHook = mountWorkInProgressHook();curHook.state = initialState;} else {curHook = updateWorkInProgressHook();}curHook.setState = function (newState) {curHook.state = typeof newState === "function" ? newState(curHook.state) : newState;workInProgressHook = firstWorkInProgressHook;initialRender = false;rerender();};return [curHook.state, curHook.setState];};return {useState};})();
上述实现了一个useState,useEffect实现会更复杂一些,先看一下这个结构:
const effect: Effect = {tag: number, // 标记 react 类型create: null, // 副作用功能函数destroy: null, // 清除副作用的注销函数deps: null, // 前一次的依赖项next: null, // 下一个hook};
内部实现方式:
function pushEffect(tag, create, destroy, deps) {const effect: Effect = {tag,create,destroy,deps,next: null,};let componentUpdateQueue = currentlyRenderingFiber.updateQueue;if (componentUpdateQueue === null) {componentUpdateQueue = createFunctionComponentUpdateQueue();currentlyRenderingFiber.updateQueue = componentUpdateQueue;componentUpdateQueue.lastEffect = effect.next = effect;} else {const lastEffect = componentUpdateQueue.lastEffect;if (lastEffect === null) {componentUpdateQueue.lastEffect = effect.next = effect;} else {const firstEffect = lastEffect.next;lastEffect.next = effect;effect.next = firstEffect;componentUpdateQueue.lastEffect = effect;}}return effect;}
