react:Hook API

Basic Hook

Additional Hooks

Basic Hook

1.useState

Returns a state, and a function to update the state.

During initial rendering, the returned state (state) is the same value as the first parameter (initialState) passed in.

The setState function is used to update the state. It receives a new state value and queues a re-render of the component.

On subsequent re-renders, the first value returned by useState will always be the latest updated state.

React will ensure that the identity of the setState function is stable and does not change across component re-renders. That's why setState can be safely omitted from the dependency list of useEffect or useCallback.

const [state, setState] = useState(initialState);
setState(newState);

functional update

If the new state needs to be calculated using the previous state, a function can be passed to setState. This function will take the previous state and return an updated value. The counter component example below shows two usages of setState

function Counter({initialCount}) {
  const [count, setCount] = useState(initialCount);
  return (
    <>
      Count: {count}
      <button onClick={() => setCount(initialCount)}>Reset</button>
      <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
      <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
    </>
  );
}

The "+" and "-" buttons are functional because the updated state needs to be based on the previous state. But the "reset" button takes the normal form, since it always sets count back to its initial value.

If your update function returns exactly the same state as the current state, subsequent re-renders will be skipped entirely.

Notice:

Unlike the setState method in class components, useState does not automatically merge updated objects. You can use the functional setState combined with the spread operator to achieve the effect of merging and updating objects.

与 class 组件中的 setState 方法不同,useState 不会自动合并更新对象。你可以用函数式的 setState 结合展开运算符来达到合并更新对象的效果。
useReducer is another alternative, which is more suitable for managing state objects with multiple subvalues.

lazy initial state

The initialState parameter will only work on the initial render of the component, and will be ignored on subsequent renders. If the initial state needs to be obtained through complex calculations, you can pass in a function, calculate and return the initial state in the function, this function is only called during the initial rendering:

const [state, setState] = useState(() => {
  const initialState = someExpensiveComputation(props);
  return initialState;
});

Skip state update

When calling the update function of State Hook and passing in the current state, React will skip the rendering of subcomponents and the execution of effects. (React uses the Object.is comparison algorithm to compare states.)

Note that React may still need to render the component before skipping the render. But since React doesn't unnecessarily render "deep" nodes in the component tree, you don't have to worry about it. If you perform expensive calculations during rendering, you can use useMemo to optimize

2.useEffect

useEffect(didUpdate);

This Hook accepts a function containing imperative, possibly side-effecting code.

Changing the DOM, adding subscriptions, setting timers, logging, and performing other operations with side effects within the body of a function component (here during the React rendering phase) are not allowed, because this may cause inexplicable bugs and break the UI Consistency

Use useEffect to complete side effects. The function assigned to useEffect will be executed after the component is rendered to the screen. You can think of effects as an escape route from the purely functional world of React to the imperative world.

By default, the effect will be executed after each rendering round, but you can choose to have it only executed when certain values ​​change.

clear effect

Typically, resources created by effects such as subscription or timer IDs need to be cleaned up when the component unmounts. To do this, the useEffect function returns a cleanup function. The following is an example of creating a subscription:

useEffect(() => {
  const subscription = props.source.subscribe();
  return () => {
    // 清除订阅
    subscription.unsubscribe();
  };
});

To prevent memory leaks, the cleanup function will be executed before the component is unmounted. Also, if a component renders multiple times (which it usually does), the previous effect is cleared before the next one is executed . In the above example, this means that every update of the component creates a new subscription.

The execution timing of effect

Different from componentDidMount and componentDidUpdate, the function passed to useEffect will be delayed after the browser finishes layout and drawing. This makes it suitable for many common side-effect scenarios, such as setting subscriptions and event handling, so operations that block the browser updating the screen should not be performed in the function.

However, not all effects can be delayed. For example, user-visible DOM changes must be performed synchronously before the browser performs the next paint, so that the user does not experience visual inconsistencies. (Conceptually similar to the difference between passively listening to events and actively listening to events.) React provides an additional useLayoutEffect Hook to handle this type of effect. It has the same structure as useEffect, the only difference is the timing of calling.

