Interpretación del código fuente y los principios de React (11): el principio de los ganchos

Escrito al principio de la columna (armadura de pila)

  1. El autor no es un experto en tecnología front-end, sino solo un novato en tecnología front-end al que le gusta aprender cosas nuevas. Quiere aprender código fuente solo para hacer frente al mercado front-end en rápido declive y la necesidad de encontrar un trabajo. Esta columna es el propio pensamiento del autor en el proceso de aprendizaje y experiencia, también hay muchas partes que hacen referencia a otros tutoriales. Si hay errores o problemas, indíquelos al autor. El autor garantiza que el contenido es 100 % correcto. No utilice esta columna como respuesta de referencia.

  2. La lectura de esta columna requiere que tenga una cierta base de React, JavaScript e ingeniería front-end. El autor no explicará muchos puntos básicos de conocimiento, como: qué es babel, cuál es la sintaxis de jsx, consúltelo cuando sea necesario material.

  3. Muchas partes de esta columna hacen referencia a una gran cantidad de otros tutoriales. Si hay similitudes, el autor los plagió. Por lo tanto, este tutorial es completamente de código abierto. Puede integrar, resumir y agregar su propia comprensión a varios tutoriales como autor.

Contenido de esta sección

Este capítulo es relativamente independiente de la parte anterior. Hablaremos sobre la nueva función Hooks que actualizamos en React 16.8 . Todos sabemos que Hooks se propone principalmente para resolver el problema de estado de los componentes de funciones. Antes de que nuestros componentes de clase tuvieran estado, pero la función los componentes no, por lo que para resolver este problema nacieron los ganchos. Entonces, para que nuestros ganchos cuelguen en todo el ciclo de vida de React, ¿cómo los maneja React? Echemos un vistazo a esta parte:

Definición de anzuelos

Primero, echemos un vistazo al código de los ganchos. Están almacenados en /react/blob/main/packages/react/src/ReactHooks.js. Todos usan para resolveDispatcherinicializar uno dispatcher y luego llaman al método dispatcher correspondiente para realizar su lógica. .inresolveDispatcher dispatcher se ReactCurrentDispatcher.currentobtiene de nuevo a partir de:

function resolveDispatcher() {
  const dispatcher = ReactCurrentDispatcher.current;
  return ((dispatcher: any): Dispatcher);
}

export function useContext<T>(Context: ReactContext<T>): T {
  const dispatcher = resolveDispatcher();
  if (__DEV__) {
  //....省略
  }
  return dispatcher.useContext(Context);
}

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

Volver a renderConganchos

Acerca de ReactCurrentDispatcher.current, debemos volver al lugar donde aparecían por primera vez las operaciones relacionadas con ganchos, es decir, nuestra renderWithHooksfunción , omitimos el contenido relevante en ese momento, ahora debemos volver atrás y hablar sobre esta parte del contenido:

En primer lugar, vemos el comienzo de renderWithHooksla función current === null. Decidimos si usamos el hooksDispatcher inicializado o el hooksDispatcher actualizado según el criterio de. Estos dos hooks son el Dispatcher que acabamos de ver en la función. Las funciones de operación de varios hooks usar

export function renderWithHooks<Props, SecondArg>(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: (p: Props, arg: SecondArg) => any,
  props: Props,
  secondArg: SecondArg,
  nextRenderLanes: Lanes,
): any {
    
    
  // 获取当前函数组件对应的 fiber,并且初始化
  currentlyRenderingFiber = workInProgress;
  workInProgress.memoizedState = null;
  workInProgress.updateQueue = null;


  if (__DEV__) {
    
    
    // ....
  } else {
    
    
    // 根据当前阶段决定是初始化hook,还是更新hook
    ReactCurrentDispatcher.current =
      current === null || current.memoizedState === null
        ? HooksDispatcherOnMount
        : HooksDispatcherOnUpdate;
  }
  // 调用函数组件
  let children = Component(props, secondArg);
    
  // 重置hook链表指针
  currentHook = null;
  workInProgressHook = null;
    
  return children;
}

const HooksDispatcherOnMount: Dispatcher = {
    
    
  ...
  useCallback: mountCallback,
  useContext: readContext,
  useEffect: mountEffect,
  useImperativeHandle: mountImperativeHandle,
  useLayoutEffect: mountLayoutEffect,
  useMemo: mountMemo,
  useReducer: mountReducer,
  useRef: mountRef,
  useState: mountState,
  ...
};

const HooksDispatcherOnUpdate: Dispatcher = {
    
    
  ...
  useCallback: updateCallback,
  useContext: readContext,
  useEffect: updateEffect,
  useImperativeHandle: updateImperativeHandle,
  useLayoutEffect: updateLayoutEffect,
  useMemo: updateMemo,
  useReducer: updateReducer,
  useRef: updateRef,
  useState: updateState,
  ...
};

Montar ganchos durante la inicialización

Luego miramos nuestra lógica de código basada en las dos fases que acabamos de tener, Montar y Actualizar. Primero, montamos nuestros ganchos durante el Montaje. Al observar las funciones en cada uno, podemos ver que todos usan un Echemos un vistazo aHooksDispatcherOnMount la lógica de esta función:mountWorkInProgressHook

  • En primer lugar, crea un enlace. Su estructura de datos se ha escrito claramente en los comentarios. Cada tipo de enlace necesita almacenar diferentes valores. Hablaremos de esto en detalle más adelante, y también almacena la cola de actualización de nuestros enlaces, que es similar a lo que dijimos anteriormente. La cola de actualización de un artículo es consistente, por lo que no lo repetiré

  • workInProgressHookEs la lista enlazada de ganchos en este componente de función. Los ganchos en un componente de función se almacenan juntos en forma de lista enlazada, y esta lista enlazada se almacenará currentlyRenderingFiberen FibramemoizedStatecurrentlyRenderingFiber

