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]; 
    } 
}
相关推荐
豐儀麟阁贵7 分钟前
8.5在方法中抛出异常
java·开发语言·前端·算法
zengyuhan50337 分钟前
Windows BLE 开发指南(Rust windows-rs)
前端·rust
醉方休40 分钟前
Webpack loader 的执行机制
前端·webpack·rust
前端老宋Running1 小时前
一次从“卡顿地狱”到“丝般顺滑”的 React 搜索优化实战
前端·react.js·掘金日报
隔壁的大叔1 小时前
如何自己构建一个Markdown增量渲染器
前端·javascript
用户4445543654261 小时前
Android的自定义View
前端
WILLF1 小时前
HTML iframe 标签
前端·javascript
枫,为落叶1 小时前
Axios使用教程(一)
前端
小章鱼学前端1 小时前
2025 年最新 Fabric.js 实战:一个完整可上线的图片选区标注组件(含全部源码).
前端·vue.js
ohyeah1 小时前
JavaScript 词法作用域、作用域链与闭包:从代码看机制
前端·javascript