While useEffect will execute lazily after the browser paints, it is guaranteed to execute before any new renders. React will refresh the effects from the last render before the component is updated.

conditional execution of effect

By default, effects are executed after each round of component rendering. This way, whenever the effect's dependencies change, it will be recreated.

Instead of creating a new subscription every time the component is updated, we only need to recreate it when the source prop changes.

To achieve this, useEffect can be passed a second parameter, which is an array of values ​​that the effect depends on. The updated example is as follows

useEffect(
  () => {
    const subscription = props.source.subscribe();
    return () => {
      subscription.unsubscribe();
    };
  },
  [props.source],
);

At this point, the subscription will only be recreated if props.source changes.

3.useContext

const value = useContext(MyContext);

Receives a context object (the return value of React.createContext) and returns the current value of the context. The current context value is determined by the value prop of the <MyContext.Provider> closest to the current component in the upper component.

When the latest <MyContext.Provider> above the component is updated, this Hook will trigger re-rendering and use the latest context value passed to the MyContext provider. Even if the ancestor uses React.memo or shouldComponentUpdate , it will re-render when the component itself uses useContext.

Don't forget that the parameter of useContext must be the context object itself :

  • Correct: useContext(MyContext)

  • Error: useContext(MyContext.Consumer)

  • Error: useContext(MyContext.Provider)

Components that call useContext will always re-render when the context value changes. If re-rendering components is expensive, you can optimize it by using memoization .

hint:

If you are already familiar with context API before contacting Hook, it should be understandable that useContext(MyContext) is equivalent to static contextType = MyContext or <MyContext.Consumer> in class component.

useContext(MyContext) just allows you to read the context value and subscribe to context changes. You still need to use <MyContext.Provider> in the upper component tree to provide context for lower components.

const themes = {
  light: {
    foreground: "#000000",
    background: "#eeeeee"
  },
  dark: {
    foreground: "#ffffff",
    background: "#222222"
  }
};

const ThemeContext = React.createContext(themes.light);

