Hook profile

Hook is a new feature in React 16.8. It lets you use state and other React features without having to write a class. Take a look at two examples of Hook usage provided in the official documentation.

  1. State Hook

    import React, { useState } from 'react';
    
    function Example() {
      // useState is a hook that declares a state variable called "count" with an initial value of 0. A function that returns the current value and changed value.
      const [count, setCount] = useState(0);
    
      return (
        <div>
          <p>You clicked {count} times</p>
          <button onClick={()= > setCount(count + 1)}>
            Click me
          </button>
        </div>
      );
    }
    Copy the code
  2. Effect Hook

    import React, { useState, useEffect } from 'react'; function Example() { const [count, setCount] = useState(0); // Similar to componentDidMount and componentDidUpdate: Document. title = 'You clicked ${count} times'; useEffect() => {document.title =' You clicked ${count} times'; }); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); }Copy the code

Hook to solve the problem

  1. It is difficult to reuse state logic between components.
  2. The state and side effect code dispersion of logic within complex components is difficult to understand.
  3. Class components are difficult to understand and not conducive to tool optimization.

Introduce Hook influence

  1. Optional: Use incrementally without rewriting existing code, breaking existing structures, or replacing existing React concepts.
  2. Compatibility: 100% backward compatibility, no destructive changes.
  3. Available since V16.8.0.

Use Hook limitation and detection tools

Limitations:

  1. Use hooks only at the top level; do not call hooks in loops, conditions, or nested functions
  2. Only call the React function component and custom hooks, not normal JavaScript functions

React provides an elint plugin, eslint-plugin-react-hooks. Use this plugin to check that hooks are used correctly.

  1. react-hooks/rules-of-hooks: Check the Hook rules
  2. react-hooks/exhaustive-deps: Check for effect dependencies

Use of built-in hooks

Built-in 10 hooks are as follows:

  1. useState: used to declare and update component state.
  2. userEffect: used to pass in a function to perform side effects such as Dom changes, which is executed after each rendering of the component.
  3. userContext: Used to receive the upper layerContextObject and return the current value.
  4. useReducer: a simple redux for handling complex state of components.
  5. useCallbackMemoized: Used to return a memoized callback function, with the function reference returned unchanged if the dependency is not changed. Often used for event binding or function props passing.
  6. userMemoReturns an memoized value that is directly used by the last calculated value without re-executing the function if the dependency is not changed.
  7. userRef: Returns a ref object, which can be thought of as an instance variable of the component. Rerendering is not triggered when the value of ref.current changes.
  8. userImperativeHandle: in conjunction with ref, exposes instance methods and values to the parent component.
  9. userLayoutEffectBasically equivalent touserEffect, but it is executed before drawing.
  10. userDebugValueFor custom Hook debugging, the React developer tool displays the tag for custom hooks.

The detailed usage of each Hook is described below.

0. Related types

The following are the type definitions for the built-in Hook function definition dependencies.

type Dispatch<A> = A => void;
type BasicStateAction<S> = (S => S) | S;
Copy the code

1. useState

define

useState<S>(
	initialState: (() => S) | S
): [S, Dispatch<BasicStateAction<S>>]
Copy the code

describe

Declare a state and set the initial value, return a state value and a function to update the state.

The sample

import React, { useState } from "react";

export default function App({ initialCount = 0 }) {
  const [count, setCount] = useState(initialCount);
  return (
    <>
      Count: {count}
      <button onClick={() => setCount(count + 1)}>+</button>
    </>
  );
}
Copy the code

points

  1. UseState returns an array of current state and update state functions, named by destruct.
  2. UseState takes a function that lazily evaluates the initial value and skips it when not rendered for the first time. Eg:useState(computedFunction)}.
  3. SetCount updates behave by overwriting the current value rather than merging to the current value.
  4. SetCount can take a function that takes the previous state and returns the updated value. Eg:setCount(prevCount => prevCount + 1)}.

2. userEffect

define

useEffect(
  create: () => (() => void) | void,
  deps: Array<mixed> | void | null,
): void
Copy the code

describe

The side effects are done using the userEffect pass-in function, which is executed after each rendering of the component.

The sample

import React, { useState, useEffect } from 'react';

export default function App() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  }, [count]);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
Copy the code

note

  1. Support for returning a function to remove side effects, side effects and side effect removal functions are executed after each render. To do this before rendering, see useLayoutEffect.
  2. Support for passing in an array of side effect function dependencies with the second argument, bypassside processing if the dependencies remain unchanged.
  3. If you want to dynamically decide whether to execute based on a condition, you need to put the condition in the side effect function.

3. userContext

define

useContext<T>(context: ReactContext<T>): T
Copy the code

use

Used to accept the upper Context object and return the current value.

The sample

import React, { useContext } from "react";

const Context = React.createContext();

function Counter() {
  const count = useContext(Context);
  return <div>Current: {count}</div>;
}

export default function App() {
  return (
    <Context.Provider value={1}>
      <Counter />
    </Context.Provider>
  );
};
Copy the code

