【react】Hook 源码解读

文章探讨了React的函数组件与类组件在处理状态更新上的差异,解释了为何在类组件中setTimeout内的setState可以获取到更新后的状态,而在函数组件中却不行。文章还介绍了Hooks的概念,特别是useState钩子的工作原理,模拟了简单的Hooks源码来帮助理解状态管理和更新流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

hook 用在最顶层 不能包在if else 里面

function组件和class组件本质的区别

class Index extends React.Component<any,any>{
    constructor(props){
        super(props)
        this.state={
            number:0
        }
    }
    handerClick=()=>{
       for(let i = 0 ;i<5;i++){
           setTimeout(()=>{
               this.setState({ number:this.state.number+1 })
               console.log(this.state.number)
           },1000)
       }
    }

    render(){
        return <div>
            <button onClick={ this.handerClick } >num++</button>
        </div>
    }
}
function Index(){
    const [ num ,setNumber ] = React.useState(0)
    const handerClick=()=>{
        for(let i=0; i<5;i++ ){
           setTimeout(() => {
                setNumber(num+1)
                console.log(num)
           }, 1000)
        }
    }
    return <button onClick={ handerClick } >{ num }</button>
}

上面得类组件和函数组件得打印结果?

------------公布答案-------------

在第一个例子🌰打印结果: 1 2 3 4 5

在第二个例子🌰打印结果: 0 0 0 0 0

这个问题实际很蒙人,我们来一起分析一下,第一个类组件中,由于执行上setState没有在react正常的函数执行上下文上执行,而是setTimeout中执行的,批量更新条件被破坏。原理这里我就不讲了,所以可以直接获取到变化后的state。

但是在无状态组件中,似乎没有生效。原因很简单,在class状态中,通过一个实例化的class,去维护组件中的各种状态;但是在function组件中,没有一个状态去保存这些信息,每一次函数上下文执行,所有变量,常量都重新声明,执行完毕,再被垃圾机制回收。所以如上,无论setTimeout执行多少次,都是在当前函数上下文执行,此时num = 0不会变,之后setNumber执行,函数组件重新执行之后,num才变化。

所以, 对于class组件,我们只需要实例化一次,实例中保存了组件的state等状态。对于每一次更新只需要调用render方法就可以。但是在function组件中,每一次更新都是一次新的函数执行,为了保存一些状态,执行一些副作用钩子,react-hooks应运而生,去帮助记录组件的状态,处理一些额外的副作用。

min-hooks模拟源码


let isMount = true;  //是初始状态还是更新状态
let workInProgressHook = null  // 指针当前正在运行的hook
const fiber = {
    // 保存该对应的Hooks链表
    memoizedState: null,
    // 指向App函数
    stateNode: APP
};


function useState (initialState) {
    // 是哪个hook?当前useState对应的hook
    let hookCurrent;
    // 首次渲染(即便是首次渲染一个页面也可能使用多个useState,每一个useState都是一个hook)
    if (isMount) {

        hookCurrent = {
            // 每一个hook 里面的更新可以调用多次updateNum
            queue: {
                pending: null
            },
            // memoizedStateHook 初始值//hooks当前更新的值
            memoizedStateHook: initialState,
            next: null,
            // 指向App函数
            stateNode: APP
        }
        if (!fiber.memoizedState) {
            // 第一次执行时,当前执行片段的fiber的hook只有目前这一个hookValue
            fiber.memoizedState = hookCurrent;
            // 当前hook 指针指向这唯一的这个hookValue
            workInProgressHook = hookCurrent;

        } else {
            // 由于第一也就是上面的if 执行过了所以fiber.memoizedState不再是空
            // 之前的hook和现在当前的hook形成链表(由于目前workInProgressHook只想最后一个hook)
            workInProgressHook.next = hookCurrent;
            // 当前hook 指针指向这个hookValue
            workInProgressHook = hookCurrent;

        }
    } else {
        // 更新时候直接用初始化时候hook
        hookCurrent = workInProgressHook;
        workInProgressHook = workInProgressHook.next;
    }


    let baseState = hookCurrent.memoizedStateHook; // 上一次状态
    // 存在要更新的值
    if (hookCurrent.queue.pending) {
        //第一个要更新的值
        let firstUpdate = hookCurrent.queue.pending.next;

        do {
            const action = firstUpdate.action;
            baseState = action(baseState); // 这是新的值
            firstUpdate = firstUpdate.next;
        } while (firstUpdate !== hookCurrent.queue.pending.next)

        hookCurrent.queue.pending = null;
    }
    hookCurrent.memoizedStateHook = baseState;

    return [baseState, dispatchAction.bind(null, hookCurrent.queue)];
}


function dispatchAction (queue, action) {
    // 创建update  ---每一次更新
    const update = {
        // action更新执行的函数(更改数据)
        action,
        // 与同一个Hook的其他更新形成链表
        next: null
    }

    // 环状单向链表操作
    if (queue.pending === null) {
        update.next = update;
    } else {
        update.next = queue.pending.next;
        queue.pending.next = update;
    }
    queue.pending = update;
    // 模拟React开始调度更新
    schedule();
}

// 首次render时是mount
isMount = true;

function schedule () {
    console.log(fiber.memoizedState, '---')
    // 更新前将workInProgressHook重置为fiber保存的第一个Hook
    workInProgressHook = fiber.memoizedState;
    // 触发组件render
    const app = fiber.stateNode();
    // 组件首次render为mount,以后再触发的更新为update
    isMount = false;
    return app
}


function APP () {
    const [num, updateNum] = useState(0)
    const [tel, updateTel] = useState(0)

    console.log(`${isMount ? 'mount' : 'update'} num: `, num);
    console.log(`${isMount ? 'mount' : 'update'} tel: `, tel);
    return ({
        onclick () {
            updateNum(num => num + 1)
            updateNum(num => num + 1)
            updateNum(num => num + 1)
            updateTel(tel => tel + 1)
            updateTel(tel => tel + 1)
        }
    })
}

window.app = schedule();

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值