1

This article is the fifth in a series of in-depth ahoos source code articles, which have been organized into document- address . I think it's not bad, give a star to support it, thanks.

This article explores how ahooks encapsulates some of React's execution "timings"?

Function Component VS Class Component

To learn frameworks like React and Vue, it is necessary to master their life cycle. We need to clearly know the execution order of our code, and execute code of different operations at different stages, such as the need to mount it before going Get the value of dom, otherwise the corresponding value may not be obtained.

Class Component

Students who have used React's Class Component will know that its component life cycle is divided into three states:

  • Mounting: The real DOM has been inserted
  • Updating: is being re-rendered
  • Unmounting: Moved out of the real DOM

The simple version looks like this:

In each state, different methods will be called in sequence, and the corresponding details are as follows (not expanded here):

Details can be found on this official website

As you can see, there are a lot of life cycle methods, and in different versions, the life cycle methods are different.

Function Component

When it comes to Function Component, you will find that there is no direct mention of the concept of life cycle, it is a more thorough state-driven, it has only one state, and React is responsible for rendering the state into the view.

For Function Component, there are only three steps from state to page rendering:

  • input state (prop, state)
  • Execute the component's logic and subscribe to side effects in useEffect/useLayoutEffect
  • Output UI (Dom node)

The focus is on the second step, React subscribes to side effects via useEffect/useLayoutEffect. The life cycle in Class Component can be realized through useEffect/useLayoutEffect. The functions of the two of them are very similar, let's take a look at useEffect here.

Using useEffect is equivalent to telling React that the component needs to do something after rendering, and React will call it after performing a DOM update. React guarantees that every time useEffect is run, the DOM has been updated. This implements the Mounting (mounting phase) in the Class Component.

When the state changes, it can execute the corresponding logic, update the state and render the result to the view, which completes the Updating (update phase) in the Class Component.

Finally, by returning a function in useEffect, it can clean up side effects. Its rules are:

  • The first rendering will not be cleaned up, and the next rendering will clear the previous side effects.
  • The uninstall phase also performs cleanup operations.

By returning a function, we can implement Unmounting in Class Component.

Based on useEffect/useLayoutEffect, ahooks does some encapsulation, which allows you to know the execution time of your code more clearly.

LifeCycle - Lifecycle

useMount

Hook that is only executed when the component is initialized.
If the useEffect dependency is empty, it will only be executed when the component is initialized.

 // 省略部分代码
const useMount = (fn: () => void) => {
  // 省略部分代码
  // 单纯就在 useEffect 基础上封装了一层
  useEffect(() => {
    fn?.();
  }, []);
};

export default useMount;

useUnmount

useUnmount, the Hook executed when the component is unmounted (unmount).

useEffect can implement various side effects after the component is rendered. Some side effects may need to be cleaned up, so you need to return a function that will be executed when the component is unloaded.

 const useUnmount = (fn: () => void) => {
  const fnRef = useLatest(fn);

  useEffect(
    // 在组件卸载(unmount)时执行的 Hook。
    // useEffect 的返回值中执行函数
    () => () => {
      fnRef.current();
    },
    [],
  );
};

export default useUnmount;

useUnmountedRef

Hook to get whether the current component has been uninstalled.

Determine whether the current component has been uninstalled by judging whether the return value in useEffect is executed or not.

 // 获取当前组件是否已经卸载的 Hook。
const useUnmountedRef = () => {
  const unmountedRef = useRef(false);
  useEffect(() => {
    unmountedRef.current = false;
    // 如果已经卸载,则会执行 return 中的逻辑
    return () => {
      unmountedRef.current = true;
    };
  }, []);
  return unmountedRef;
};

export default useUnmountedRef;

Effect

Only the official documents Effect are described here, some of which are timers, anti-shake throttling, etc. We will analyze them in the following series.

useUpdateEffect and useUpdateLayoutEffect

The usage of useUpdateEffect and useUpdateLayoutEffect is the same as useEffect and useLayoutEffect, except that the first execution is ignored and only executed when the dependency is updated.

Implementation idea: Initialize an identifier, which is false at the beginning. Set to true when the first execution is complete. The callback function is executed only when the identifier is true.

 // 忽略首次执行
export const createUpdateEffect: (hook: effectHookType) => effectHookType =
  (hook) => (effect, deps) => {
    const isMounted = useRef(false);

    // for react-refresh
    hook(() => {
      return () => {
        isMounted.current = false;
      };
    }, []);

    hook(() => {
      // 首次执行完时候,设置为 true,从而下次依赖更新的时候可以执行逻辑
      if (!isMounted.current) {
        isMounted.current = true;
      } else {
        return effect();
      }
    }, deps);
  };

useDeepCompareEffect and useDeepCompareLayoutEffect

The usage is the same as useEffect, but deps does a deep comparison via lodash isEqual.

Save the value of the last dependency through useRef, compare it with the current dependency (using lodash's isEqual), and use the comparison result as a dependency of useEffect to determine whether the callback function is executed.

 const depsEqual = (aDeps: DependencyList, bDeps: DependencyList = []) => {
  return isEqual(aDeps, bDeps);
};

const useDeepCompareEffect = (effect: EffectCallback, deps: DependencyList) => {
  // 通过 useRef 保存上一次的依赖的值
  const ref = useRef<DependencyList>();
  const signalRef = useRef<number>(0);

  // 判断最新的依赖和旧的区别
  // 如果相等,则变更 signalRef.current,从而触发 useEffect 中的回调
  if (!depsEqual(deps, ref.current)) {
    ref.current = deps;
    signalRef.current += 1;
  }

  useEffect(effect, [signalRef.current]);
};

useUpdate

useUpdate returns a function, and calling this function forces the component to re-render.

The returned function causes the component to update by changing the state returned by useState.

 import { useCallback, useState } from 'react';

const useUpdate = () => {
  const [, setState] = useState({});
  // 通过设置一个全新的状态,促使 function 组件更新
  return useCallback(() => setState({}), []);
};

export default useUpdate;

Summary and thinking

When we write code, we need to know clearly what the life cycle of components is, and what is the execution order and timing of our code.

In the Function Component, useEffect/useLayoutEffect is used to complete the responsibilities of the Class Components life cycle. ahooks also encapsulates common code execution timing based on these two. Using these hooks can make our code more readable and logically clearer.


Gopal
366 声望77 粉丝