function App() {
  return (
    <ThemeContext.Provider value={themes.dark}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

function ThemedButton() {
  const theme = useContext(ThemeContext);
  return (
    <button style={
    
    { background: theme.background, color: theme.foreground }}>
      I am styled by theme context!
    </button>
  );
}

Additional Hooks

useReducer

An alternative to useState . It receives a reducer in the form of (state, action) => newState, and returns the current state and its dispatch method. (If you're familiar with Redux, you already know how it works.)

In some scenarios, useReducer is more suitable than useState, for example, the state logic is complex and contains multiple sub-values, or the next state depends on the previous state, etc. Also, using useReducer can also optimize the performance of components that trigger deep updates, because you can pass dispatch to subcomponents instead of callback functions .

const [state, dispatch] = useReducer(reducer, initialArg, init);
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();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}

React will ensure that the identity of the dispatch function is stable and does not change across component re-renders. That's why dispatch can be safely omitted from the dependency list of useEffect or useCallback.

Specify the initial state

There are two different ways to initialize useReducer state, you can choose one of them according to the usage scenario. Passing the initial state as the second argument to useReducer is the easiest way:

  const [state, dispatch] = useReducer(
    reducer,
    {count: initialCount}
  );

lazy initialization

You can choose to lazily create the initial state. To do this, the init function needs to be passed in as the third parameter of useReducer, so that the initial state will be set to init(initialArg).

function init(initialCount) {
  return {count: initialCount};
}

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

function Counter({initialCount}) {
  const [state, dispatch] = useReducer(reducer, initialCount, init);
  return (
    <>
      Count: {state.count}
      <button
        onClick={() => dispatch({type: 'reset', payload: initialCount})}>
        Reset
      </button>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}

skip dispatch

If the return value of the Reducer Hook is the same as the current state, React will skip the rendering of child components and the execution of side effects. (React uses the Object.is comparison algorithm to compare states.)

Note that React may still need to render the component again before skipping the render. But since React doesn't unnecessarily render "deep" nodes in the component tree, you don't have to worry about it. If you perform expensive calculations during rendering, you can use useMemo to optimize.

useCallback

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

Return a memoized callback function

Pass an inline callback function and an array of dependencies as parameters to useCallback, and it will return a memoized version of the callback function that will only be updated when a dependency changes. It is useful when you pass callbacks to child components that are optimized and use reference equality to avoid unnecessary rendering (eg shouldComponentUpdate).

useCallback(fn, deps) is equivalent to useMemo(() => fn, deps).

use Memo

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

Returns a memoized value.

Pass the "create" function and the dependencies array as parameters to useMemo, and it will only recalculate the memoized value when one of the dependencies changes. This optimization helps avoid expensive calculations on every render.

Remember, the function passed to useMemo will be executed during rendering. Please do not perform operations not related to rendering inside this function. Operations such as side effects belong to the applicable scope of useEffect, not useMemo.

If no dependencies array is provided, useMemo will compute new values ​​on every render.

You can use useMemo as a performance optimization, but don't take it as a semantic guarantee. In the future, React may choose to "forget" some previous memoized values ​​and recalculate them on the next render, e.g. to free up memory for offscreen components. Write code that will execute without useMemo first -- add useMemo to your code later to optimize performance.

useRef

const refContainer = useRef(initialValue);

useRef returns a mutable ref object whose .current property is initialized to the passed parameter (initialValue). The returned ref object persists throughout the lifetime of the component.

function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // `current` 指向已挂载到 DOM 上的文本输入元素
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

Essentially, useRef is like a "box" that can hold a mutable value in its .current property.

You should be familiar with ref which is the main way of accessing the DOM . If you pass a ref object into a component as <div ref={myRef} /> , React will set the ref object's .current property to the corresponding DOM node no matter how the node changes.

However, useRef() is more useful than the ref attribute. It's a convenient way to store any mutable value, similar to how instance fields are used in a class.

This is because it creates a plain Javascript object. The only difference between useRef() and self-built {current: ...} object is that useRef will return the same ref object every time it is rendered.

Remember that useRef doesn't notify you when the ref object's content changes . Changing the .current property does not cause the component to re-render. If you want to run some code when React binds or unbinds the ref of a DOM node, you need to use a callback ref to achieve

useImperativeHandle

useImperativeHandle(ref, createHandle, [deps])

useImperativeHandle allows you to customize the instance value exposed to the parent component when using ref. In most cases, imperative code like ref should be avoided. useImperativeHandle should be used together with forwardRef :

function FancyInput(props, ref) {
  const inputRef = useRef();
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    }
  }));
  return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);

In this example, the parent component that renders <FancyInput ref={inputRef} /> can call inputRef.current.focus().

useLayoutEffect

Its function signature is the same as useEffect, but it will call effect synchronously after all DOM changes. It can be used to read the DOM layout and trigger re-renders synchronously. The update plan inside useLayoutEffect will be refreshed synchronously before the browser performs the drawing.

Use standard useEffect whenever possible to avoid blocking visual updates.

useDebugValue

useDebugValue(value)

useDebugValue can be used to display a custom hook's label in React DevTools.

It is not recommended that you add debug values ​​to each custom Hook. It's most valuable when it's part of a shared library.

Lazy formatting of debug values

In some cases, the display of formatted values ​​can be an expensive operation. Unless you need to check the Hook, there is no need to do this.

Therefore, useDebugValue accepts a formatting function as an optional second argument. This function will only be called when the Hook is checked. It accepts the debug value as an argument and returns a formatted display value.

For example, a custom Hook that returns a Date value can avoid unnecessary toDateString function calls through the format function:

useDebugValue(date, date => date.toDateString());

Guess you like

Origin blog.csdn.net/weixin_62364503/article/details/129358148