2

1. Why use Hooks

Side effects: behaviors such as data acquisition, subscription, scheduled tasks, and manual modification of ReactDOM can be called side effects; the emergence of Hooks can use useEffect to handle these side effects

Complex state management: In the past, third-party state managers such as redux and mobx were usually used to manage complex states, while Hooks can use useReducer and useContext to achieve complex state management;

Development efficiency and quality issues: Functional components are simpler, more efficient, and perform better than class components.

2. useState

const [state, setState] = useState(initState)

2.1 Each render is an independent closure

  • Each render has its own props and state
  • Each render has its own event handler
  • The component function will be called every time it is rendered. The value of the number in each call is a constant, and it is assigned the state value in the current rendering.
  • In a single render, props and state are always the same
 function Counter1() {
    let [info, setInfo] = useState({ number: 0 });
    const alertNumber = () => {
        setTimeout(() => {
            alert(info.number);
        }, 3000);
    }
    return (
        <div>
            <p>{info.number}</p>
            <button onClick={() => setInfo({ number: info.number + 1 })}>+</button>
            <button onClick={alertNumber}>alertNumber</button>
        </div>
    )
}

2.2 Functional update

If the new state needs to be calculated using the previous state, a function can be passed to setState, which will receive the previous state value and return an updated value

 function Counter2() {
    let [info, setInfo] = useState({ number: 0 });
    function lazy() {
        setTimeout(() => {
            setInfo({ number: info.number + 1 })
        }, 2000);
    }
    function lazyFunction() {
        setTimeout(() => {
            setInfo(info => ({ number: info.number + 1 }));
            setInfo(info => ({ number: info.number + 1 }));
        }, 3000);
    }
    return (
        <div>
            <p>{info.number}</p>
            <button onClick={() => setInfo({ number: info.number + 1 })}>+</button>
            <button onClick={lazy}>lazy</button>
            <button onClick={lazyFunction}>lazyFunction</button>
        </div>
    )
}

2.3 Lazy initialization

  • The initState parameter will only work in the initial rendering, subsequent renderings will be ignored
  • If the initial state needs to be obtained through complex calculations, you can pass in a function, and calculate and return the initial state in the function. This function is only called during initial rendering
  • Unlike the setState method of class, useState does not automatically merge and update objects. You can use functional setState combined with the spread operator to achieve the effect of merging objects.
 function Counter3() {
    let [info, setInfo] = useState(function () {
      console.log('初始状态-----')
      return { number: 0, name: "计数器" };
    });
    console.log("Counter5 render");
    return (
      <div>
        <p>
          {info.name}:{info.number}
        </p>
        <button onClick={() => setInfo({ ...info, number: info.number + 1 })}>
          +
        </button>
        <button onClick={() => setInfo(info)}>+</button>
      </div>
    );
}

3. useEffect

useEffect(callback, array) : a hook for handling side effects; it is also componentDidMount()、componentDidUpdate()、componentWillUnmount()、 the unification of these life cycle methods, one tops three!

  • If the second parameter is not written, it will be executed as long as the state changes.
  • When the second parameter is an empty array, no matter which state changes, it will not be executed, only executed once when the component is initialized.
  • When there is a return value in the first callback function, it means that componentWIllUnmount is executed

4. Performance optimization

  • Passing a callback function and an array of dependencies as arguments to useCallback will return a memoized version of the callback function that will only be updated when a dependency changes. (controls when components are updated)
  • Passing a create function and an array of dependencies as arguments to useMemo will only recalculate the memoized value when a dependency changes. This optimization helps avoid expensive calculations on every render. (controls whether the component is updated)
 let lastAddClick;
let lastChangeName;
function Counter4() {
    let [number, setNumber] = useState(0);
    let [name, setName] = useState('zhufeng');
    //会在每次渲染的时候都 会生成一个新的函数
    //只有在依赖的变量发生变化的时候才会重新生成
    const addClick = useCallback(() => setNumber(number + 1), [number]);
    console.log(lastAddClick === addClick);
    lastAddClick = addClick;
    const changeName = useCallback(() => setName(Date.now()), [name]);
    console.log(lastChangeName === changeName);
    lastChangeName = changeName;
    return (
        <div>
            <p>{name}:{number}</p>
            <button onClick={addClick}>addClick</button>
            <button onClick={changeName}>changeName</button>
        </div>
    )
}

5. Hooks rules

Do custom hooks have to start with " **use** "? It must be so. This convention is very important. Without it, React won't be able to automatically check if your Hook violates the rules of hooks, since there's no way to tell if a function contains a call to its internal Hook.

  • Call hooks can only be used in react function components
  • It needs to be called at the top level of the component, and cannot be called in the judgment or loop, because the hook is executed in sequence, and the addition is placed in the judgment. The first call is called, but the second is not called. bug.

