初识React Hooks原理

本文深入探讨React Hooks的 useState 和 useEffect 的工作原理。通过简单的实现展示了如何使用数组和指针来模拟这两个核心Hooks的功能,解释了它们如何管理状态和副作用。此外,还对比了轻量级实现与官方实现的区别,并提到了useReducer作为useState的替代选项。文章总结了Hooks如何使函数组件具备类组件的状态管理能力,并提供了面向函数编程的封装性思考。
摘要由CSDN通过智能技术生成

初识React Hooks原理

前言

记录一下在学习React Hooks原理过程中的心得。

一、useState原理

useState函数接受一个初识状态,返回一个元组,包含当前状态和设置状态的函数。

其原理是通过使用数组保存按顺序调用useState得到的状态。主要依靠cursor指针。在第一次初始化时按顺序将state存入数组中,每存一个cursor++。当调用setState后,将cursor = 0,并调用render函数。于是当前组件函数会按顺序重新执行useState函数,然后得到对应的state,这也是为什么只能在函数顶层调用hooks的原因。

简单实现:

function render() {
  ReactDOM.render(<App />, document.getElementById("root"));
}

let state: any;

function useState<T>(initialState: T): [T, (newState: T) => void] {
  state = state || initialState;

  function setState(newState: T) {
    state = newState;
    render();
  }

  return [state, setState];
}

render(); // 首次渲染

过程图
在这里插入图片描述

二、useEffect原理

和useState原理相似,还是使用Array + cursor来实现。

下面是一个不包括销毁副作用功能的 useEffect 的 TypeScript 实现

// 还是利用 Array + Cursor的思路
const allDeps: any[][] = [];
let effectCursor: number = 0;

function useEffect(callback: () => void, deps: any[]) {
  if (!allDeps[effectCursor]) {
    // 初次渲染:赋值 + 调用回调函数
    allDeps[effectCursor] = deps;
    ++effectCursor;
    callback();
    return;
  }

  const currenEffectCursor = effectCursor;
  const rawDeps = allDeps[currenEffectCursor];
  // 检测依赖项是否发生变化,发生变化需要重新render
  const isChanged = rawDeps.some(
    (dep: any, index: number) => dep !== deps[index]
  );
  if (isChanged) {
    callback();
    allDeps[effectCursor] = deps; 
  }
  ++effectCursor;
}

function render() {
  ReactDOM.render(<App />, document.getElementById("root"));
  effectCursor = 0; // 注意将 effectCursor 重置为0
}

!补充

以上的方法是轻量级react hook 实现的方法,官方的方法是使用链表来实现的

/**
 * 简单实现useSate
 * 
 */
// 记录是否挂载
let isMount = true;
// 记录当前hook
let workInProgressHook;

// fiber对象
const fiber = {
    memorizedState: null,
    targetApp: App
};

function schedule() {
    // 执行后,workInProgressHook应该是第一个hook
    workInProgressHook = fiber.memorizedState;
    // 每次调用该函数返回app执行结果
    const app = fiber.targetApp;
    isMount = false;
    return app();
}

// 每次调用dispatchAction就是为了调用action,更新state
function dispatchAction(queue, action) {
    const update = {
        action,
        next: null
    };
    // 若queue.pending为空=>还没有update,把当前action添加进去
    if (!queue.pending) {
        update.next = update;
    } else { // 若不为空=>添加到环形链表最前面
        update.next = queue.pending.next;
        queue.pending.next = update;
    }
    queue.pending = update;
    // 调用schedule进行组件render,在下次render的时候在useState中计算获取最新的state
    schedule();
}

function useState(initialState) {
    let hook;

    // 若是挂载,创建新hook对象,挂载到fiber对象的hooks链表中
    if (isMount) {
        hook = {
            queue: {
                pending: null
            },
            state: initialState,
            next: null
        };
        if (!fiber.memorizedState) {
            fiber.memorizedState = hook;
        } else {
            workInProgressHook.next = hook;
        }
        workInProgressHook = hook;
        
    } else { // 若是render,要拿到对应顺序的hook,依靠workInProgressHook指针
        hook = workInProgressHook;
        workInProgressHook = workInProgressHook.next;
    }

    let baseState = hook.state;
    // 拿到hook后,如果有update对象了,就要执行完,得到最新的state
    if (hook.queue.pending) {
        let firstUpdate = hook.queue.pending.next;

        do {
            baseState = firstUpdate.action(baseState);
            firstUpdate = firstUpdate.next;
        } while (firstUpdate !== hook.queue.pending);
        hook.queue.pending = null;
    }
    hook.state = baseState;
    // 返回可以执行所有update对象的函数
    return [baseState, dispatchAction.bind(null, hook.queue)];
}

function App() {
    const [num, updateNum] = useState(0);
    const [str, updateStr] = useState("0");

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

const app = App();
app.click();

二、useReducer

useReducer是useState的代替用法const [state, dispatch] = useReducer(reducer, initialArg, init);
useState的原理调用useReducer,只是在mount阶段,useStatelastRenderedReducerbasicStateReducer

function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
  return typeof action === 'function' ? action(state) : action;
}

区别就是,useReducer的dispatch中会使用自定义的reducer,而useState会使用basicStateReducer

React技术揭秘

对hooks功能的总结

  • 相比类组件,函数组件是无状态组件,使用hooks可以让函数组件拥有和类组件相似的状态功能。
  • 在类组件中,每个组件有很强的封装性,有自己的生命周期函数。而函数组件可以通过hooks来实现相似的生命周期函数。
  • 但是hooks还是和函数组件有一定割裂感,并不是像类组件一样有很强的封装性。如:在不使用hooks时函数组件就是一个普通函数,这也体现了其面向函数编程的思想。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值