React Hooks之useState

一、基础回顾

1、useState

该hook用于给函数组件添加状态变量;

js 复制代码
const [state, setState] = useState(initialState);

(1)参数 - initialState

initialState用于设置组件的初始化状态,该初始值只有在组件首次渲染时使用。

(2)返回值

该hook返回一组值:[state, setState]

  • state:状态值;每调用一次useState,返回的都是一个新的state变量;
  • setState:该函数用于更新状态值,并触发组件的重新渲染;
    • useState的更新是异步的,在调用setState时,并不会立即更新状态,而是把一次重新渲染添加到队列中,React会在合适的时机处理这次更新;

2、为什么要引入useState?

  • 局部变量无法在多次渲染中持久保持;
    • React在渲染函数式组件时,每次都会从头开始渲染,不会考虑之前对局部变量的任何更改;
  • 更改局部变量不会触发组件的重新渲染;

二、useState的原理

Code示例

js 复制代码
import {useState, useRef, useCallback, useEffect} from 'react'; 
function App() { 
    const [count, setCount] = useState(0); 
    const [name, setName] = useState('wxm'); 
    const ref = useRef('ref'); 
    console.log('组件渲染了'); 
    const handleClick = useCallback(() => { 
        setCount(count + 1); 
        setCount(count + 1); 
        console.log('------'); 
        setName('Amy'); 
        setCount(count => count + 1); 
    }, []); 
    
    useEffect(() => { 
        if (name === 'Amy') { 
            setCount(count => count + 1); 
        } 
    }, [name]); 
    return ( 
        <div> 
            <button onClick={handleClick}>更改</button> 
            <p> 
                {/* 在React中,当前标签包含5个子元素:当前的name值为:{name} 这是两个子元素 */} 
                当前的name值为:{name} 
                <br/> 
                当前的count值为:{count} 
            </p> 
            {/* 一般不建议将ref作为渲染在页面中的内容 */} 
            {ref.current} 
        </div> 
    ); 
}; 
export default App;

useState Hook的定义

当我们在函数组件中调用useState时,useState到底是怎样的一个定义呢?它在源码中其实是这样的:

js 复制代码
export function useState<S>( 
    initialState: (() => S) | S 
): [S, Dispatch<BasicStateAction<S>>] { 
    const dispatcher = resolveDispatcher(); 
    return dispatcher.useState(initialState); 
} 

可以看到,它实际上调用的是dispatcher对象的useState函数;

dispatcher对象

js 复制代码
function resolveDispatcher() { 
    const dispatcher = ReactCurrentDispatcher.current; 
    return ((dispatcher: any): Dispatcher); 
} 
// ReactCurrentDispatcher.current的赋值 
ReactCurrentDispatcher.current = 
    current === null || current.memoizedState === null 
        ? HookDispatcherOnMount 
        : HookDispatcherOnUpdate; 
// HookDispatcherOnMount 与 HookDispatcherOnUpdate 是两个存储hook函数的对象 
const HooksDispatcherOnMount: Dispatcher = { // 首次渲染时,要访问的对象 
    // ...省略了一些hook 
    useState: mountState // 首次渲染时,调用mountState函数 
}; 
const HooksDispatcherOnUpdate: Dispatcher = { // 更新渲染时,要访问的对象 
    // ...省略了一些hook 
    useState: updateState // 渲染更新时,调用updateState函数 
};

组件的首次渲染

当组件首次渲染时,调用useState,其实调用的是一个叫做mountState的函数,该函数通过调用mountWorkInProgress创建一个hook对象;源码如下

js 复制代码
function mountState<S>( 
    initialState: (() => S) | S 
): [S, Dispatch<BasicStateAction<S>>] { 
    const hook = mountWorkInProgressHook();
    // ...省略代码
}
function mountWorkInProgressHook(): Hook { 
    // 新建一个hook对象,它的结构如下:
    const hook: Hook = { 
        memoizedState: null, // 可能保存的值有:state状态值,effect中的副作用操作;useMemo中缓存的值;useRef缓存的值 
        baseState: null, // 更新中,产生的最新的state值【计算最新状态值需要参考的值】 
        baseQueue: null, // 保存最新的更新队列 
        queue: null, // 存储该hook本次更新阶段的所有操作【保存待更新队列pendingQueue、更新函数dispatch】 
        next: null, // 链接下一个hook 
    }; 
    // ...省略代码
    // 返回新创建的hook对象 
    return workInProgressHook; 
}

当我们执行第一个hookconst [count, setCount] = useState(0);时,会创建第一个hook对象,如下图所示:

接着,我们执行const [name, setName] = useState('wxm');,这是该hook的首次创建,依然需要创建一个新的hook对象;但workInProgressHook对象不为空,所以我们执行的是mountWorkInProgressHook 的else逻辑:workInProgressHook = workInProgressHook.next = hook;