Each Hook has two related functions: mountXxx() and updateXxx() , they are the Hook in the Mount stage (that is, the mounting of the component, or the initialization stage, or the When useXxx() is executed for the first time) and the logic of the Update phase (that is, the update of the component, or the component re-rendering phase). In order to facilitate management and invocation, react engineers store the logic of the Hook in the Mount phase into the ( HooksDispatcherOnMount ) object, and store the logic in the Update phase into the ( HooksDispatcherOnUpdate ) object

 const HooksDispatcherOnMount: Dispatcher = {
  readContext,

  useCallback: mountCallback,
  useContext: readContext,
  useEffect: mountEffect,
  useImperativeHandle: mountImperativeHandle,
  useLayoutEffect: mountLayoutEffect,
  useMemo: mountMemo,
  useReducer: mountReducer,
  useRef: mountRef,
  useState: mountState,
  useDebugValue: mountDebugValue,
  useResponder: createDeprecatedResponderListener,
  useDeferredValue: mountDeferredValue,
  useTransition: mountTransition,
};

const HooksDispatcherOnUpdate: Dispatcher = {
  readContext,

  useCallback: updateCallback,
  useContext: readContext,
  useEffect: updateEffect,
  useImperativeHandle: updateImperativeHandle,
  useLayoutEffect: updateLayoutEffect,
  useMemo: updateMemo,
  useReducer: updateReducer,
  useRef: updateRef,
  useState: updateState,
  useDebugValue: updateDebugValue,
  useResponder: createDeprecatedResponderListener,
  useDeferredValue: updateDeferredValue,
  useTransition: updateTransition,
};

What does Hook do in the Mount phase?

Take useState as an example. The logic of useState in the Mount phase is written in the mountState() method:

  1. Get the current Hook node and add the current Hook to the Hook linked list
  2. Initialize the state of the Hook, that is, read the initial state value
  3. Create a new linked list as an update queue to store update operations (setXxx())
  4. Create a dispatch method (that is, the second parameter of the array returned by useState: setXxx()), the purpose of this method is to modify the state, add this update operation to the update queue, and also combine the update with the current The fibers being rendered are bound
  5. A method that returns the current state and modifies the state (dispatch)
 function mountState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {

  // 获取当前 Hook 节点,同时将当前 Hook 添加到 Hook 链表中
  const hook = mountWorkInProgressHook();
  
  // 初始化 Hook 的状态,即读取初始 state 值
  if (typeof initialState === 'function') {
    initialState = initialState();
  }
  hook.memoizedState = hook.baseState = initialState;
  
  // 创建一个新的链表作为更新队列,用来存放更新(setXxx())
  const queue = (hook.queue = {
    pending: null,
    dispatch: null,
    lastRenderedReducer: basicStateReducer,
    lastRenderedState: (initialState: any),
  });
  
  // 创建一个 dispatch 方法(即 useState 返回的数组的第二个参数:setXxx()),
  // 该方法的作用是用来修改 state,并将此更新添加到更新队列中,另外还会将改更新和当前正在渲染的 fiber 绑定起来
  const dispatch: Dispatch<
    BasicStateAction<S>,
  > = (queue.dispatch = (dispatchAction.bind(
    null,
    currentlyRenderingFiber,
    queue,
  ): any));
  
  // 返回当前 state 和 修改 state 的方法
  return [hook.memoizedState, dispatch];
}

Data structure for storing Hook - linked list

All Hooks in a function component are stored in the form of a linked list . Each node in the linked list is a Hook

 export type Hook = {
    memoizedState: any, // Hook 自身维护的状态
    ...
    queue: UpdateQueue<any, any> | null, // Hook 自身维护的更新队列
    next: Hook | null, // next 指向下一个 Hook
};
 const [firstName, setFirstName] = useState('尼古拉斯');
const [lastName, setLastName] = useState('赵四');
useEffect(() => {})

pic_135a5a7bWbVaWb5a7b5ad137U5V9VaU5.png

How useState handles state updates

The update queue linked list queue is used to store update operations. Each node in the linked list is an operation to update the state (that is, calling setXxx() once), so that the latest state can be obtained in the subsequent Update phase.

 const queue = (hook.queue = {
      pending: null,
      dispatch: null,
      lastRenderedReducer: basicStateReducer,
      lastRenderedState: (initialState: any),
});

pending : the latest pending update

dispatch : method to update state (setXxx)

lastRenderedReducer : The reducer used in the last rendering of the component (useState is actually a simplified version of useReducer, the reason why the user does not need to pass in the reducer when using useState is because useState uses react officially written by default reducer: basicStateReducer )

lastRenderedState : The state of the last rendering of the component

img


兰俊秋雨
5.1k 声望3.5k 粉丝

基于大前端端技术的一些探索反思总结及讨论