React hooks实战总结

  • 深入源码分析hooks,这里我们以刚使用过的hook useState
    为例,看看他是怎么管理我们的FC state的。

    export function useState(initialState: (() => S) | S) {
         const dispatcher = resolveDispatcher();
         return dispatcher.useState(initialState);
     }

    这个函数接收一个参数 initialState: (() => S) | S
    ,初始state的函数或者我们的state初始值。
    然后调用

                    <code>dispatcher.useState(initialState);</code>
    

    ,这里我们看一下 dispatcher
    是怎么来的:

    function resolveDispatcher() {
        const dispatcher = ReactCurrentDispatcher.current;
        ...
        return dispatcher;
    }

    发现是通过 ReactCurrentDispatcher.current
    得到,那 ReactCurrentDispatcher
    又是何方神圣呢?

    我们进一步看看它怎么来的
            <pre>import type {Dispatcher} from 'react-reconciler/src/ReactFiberHooks';
    

    const ReactCurrentDispatcher = {

    current: (null: null | Dispatcher),
    };
    export default ReactCurrentDispatcher;

    根据type,我们可以判断dispatcher的类型是 react-reconciler/src/ReactFiberHooks
    里面定义的 Dispatcher
    ,可以看到这个current属性是个null。那它是什么时候被赋值的呢?

    我们来看看functionComponent的render过程 renderWithHooks

    export function renderWithHooks(
        current: Fiber | null,
        workInProgress: Fiber,
        Component: any,
        props: any,
        refOrContext: any,
        nextRenderExpirationTime: ExpirationTime,
    ): any{
        ....
        if (__DEV__) {
            ReactCurrentDispatcher.current = HooksDispatcherOnMountInDEV;
        } else {
            ReactCurrentDispatcher.current =
            nextCurrentHook === null
                ? HooksDispatcherOnMount
                : HooksDispatcherOnUpdate;
        }
    
    }

    这里react源码根据 nextCurrentHook
    做了一些判断,我移除掉了,只关注 ReactCurrentDispatcher.current
    的值,可以看到它的取值分为两种, HooksDispatcherOnMount
    HooksDispatcherOnUpdate
    分别对应 mount/update
    两个组件状态;这里我们先看
    HooksDispatcherOnMount

    const HooksDispatcherOnMount: Dispatcher = {
    ...
    useState: mountState,
    ...
    };

    这就是我们寻寻觅觅的 Dispatcher
    的长相,最终我们 useState
    在组件mount的时候执行的就是这个
    mountState

    了,那我们就迫不及待如饥似渴的来看看 mountState
    又做了什么吧。

    function mountState(
    initialState: (() => S) | S,
    ): [S, Dispatch<BasicStateAction>] {
    const hook = mountWorkInProgressHook();
    if (typeof initialState === 'function') {
        initialState = initialState();
    }
    hook.memoizedState = hook.baseState = initialState;
    const queue = (hook.queue = {
        last: null,
        dispatch: null,
        lastRenderedReducer: basicStateReducer,
        lastRenderedState: (initialState: any),
    });
    const dispatch: Dispatch<
        BasicStateAction,
    > = (queue.dispatch = (dispatchAction.bind(
        null,
        // Flow doesn't know this is non-null, but we do.
        ((currentlyRenderingFiber: any): Fiber),
        queue,
    ): any));
    return [hook.memoizedState, dispatch];
    }

    进入这个函数首先执行的
    mountWorkInProgressHook()

    获取到当前的 workInProgressHook
    ,看这个名字就知道他是和 workInProgress
    分不开了,这个 workInProgress
    代表了当前正在处理的fiber,
    fiber

    是当前组件的需要完成或者已经完成的work的对象,也可以理解为我们的这个正在执行mountState的组件的各种数据和状态的集合。我们来具体的看一下mountWorkInProgressHook的执行逻辑:

    function mountWorkInProgressHook(): Hook {
        const hook: Hook = {
            memoizedState: null,
            baseState: null,
            queue: null,
            baseUpdate: null,
            next: null,
        };
    
        if (workInProgressHook === null) {
        // This is the first hook in the list
            firstWorkInProgressHook = workInProgressHook = hook;
        } else {
        // Append to the end of the list
            workInProgressHook = workInProgressHook.next = hook;
        }
        return workInProgressHook;
    }

    判断当前 fiber
    workInProgressHook
    是不是 null
    ,如果是,将全新的hook赋值给全局的 workInProgressHook
    firstWorkInProgressHook
    ,否则,将初始值赋值给 workInProgressHook
    。相当于 mountState
    里面的hook值就是

    const hook: Hook = {
            memoizedState: null,
            baseState: null,
            queue: null,
            baseUpdate: null,
            next: null,
        };
            实际上,workInProgressHook是这样的一个链表结构,React里面广泛使用了这样的结构存储副作用。
            <pre>{
    memoizedState: null,
    baseState: null,
    queue: null,
    baseUpdate: null,
    next: {
        ...
        next: {
            ...
            next: {
                next: {...},
                ...
            },
        },
    }
    

    }
    继续往下看:

    if (typeof initialState === 'function') {
        initialState = initialState();
    }
    hook.memoizedState = hook.baseState = initialState;

    useState接收的参数类型如果是函数,这里就会执行传进来的函数获取 initialState
    ,赋值给 hook.memoizedState = hook.baseState
    这两个属性,再往下,建立了当前 hook
    的更新队列 queue:
    ,这个我们后续再讲,这里暂时不用知道。继续往下看,是我们修改state的回调函数,通常是setState,通过改变 dispatchAction
    的this指向,将当前render的fiber和上面创建的queue作为参数传入,当我们执行 setState
    的时候实际上调用的就是这里的 dispatchAction
    ,最后一行:
    return [hook.memoizedState, dispatch];

    state
    setState
    以数组的形式返回,这也是我们使用 useState hook
    的正确姿势。到这里相信大家都很清楚了, useState
    通过将我们的初始 state
    暂存到 workInProgressHook
    memoizedState
    中,每次更新的时候通过 dispatchAction
    更新 workInProgressHook

    我们回过头来再看看刚才没深入过的 queue
    ,通过类型我们可以知道他是
    ,具体看看


    的定义:

    type UpdateQueue = {
        last: Update | null,
        dispatch: (A => mixed) | null,
        lastRenderedReducer: ((S, A) => S) | null,
        lastRenderedState: S | null,
    };

    看到这个结构,熟悉 react fiber
    的同学已经心中有数了,它的last属性是一个链表,用来存储当前hook的变化信息,能够通过
    next

    迭代处理所有变更信息和状态。这里我们就到此为止,感兴趣的同志可以自行深入琢磨,对于这个 hook
    ,掌握到这里已经够了,很多文章说 useState
    useReducer
    的基友关系,从这里我们就看出来了, useState
    最终使用的也是 useReducer
    一致的api,通过类似 redux
    的理念,通过 dispatchAction
    修改 state
    ,有兴趣的同志可以看这里
    useReducer

    源码;