note

  1. The Context must be used together with context. Provider in upper-layer components.

4. useReducer

define

useReducer<S, I, A>( reducer: (S, A) => S, initialArg: I, init? : I => S, ): [S, Dispatch<A>]Copy the code

use

A simple redux for handling complex state of components.

The sample

import React, { useReducer } from "react";

const initialState = { count: 0 };

function reducer(state, action) {
  switch (action.type) {
    case "increment":
      return { count: state.count + 1 };
    case "decrement":
      return { count: state.count - 1 };
    default:
      throw new Error();
  }
}

export default function App() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({ type: "decrement" })}>-</button>
      <button onClick={() => dispatch({ type: "increment" })}>+</button>
    </>
  );
}
Copy the code

note

  1. Accept the second parameterinitialArg Is the initial value. If the initial value requires a lot of calculation, you can pass in the third parameterinitFunction to initialize.

5. useCallback

define

useCallback<T>(callback: T, deps: Array<any> | void | null): T
Copy the code

use

Used to return a memoized callback function, with the function reference returned unchanged if the dependency is not changed. Often used for event binding or function props passing.

The sample

import React, { useState, useCallback } from "react"; class Button extends React.PureComponent { render() { console.log("rendered ", this.props.text); return <button onClick={this.props.onClick}>{this.props.text}</button>; } } export default function App() { const [count1, setCount1] = useState(0); const memoizedOnClick = useCallback(function onClick() { setCount1((count1) => count1 + 1); } []); const [count2, setCount2] = useState(0); const onClick = function onClick() { setCount2((count2) => count2 + 1); }; Return (<> {count1} <Button text="button1" onClick={memoizedOnClick} /> {count2}) <Button text="button2" onClick={onClick} /> </> ); }Copy the code

note

  1. useCallback(fn, deps)The equivalent ofuseMemo(() => fn, deps).

6. userMemo

define

useMemo<T>(create: () => T, deps: Array<any> | void | null): T
Copy the code

use

Returns a memoized value that does not re-execute the function directly using the last calculated value if the dependency is not changed

The sample

import React, { useState, useMemo } from "react"; export default function App() { const [count1, setCount1] = useState(2); Const result = useMemo(function () {console.log("computed pow"); return Math.pow(count1, 2); }, [count1] ); const [count2, setCount2] = useState(2); return ( <> pow: {result} <br /> count2: {count2} <br /> <button onClick={() => setCount1(count1 + 1)}>button1</button> <button onClick={() => setCount2(count2 +  1)}>button2</button> </> ); }Copy the code

7. userRef

define

useRef<T>(initialValue: T): {current: T}
Copy the code

use

Returns a ref object, which can be thought of as an instance variable of the component, and does not trigger rerendering when the value of ref.current changes

The sample