当前App组件首次执行完成后,React会创建一个完整的hooks链表,其结构如下图:

由上图可以看出,多个hook调用形成了一个链表,该链表是一个单向链表;

组件的更新渲染

点击按钮,在事件处理函数中调用了多个更新函数,React不会立即更新状态值,而是将一次渲染添加到队列中,我们调用了三次setCount函数,它会形成一个环形链表,结构如下:

在调用hook的setter函数时,实际上调用的是dispatchSetState函数;

js 复制代码
function dispatchSetState<S, A>( 
    fiber: Fiber, 
    queue: UpdateQueue<S, A>, 
    action: A 
): void { 
    const lane = requestUpdateLane(fiber); 
    const update: Update<S, A> = { 
        lane, action, 
        hasEagerState: false, 
        eagerState: null, 
        next: (null: any) 
    }; 
    if (isRenderPhaseUpdate(fiber)) { 
        // 如果是渲染阶段的更新:将渲染阶段的更新添加到更新队列中那个 
        enqueueRenderPhaseUpdate(queue, update); 
    } else { 
        // 非渲染阶段:获取最新state,执行更新 
        const alternate = fiber.alternate; // 用于连接current和workInProgressTree 
        if ( 
            fiber.lanes === NoLanes && 
            (alternate === null || alternate.lanes === NoLanes) 
        ) { 
            // 表示在进入渲染阶段之前,可以先计算下一个state; 
            // 如果新的state和当前state相同,可以退出渲染 
            const lastRenderedReducer = queue.lastRenderedReducer; 
            if (lastRenderedReducer !== null) { 
                let prevDispatcher; 
                try { 
                    // 获取上一次更新的state 
                    const currentState: S = (queue.lastRenderedState: any); 
                    // eagerState:紧迫的状态 
                    // 意义在于:在当前组件不存在更新的情况下,可以在组件render钱就提前计算出最新状态 
                    // 当前组件不存在更新:指的是current fiber和 WIP fiber都不存在更新 
                    const eagerState = lastRenderedReducer(currentState, action); 
                    // 在update对象上,隐藏计算的状态,并且使用reducer去计算新的状态 
                    update.hasEagerState = true; 
                    update.eagerState = eagerState; 
                    if (is(eagerState, currentState)) { 
                        enqueueConcurrentHookUpdateAndEagerlyBailout(fiber, queue, update); return; 
                    } 
                } catch (error) { } finally { } 
            } 
        } 
        const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane); 
        if (root !== null) { 
            const eventTime = requestEventTime(); 
            scheduleUpdateOnFiber(root, fiber, lane, eventTime); 
            entangleTransitionUpdate(root, queue, lane); 
        } 
    } 
} 
function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S { 
    return typeof action === 'function' ? action(state) : action; 
}

由于在事件处理函数中调用了状态更新函数,会触发组件的重新渲染,当组件再次渲染时,调用const [count, setCount] = useState(0),由于是第二次渲染,在调用useState时,实际上调用的是updateState函数。

js 复制代码
function updateState<S> ( 
    initialState: (() => S) | S 
): [S, Dispatch<BasicStateAction<S>>] { 
    return updateReducer(basicStateReducer, (initialState: any)); 
}

[小结]

到这里,我们会发现:

第一次渲染组件,执行到useState时,React实际上会调用mountState去初始化状态;

当整个组件渲染完成,点击按钮执行事件处理函数时,调用setCount函数,实际上调用dispatchSetState将此次更新添加到队列中,并触发组件的重新渲染;

第二次渲染组件时,遇到useState时,React实际上会执行updateState函数;

三、源码

useState函数定义

js 复制代码
export function useState<S>( 
    initialState: (() => S) | S 
): [S, Dispatch<BasicStateAction<S>>] { 
    const dispatcher = resolveDispatcher(); 
    return dispatcher.useState(initialState); 
} 
function resolveDispatcher() { 
    const dispatcher = ReactCurrentDispatcher.current; 
    return ((dispatcher: any): Dispatcher); 
} 
// ReactCurrentDispatcher.current的赋值 
ReactCurrentDispatcher.current = 
    current === null || current.memoizedState === null 
        ? HookDispatcherOnMount 
        : HookDispatcherOnUpdate; 
// HookDispatcherOnMount 与 HookDispatcherOnUpdate 是两个存储hook函数的对象 
const HooksDispatcherOnMount: Dispatcher = { // 首次渲染时,要访问的对象 
    // ...省略了一些hook 
    useState: mountState // 首次渲染时,调用mountState函数 
}; 
const HooksDispatcherOnUpdate: Dispatcher = { // 更新渲染时,要访问的对象 
    // ...省略了一些hook 
    useState: updateState // 渲染更新时,调用updateState函数 
};