function mountState(initialState) {
    
    
  const hook = mountWorkInProgressHook();
  ...
}
  
function mountWorkInProgressHook(): Hook {
    
    
  const hook: Hook = {
    
    
    memoizedState: null, // 上次渲染时所用的 state
    baseState: null,  // 已处理的 update 计算出的 state
    baseQueue: null,  // 未处理的 update 队列(上一轮没有处理完成的)
    queue: null,      // 当前的 update 队列
    next: null, // 指向下一个hook
  };

  // 保存到链表中
  if (workInProgressHook === null) {
    
    
    currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
  } else {
    
    
    workInProgressHook = workInProgressHook.next = hook;
  }
  return workInProgressHook;
}

Ganchos al actualizar

Después de leer los ganchos al montar, echemos un vistazo a la actualización, todavía solo miramos la parte pública:

  • En primer lugar, sabemos que nuestro React tiene dos árboles, uno es el árbol actual para representar el árbol que se muestra actualmente, y el otro es el árbol WorkInProgress para representar el árbol de construcción actual, por lo que nuestra lista de ganchos vinculados también se divide en dos ganchos actuales. y workInProgressHook
  • Ahora, cuando queremos actualizar un gancho, primero usamos nextCurrentHook y nextWorkInProgressHook para identificar los próximos ganchos que deben operarse. Si existe nextWorkInProgressHook, podemos usarlo directamente; de ​​lo contrario, necesitamos clonar nuestro gancho de nuestro nextCurrentHook y ponerlo en él.
function updateWorkInProgressHook(): Hook {
    
    

  // 获取 current 树上的 Fiber 的 hook 链表
  let nextCurrentHook: null | Hook;
  if (currentHook === null) {
    
    
    const current = currentlyRenderingFiber.alternate;
    if (current !== null) {
    
    
      nextCurrentHook = current.memoizedState;
    } else {
    
    
      nextCurrentHook = null;
    }
  } else {
    
    
    nextCurrentHook = currentHook.next;
  }

  // workInProgress 树上的 Fiber 的 hook 链表
  let nextWorkInProgressHook: null | Hook;
  if (workInProgressHook === null) {
    
    
    nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;
  } else {
    
    
    nextWorkInProgressHook = workInProgressHook.next;
  }
    
  // 如果 nextWorkInProgressHook 不为空,直接使用
  if (nextWorkInProgressHook !== null) {
    
    
    workInProgressHook = nextWorkInProgressHook;
    nextWorkInProgressHook = workInProgressHook.next;
    currentHook = nextCurrentHook;
  } else {
    
    
    //否则我们克隆一份 hooks
    currentHook = nextCurrentHook;
    const newHook: Hook = {
    
    
      memoizedState: currentHook.memoizedState,
      baseState: currentHook.baseState,
      baseQueue: currentHook.baseQueue,
      queue: currentHook.queue,
      next: null,
    };
    if (workInProgressHook === null) {
    
    
      currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
    } else {
    
    
      workInProgressHook = workInProgressHook.next = newHook;
    }
  }
  return workInProgressHook;
}

Resumen y expansión y preguntas clásicas de la entrevista.

De acuerdo con la función anterior, también podemos ver que cuando actualizamos un gancho, necesitamos recorrer su árbol Actual y la lista enlazada de ganchos del árbol WorkInProgress, por lo que el orden de los ganchos identificados por sus dos listas enlazadas debe ser consistente, lo cual también conduce a una pregunta común de la entrevista: ¿ por qué necesita colocar la función de gancho en el nivel superior del componente de función?

Imaginemos una situación como esta: si se genera un gancho en una condición de bucle o una instrucción if, cuando montamos y cuando actualizamos, el entorno de ejecución de los dos tiempos es diferente, lo que resulta en diferentes ganchos o diferentes órdenes generadas dos veces, pero aún recorremos nuestras dos listas enlazadas en el orden que esperábamos, y el resultado final es que reutilizamos por error otro enlace como el enlace que necesitamos reutilizar actualmente, lo que genera resultados impredecibles. Un ejemplo es el siguiente:

Configuramos dos useStatey que nuestro primer gancho no se ejecute después de ejecutarlo una vez

let isMount = false;
if(!isMount) {
    
    
    const [num, setNum] = useState('value1'); // useState1
    isMount = true;
}
const [num2, setNum2] = useState('value2'); // useState2

Luego, la lista enlazada que obtenemos cuando llamamos por primera vez, es decir, el montaje es: useState1 – useState2

Obviamente, nuestra lista enlazada al actualizar debería ser useState2

Pero de acuerdo con nuestro código anterior, cuando actualizamos, necesitamos reutilizar un enlace como nuestro useState2.En este momento, nuestro nextCurrentHook apunta al primer enlace de la lista vinculada, que es useState1

Al final, toda la lógica está desordenada, por lo que debemos tener en cuenta que la función de enlace debe en el nivel superior del componente de la función, y los enlaces no se pueden llamar en bucles, condiciones o funciones anidadas.

El uso y seguimiento de anzuelos

Ahora conocemos la lógica de nuestros ganchos. En los próximos artículos, espero explicar los principios de algunos ganchos de uso común y el análisis del código fuente, que incluye aproximadamente:

  • Los useState y useEffect más utilizados
  • Optimización del rendimiento useMemo y useCallback
  • Obtener elemento useRef

También puede incluir algún otro contenido y ganchos personalizados, puede seguir prestando atención

Supongo que te gusta

Origin blog.csdn.net/weixin_46463785/article/details/130566356
Recomendado
Clasificación