Import React, {useRef} from "React "; export default function TextInputWithFocusButton() { const inputEl = useRef(null); Const onButtonClick = () => {// Get the DOM node inputel.current.focus (); }; return ( <> <input ref={inputEl} type="text" /> <button onClick={onButtonClick}>Focus the input</button> </> ); }Copy the code
Import React, {useEffect, useRef, useState} from "React "; function Counter() { const [count, setCount] = useState(0); // intervalId for component instance variables let intervalId = useRef(null); useEffect(() => { intervalId.current = setInterval(() => { setCount((c) => c + 1); }, 1000); console.log("create ", intervalId.current); return () => { console.log("clear ", intervalId.current); clearInterval(intervalId.current); }; } []); Return (<> count: {count}; Intervali.current: {intervali.current} <button onClick={() => {// example 3: ClearInterval (Intervali.current) will not be re-rendered if you change the value of intervalId after clicking Stop. intervalId.current = Math.random(); }} > stop </button> </> ); } export default function App() { const [visible, setVisible] = useState(true); return ( <> {visible && <Counter />} <button onClick={() => setVisible((v) => ! v)}>Toggle</button> </> ); }Copy the code

note

  1. Can be used to get dom nodes
  2. Can be used with component instance variables
  3. The ref.current update does not cause component re-rendering

8. userImperativeHandle

define

useImperativeHandle<T>(
  ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
  create: () => T,
  deps: Array<any> | void | null,
): void
Copy the code

use

Expose instance methods and values to the parent component in conjunction with ref

The sample

import React, { forwardRef, useRef, useState, useImperativeHandle } from "react"; const Counter = forwardRef(function Counter(props, ref) { const [count, setCount] = useState(0); UseImperativeHandle (ref, () => (increment: () => {increment: ()) setCount((c) => c + 1); }})); return <>count: {count}</>; }); export default function App() { const counterRef = useRef(); return ( <> <Counter ref={counterRef} /> <button onClick={() => counterRef.current.increment()}>increment</button> </> ); }Copy the code

note

  1. Avoid using
  2. Should be used with the forwardRef

9. userLayoutEffect

define

useLayoutEffect(
  create: () => (() => void) | void,
  deps: Array<any> | void | null,
): void
Copy the code

use

Basically the same as userEffect, except that it is executed after a DOM change and before drawing

The sample

// styles.css
.container {
  background: green;
  height: 200px;
  position: relative;
  margin-bottom: 10px;
}
.inner {
  display: block;
  width: 130px;
  height: 50px;
  background: red;
  position: absolute;
  left: 0;
  top: 0;
  transition: all 3s;
}
.active {
  left: 100px;
}
Copy the code
// App.js import React, { useLayoutEffect, useRef, useEffect } from "react"; import "./styles.css"; export default function App() { const ref1 = useRef(); const ref2 = useRef(); UseLayoutEffect (() => {// 1. Ref1.current. ClassName = "inner active"; ref1.current. } []); UseEffect (() => {// 1. 3. Execute useEffect to add class active // 4. Ref2.current. ClassName = "inner active"; } []); return ( <div> <div className="container"> <div ref={ref1} className="inner"> useLayoutEffect </div> </div> <div className="container"> <div ref={ref2} className="inner"> useEffect </div> </div> </div> ); }Copy the code

10. userDebugValue

define

useDebugValue<T>(value: T, formatterFn: ? (value: T) => any): voidCopy the code

use

For custom Hook debugging, the React developer tool displays the tag for custom hooks.

The sample

import React, { useDebugValue, useState } from "react"; function useFriendStatus() { const [isOnline] = useState("1"); "FriendStatus: Online" useDebugValue(isOnline? "Online" : "Offline"); return isOnline; } export default function App() { const isOnline = useFriendStatus(); return <>isOnline: {isOnline}</>; }Copy the code

note

  1. Accepts a formatting function as an optional second argument. This function is called only when the Hook is checked. It takes a debug value as an argument and returns a formatted display value. eg:useDebugValue(date, date => date.toDateString());

Customize the Hook

  1. A custom Hook is a normal function that calls a Hook. It is highly recommended that the name begin with “use”. This is a non-mandatory convention, which does not affect function operation, but affects reading experience and tool recognition and detection Hook rules.
  2. Other built-in or custom hooks can be called from within a function, again observing the limitations of hooks such as only being called at the top level.
  3. By custom Hook, component logic can be extracted into a separate function for reuse. If the purpose is not to reuse component logic and no Hook is called, do not name the function with the beginning of “use”.

Here is an example of a custom Hook that reuses formatting and updating logic over time for chat and comment message delivery times.

import React, { useRef, useEffect, useState } from "react"; function formatTime(time) { const now = +new Date(); const offsetSeconds = Math.round((now - time) / 1000); const Minute = 60; const Hour = Minute * 60; const Day = Hour * 24; If (offsetSeconds < Minute) {return '${offsetSeconds} before'; } else if (offsetSeconds < Hour) {return '${math. round(offsetSeconds/Minute)}'; } else if (offsetSeconds < Day) {return '${math.round (offsetSeconds/Hour)} Hour ago'; } else {return '${math.round (offsetSeconds/Day)} days ago'; } } function useFormatMessageTime(arrivedTime) { const [formattedTime, setFormattedTime] = useState(() => formatTime(arrivedTime) ); const ref = useRef(); useEffect(() => { console.log("setInterval"); ref.current = setInterval(() => { setFormattedTime(formatTime(arrivedTime)); }); return () => { console.log("clearInterval"); clearInterval(ref.current); ref.current = null; }; }, [arrivedTime]); return formattedTime; } export default function App() { const [time, setTime] = useState(new Date()); const formattedTime = useFormatMessageTime(time); return ( <> message arrived at: {formattedTime} <button onClick={() => setTime(new Date())}>set now</button> </> ); }Copy the code

Realize the principle of

Recommended reading:

  • Use an array of closures to save hook state
  • Simple understanding and implementation of Fiber architecture: rendering task refinement using requestIdleCallback and saving the current task, hook hanging on Fiber in the form of linked list, simple DOM diff

IO /s/react-fib…

Contrast the Vue

This article compares React Hooks with the Vue Composition API in more detail. List briefly the similarities and differences in the presentation.

Similarities:

  1. Solve the problem of state reuse and logical code point dispersion
  2. The API has similarities

Difference:

  1. The React list-based implementation has restrictions on the order of calls, while the Vue proxy-based implementation does not.
  2. React requires manually defining hook dependencies but provides an optimized means of skipping execution. Vue collects dependencies automatically.
  3. React hooks need to be executed every time they are rendered, and vue is executed once in setup.
  4. React provides useEffect to handle side effects similar to component execution equivalent to multiple life cycles. Vue provides different apis for different option life cycles.

Conclusion figure

The resources

  • The react hook documents
  • Eslint – plugin – react – hooks document
  • The birth of React hooks
  • React hooks Advances and principles
  • The React principle of Hooks
  • React Hook basic implementation principles
  • React hooks: Not magic, just arrays
  • Simple understanding and implementation of the Fiber architecture
  • Why does React Fiber use linked lists to design component trees
  • Rounding requestIdleCallback
  • The react hook source
  • Vue composite API documentation
  • Contrast React Hooks with Vue Composition API