useState函数的定义可以看到,该函数接收一个参数,该参数可以是函数类型,也可以是其他类型。针对该参数的处理我们可以看函数的具体实现;

该函数返回一个数组,数组中包含两个值:一个状态对象,一个更新状态的函数;

mountState实现

js 复制代码
function mountState<S>( 
    initialState: (() => S) | S 
): [S, Dispatch<BasicStateAction<S>>] { 
    const hook = mountWorkInProgressHook(); // 初始化一个hook对象 
    if (typeof initialState === 'function') { 
        // 如果initialState是一个函数,获取函数返回值并重新赋值给initialState作为初始状态 
        initialState = initialState(); 
    } 
    // memoizedState:用于存储和管理hooks状态的数据结构;表示该节点对应的状态值 
    // React在组件首次渲染时,创建一个memoizedState链表,包含组件的各个状态 
    // 缓存状态及更新状态时要参照的状态都设置为我们的初始化状态initialState 
    hook.memoizedState = hook.baseState = initialState; 
    const queue: UpdateQueue<S, BasicStateAction<S>> = { 
        pending: null, // 待更新的状态 
        lanes: NoLanes, // 更新有优先级,默认没有优先级,或者说优先级最低 
        dispatch: null, // 负责更新函数 
        lastRenderedReducer: basicStateReducer, // 负责获取到最新的state 
        lastRenderedState: (initialState: any), // 上一次渲染的state,首次渲染时指定为当前state 
    }; 
    hook.queue = queue; // 负责更新的函数 
    const dispatch: Dispatch<BasicStateAction<S>> = (queue.dispath = 
        (dispatchSetState.bind(null, currentlyRenderingFiber, queue): any) 
    ); 
    return [hook.memoizedState, dispatch]; 
}

mountState函数的实现可以得出结论:如果传入的初始状态是个函数,则调用该函数设置初始化状态;否则,直接使用传入的参数作为初始化状态;

状态更新函数实际上调用的是dispatchSetState函数;

dispatchSetState函数实现

js 复制代码
function dispatchSetState<S, A>( 
    fiber: Fiber, 
    queue: UpdateQueue<S, A>, 
    action: A 
): void { 
    const lane = requestUpdateLane(fiber); 
    const update: Update<S, A> = { 
        lane, action, 
        hasEagerState: false, 
        eagerState: null, 
        next: (null: any) 
    }; 
    if (isRenderPhaseUpdate(fiber)) { 
        // 如果是渲染阶段的更新:将渲染阶段的更新添加到更新队列中那个 
        enqueueRenderPhaseUpdate(queue, update); 
    } else { 
        // 非渲染阶段:获取最新state,执行更新 
        const alternate = fiber.alternate; // 用于连接current和workInProgressTree 
        if ( 
            fiber.lanes === NoLanes && 
            (alternate === null || alternate.lanes === NoLanes) 
        ) { 
            // 表示在进入渲染阶段之前,可以先计算下一个state; 
            // 如果新的state和当前state相同,可以退出渲染 
            const lastRenderedReducer = queue.lastRenderedReducer; 
            if (lastRenderedReducer !== null) { 
                let prevDispatcher; 
                try { 
                    // 获取上一次更新的state 
                    const currentState: S = (queue.lastRenderedState: any); 
                    // eagerState:紧迫的状态 
                    // 意义在于:在当前组件不存在更新的情况下,可以在组件render钱就提前计算出最新状态 
                    // 当前组件不存在更新:指的是current fiber和 WIP fiber都不存在更新 
                    const eagerState = lastRenderedReducer(currentState, action); 
                    // 在update对象上,隐藏计算的状态,并且使用reducer去计算新的状态 
                    update.hasEagerState = true; 
                    update.eagerState = eagerState; 
                    if (is(eagerState, currentState)) { 
                        enqueueConcurrentHookUpdateAndEagerlyBailout(fiber, queue, update); return; 
                    } 
                } catch (error) { } finally { } 
            } 
        } 
        const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane); 
        if (root !== null) { 
            const eventTime = requestEventTime(); 
            scheduleUpdateOnFiber(root, fiber, lane, eventTime); 
            entangleTransitionUpdate(root, queue, lane); 
        } 
    } 
} 
function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S { 
    return typeof action === 'function' ? action(state) : action; 
}

updateState函数实现

