关于源码:React-Hooks源码深度解析

作者:京东批发 郑炳懿

前言

React HooksReact16.8 引入的一个新个性,它容许函数组件中应用state和其余 React 个性,而不用应用类组件。Hooks是一个十分重要的概念,因为它们提供了更简略、更易于了解的React开发体验。

React Hooks的外围源码次要包含两个局部:React外部的Hook管理器和一系列预置的Hook函数

首先,让咱们看一下React外部的Hook管理器。这个管理器是React外部的一个重要机制,它负责管理组件中的所有Hook,并确保它们在组件渲染期间以正确的顺序调用。

外部Hook管理器

示例:

const Hook = {
  queue: [],
  current: null,
};

function useState(initialState) {
  const state = Hook.current[Hook.queue.length];
  if (!state) {
    Hook.queue.push({
      state: typeof initialState === 'function' ? initialState() : initialState,
      setState(value) {
        this.state = value;
        render();
      },
    });
  }
  return [state.state, state.setState.bind(state)];
}

function useHook(callback) {
  Hook.current = {
    __proto__: Hook.current,
  };
  try {
    callback();
  } finally {
    Hook.current = Hook.current.__proto__;
  }
}

function render() {
  useHook(() => {
    const [count, setCount] = useState(0);
    console.log('count:', count);
    setTimeout(() => {
      setCount(count + 1);
    }, 1000);
  });
}

render();

在这个示例中,Hook对象有两个重要属性:queuecurrentqueue存储组件中所有Hook的状态和更新函数,current存储以后正在渲染的组件的Hook链表。useStateuseHook函数则别离负责创立新的Hook状态和在组件中应用Hook

预置 Hook 函数

useState Hook

以下是useState Hook的实现示例:

function useState(initialState) {
  const hook = updateWorkInProgressHook();
  if (!hook.memoizedState) {
    hook.memoizedState = [
      typeof initialState === 'function' ? initialState() : initialState,
      action => {
        hook.queue.pending = true;
        hook.queue.dispatch = action;
        scheduleWork();
      },
    ];
  }
  return hook.memoizedState;
}

上述代码实现了useState Hook,其次要作用是返回一个state和更新函数的数组,state 初始值为initialState

在这个实现中,updateWorkInProgressHook()函数用来获取以后正在执行的函数组件的 fiber 对象并判断是否存在对应的hook。它的实现如下:

function updateWorkInProgressHook() {
  const fiber = getWorkInProgressFiber();
  let hook = fiber.memoizedState;
  if (hook) {
    fiber.memoizedState = hook.next;
    hook.next = null;
  } else {
    hook = {
      memoizedState: null,
      queue: {
        pending: null,
        dispatch: null,
        last: null,
      },
      next: null,
    };
  }
  workInProgressHook = hook;
  return hook;
}

getWorkInProgressFiber()函数用来获取以后正在执行的函数组件的fiber对象,workInProgressHook则用来存储以后正在执行的hook对象。在函数组件中,每一个useState调用都会创立一个新的 hook 对象,并将其增加到fiber对象的hooks链表中。这个hooks链表是通过fiber对象的memoizedState属性来保护的。

咱们还须要留神到在useState Hook的实现中,每一个hook对象都蕴含了一个queue对象,用来存储待更新的状态以及更新函数。scheduleWork()函数则用来告诉React调度器有工作须要执行。

React的源码中,useState函数实际上是一个叫做useStateImpl的外部函数。

上面是useStateImpl的源码:

function useStateImpl<S>(initialState: (() => S) | S): [S, Dispatch<SetStateAction<S>>] {
  const dispatcher = resolveDispatcher();
  return dispatcher.useState(initialState);
}

能够看到,useStateImpl函数的作用就是获取以后的dispatcher并调用它的useState办法,返回一个数组,第一个元素是状态的值,第二个元素是一个dispatch函数,用来更新状态。这里的resolveDispatcher函数用来获取以后的dispatcher,其实现如下:

function resolveDispatcher(): Dispatcher {
  const dispatcher = currentlyRenderingFiber?.dispatcher;
  if (dispatcher === undefined) {
    throw new Error('Hooks can only be called inside the body of a function component. (https://fb.me/react-invalid-hook-call)');
  }
  return dispatcher;
}

resolveDispatcher函数首先尝试获取以后正在渲染的fiber对象的dispatcher属性,如果获取不到则说

明以后不在组件的渲染过程中,就会抛出一个谬误。

最初,咱们来看一下useState办法在具体的dispatcher实现中是如何实现的。咱们以useReducer

dispatcher为例,它的实现如下:

export function useReducer<S, A>(
  reducer: (prevState: S, action: A) => S,
  initialState: S,
  initialAction?: A,
): [S, Dispatch<A>] {
  const [dispatch, currentState] = updateReducer<S, A>(
    reducer,
    // $FlowFixMe: Flow doesn't like mixed types
    [initialState, initialAction],
    // $FlowFixMe: Flow doesn't like mixed types
    reducer === basicStateReducer ? basicStateReducer : updateStateReducer,
  );
  return [currentState, dispatch];
}

能够看到,useReducer办法实际上是调用了一个叫做updateReducer的函数,返回了一个蕴含以后状态和dispatch函数的数组。updateReducer的实现比较复杂,波及到了很多细节,这里不再开展介绍。

useEffect Hook

useEffectReact中罕用的一个Hook函数,用于在组件中执行副作用操作,例如拜访近程数据、增加/移除事件监听器、手动操作DOM等等。useEffect的外围性能是在组件的渲染过程完结之后异步执行回调函数,它的实现形式波及到 React 中的异步渲染机制。

以下是useEffect Hook的实现示例:

function useEffect(callback, dependencies) {
  // 通过调用 useLayoutEffect 或者 useEffect 办法来获取以后的渲染批次
  const batch = useContext(BatchContext);

  // 依据以后的渲染批次判断是否须要执行回调函数
  if (shouldFireEffect(batch, dependencies)) {
    callback();
  }

  // 在组件被卸载时革除以后 effect 的状态信息
  return () => clearEffect(batch);
}

在这个示例中,useEffect接管两个参数:回调函数和依赖项数组。当依赖项数组中的任何一个值发生变化时,

React会在下一次渲染时从新执行useEffect中传入的回调函数。

useEffect函数的实现形式次要依赖于React中的异步渲染机制。当一个组件须要从新渲染时,React会将所有的state更新操作退出到一个队列中,在以后渲染批次完结之后再异步执行这些更新操作,从而防止在同一个渲染批次中间断执行屡次更新操作。

useEffect函数中,咱们通过调用useContext(BatchContext)办法来获取以后的渲染批次,并依据shouldFireEffect办法判断是否须要执行回调函数。在回调函数执行结束后,咱们须要通过clearEffect办法来革除以后effect的状态信息,防止对后续的渲染批次产生影响。

总结

总的来说,React Hooks的实现原理并不简单,它次要依赖于React外部的fiber数据结构和调度零碎,通过这些机制来实现对组件状态的治理和更新。Hooks可能让咱们在函数组件中应用状态和其余React个性,使得函数组件的性能能够和类组件媲美。

除了useStateuseEffecthookReact还有useContext等罕用的Hook。它们的实现原理也根本类似,都是利用fiber架构来实现状态治理和生命周期钩子等性能。

以上是hook简略实现示例,它们并不是React中理论应用的代码,然而能够帮忙咱们更好地了解hook的外围实现形式。

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理