js 复制代码
function updateState<S> ( 
    initialState: (() => S) | S 
): [S, Dispatch<BasicStateAction<S>>] { 
    return updateReducer(basicStateReducer, (initialState: any)); 
}
function updateReducer<S, I, A>( 
    reducer: (S, A) => S, 
    initialArg: I, 
    init?: I => S 
): [S, Dispatch<A>] { 
    const hook = updateWorkInProgressHook(); 
    const queue = hook.queue; 
    if (queue === null) { 
        // 更新时,如果没有更新队列,会报错 
        throw new Error('Should have a queue. This is likely a bug in React. Please file an issue.'); 
    } 
    queue.lastRenderedReducer = reducer; // reducer指向basicStateReducer 
    const current: Hook = (currentHook: any);
    let baseQueue = current.baseQueue; // 获取上次收集到的还未执行的待更新队列 
    const pendingQueue = queue.pending; 
    if (pendingQueue !== null) { 
        // 有新的更新对象,需要将添加到basequeue中 
        if(baseQueue !== null) { 
            // 合并更新队列:建立起链表 
            const baseFirst = baseQueue.next; 
            const pendingFirst = pendingQueue.next; 
            baseQueue.next = pendingFirst; 
            pendingQueue.next = baseFirst; 
        } 
        current.baseQueue = baseQueue = pendingQueue; 
        queue.pending = null; // 新的更新已经添加到更新队列中,将pending置空 
    } 
    if (baseQueue !== null) { 
        // 有一个更新队列待运行 
        cosnt first = baseQueue.next; 
        let newState = current.baseState; 
        let newBaseState = null; 
        let newBaseQueueFirst = null; 
        let newBaseQueueLast: Update<S, A> | null = null; 
        let update = first; 
        do { 
            const updateLane = removeLanes(update.lane, OffscreenLane); 
            const isHiddenUpdate = updateLane !== update.lane; 
            const shouldSkipUpdate = isHiddenUpdate 
                ? !isSubsetOfLanes(getWorkInProgressRootRenderLanes(), updateLane) 
                : !isSubsetOfLanes(renderLanes, updateLane); 
            if (shouldSkipUpdate) { 
                const clone: Update<S, A> = { 
                    lane: updateLane, 
                    action: update.action, 
                    hasEagerState: update.hasEagerState, 
                    eagerState: update.eagerState, 
                    next: (null: any) 
                }; 
                if (newBaseQueueLast === null) { 
                    newBaseQueueFirst = newBaseQueueLast = clone; 
                    newBaseState = newState; 
                } else { 
                    newBaseQueueLast = newBaseQueueLast.next = clone; 
                } 
                currentlyRenderingFiber.lanes = mergeLanes( 
                    currentlyRenderingFiber.lanes, 
                    updateLane 
                ); 
                markSkippedUpdateLanes(updateLane); 
            } else { 
                if (newBaseQueueLast !== null) { 
                    const clone: Update<S, A> = { 
                        lane: NoLane, 
                        action: update.action, 
                        hasEagerState: update.hasEgaerState, 
                        eagerState: update.eagerState, 
                        next: (null: any) 
                    }; 
                    newBaseQueueLast = newBaseQueueLast.next = clone; 
                } 
                const action = update.action; 
                if (update.hasEagerState) { 
                    newState = ((update.eagerState: any): S); 
                } else { 
                    newState = reducer(newState, action); 
                } 
            } 
            update = update.next; 
        } while (update !== null && update !== first); 
        if (newBaseQueueLast === null) { 
            newBaseState = newState; 
        } else { 
            newBaseQueueLast.next = (newBaseQueueFirst: any); 
        } 
        if (!is(newState, hook.memoizedState)) { 
            markWorkInProgressReceivedUpdate(); 
        } 
        if (baseQueue === null) { 
            queue.lanes = NoLanes; 
        } 
        // 这里的queue.dispatch指向的依然是dispatchSetState函数;在首次挂载时就已经指定好了更新函数 
        const dispatch: Dispatch<A> = (queue.dispatch: any); 
        return [hook.memoizedState, dispatch]; 
    } 
}
相关推荐
bin91531 小时前
DeepSeek 助力 Vue 开发:打造丝滑的复制到剪贴板(Copy to Clipboard)
前端·javascript·vue.js·ecmascript·deepseek
晴空万里藏片云2 小时前
elment Table多级表头固定列后,合计行错位显示问题解决
前端·javascript·vue.js
曦月合一2 小时前
html中iframe标签 隐藏滚动条
前端·html·iframe
奶球不是球3 小时前
el-button按钮的loading状态设置
前端·javascript
kidding7233 小时前
前端VUE3的面试题
前端·typescript·compositionapi·fragment·teleport·suspense
Σίσυφος19005 小时前
halcon 条形码、二维码识别、opencv识别
前端·数据库
学代码的小前端5 小时前
0基础学前端-----CSS DAY13
前端·css
css趣多多6 小时前
案例自定义tabBar
前端
姑苏洛言7 小时前
DeepSeek写微信转盘小程序需求文档,这不比产品经理强?
前端
林的快手7 小时前
CSS列表属性
前端·javascript·css·ajax·firefox·html5·safari