React hook summary

redux's pit

  • Large components are difficult to split and refactor, and difficult to test
  • The business logic is scattered among the various methods of the component, resulting in duplication of logic or associative logic.
  • The component class introduces complex programming patterns, such as render, props and high-level components.

Function component

It's better to just be the pipeline of data flow, and the best way to write components should be functions, not classes.

The design purpose of React Hooks is to enhance the functional components. You can write a full-featured component without using'classes' at all.

The meaning of hook

All hooks introduce external functions to functions, so the React convention is that hooks always use the use prefix command.

If you want to use the xxx function, the hook is named usexxx.

Common hooks

<!--基础的-->
useState()
useContext()
useEffect()
<!--额外的-->
useReducer()
useCallback()
useMemo()
useRef()
useImperativeHandle()
useLayoutEffect()
useDebugValue()

Note

  • Hook can only be called in the outermost layer of the function, not in loops, conditional judgments, or sub-functions.
  • Hook can only be called in the function component of React, not in other JavaScript functions,

useState() state hook

Used to introduce state to functional components. Pure functions cannot have state, so put the state in the hook.

demo

import React, { useState } from "react";

export default function Button() {
  const [buttonText, setButtonText] = useState("Click me, please");

  function handleClick() {
    return setButtonText("Thanks, been clicked!");
  }

  return <button onClick={handleClick}>{buttonText}</button>;
}

useState() This function accepts the initial value of the state as a parameter. The initial value of the above example is the text of the button. This function returns an array, the first member of the array is 一个变量(buttonText in the above example), which points to the current value of the state. The second member is 一个函数used to update the state, the convention is set前缀加上状态的变量名(the above example is setButtonText).

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

Return a state, and a function to update the state

Update state function setXXX

For functions that update state, there are two assignment uses.

  1. Direct assignment
  2. Assign values ​​in the form of function callbacks.

useContext() shared state hook

If you need to share state between components, you can use useContext().

Receive a context object (return value of React.createContext) and return 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> of the upper layer of the component is updated, the 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.

Use the Context API to create a Context outside the component.

const AppContext = React.createContext({});

The components are packaged as follows.

<AppContext.Provider value={
   
   {
  username: 'superawesome'
}}>
  <div className="App">
    <Navbar/>
    <Messages/>
  </div>
</AppContext.Provider>

In the above code, AppContext.Provider provides a Context object, which can be shared by child components.

Navbar component

const Navbar = () => {
  const { username } = useContext(AppContext);
  return (
    <div className="navbar">
      <p>AwesomeSite</p>
      <p>{username}</p>
    </div>
  );
}

Message component code

const Messages = () => {
  const { username } = useContext(AppContext)

  return (
    <div className="messages">
      <h1>Messages</h1>
      <p>1 message for {username}</p>
      <p className="message">useContext is awesome!</p>
    </div>
  )
}

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

Correct: useContext(MyContext)

错误: useContext(MyContext.Consumer)

Error: useContext(MyContext.Provider)

useReducer() action hook

Used to enter the reducer function

const [state,dispatch] = useReducer(reducer,initialState,init);

It accepts the Reducer function and the initial value of the state as parameters, and returns an array. The first member of the array is the current value of the state, and the second member is the dispatch function that sends the action.

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


const myReducer = (state, action) => {
  switch(action.type)  {
    case('countUp'):
      return  {
        ...state,
        count: state.count + 1
      }
    default:
      return  state;
  }
}

Component code

function App() {
  const [state, dispatch] = useReducer(myReducer, { count:   0 });
  return  (
    <div className="App">
      <button onClick={() => dispatch({ type: 'countUp' })}>
        +1
      </button>
      <p>Count: {state.count}</p>
    </div>
  );
}

Since Hooks can provide shared state and Reducer functions, it can replace Redux in these aspects. However, it cannot provide middleware and time travel. If you need these two functions, you still have to use Redux.

useEffect() side effect hook

useEffect() is used to introduce operations with side effects, the most common is to request data from the server. The code that used to be placed in componentDidMount can now be placed in useEffect().

It has the same purpose as the componentDidmount, componentDidUpdate and componentWillUnmount in the clas component. It was only merged into one api.

Changing the DOM, adding subscriptions, setting timers, logging, and performing other operations that contain side effects within the body of the functional component (here in the React rendering phase) are not allowed, because this may cause inexplicable bugs and destroy the UI Consistency.

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

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

useEffect(()  =>  {
  // Async Action
}, [dependencies])

In the above usage, useEffect() accepts two parameters. The first parameter is a function, and the asynchronous operation code is placed in it. The second parameter is an array, which is used to give the dependencies of Effect. As long as this array changes, useEffect() will be executed. The second parameter can be omitted, and useEffect() will be executed every time the component is rendered.

const Person = ({ personId }) => {
  const [loading, setLoading] = useState(true);
  const [person, setPerson] = useState({});

  useEffect(() => {
    setLoading(true); 
    fetch(`https://swapi.co/api/people/${personId}/`)
      .then(response => response.json())
      .then(data => {
        setPerson(data);
        setLoading(false);
      });
  }, [personId])

  if (loading === true) {
    return <p>Loading ...</p>
  }

  return <div>
    <p>You're viewing: {person.name}</p>
    <p>Height: {person.height}</p>
    <p>Mass: {person.mass}</p>
  </div>
}
import React, { useState, useEffect } from 'react';

function Example() {
  const [count, setCount] = useState(0);

  // 相当于 componentDidMount 和 componentDidUpdate:
  useEffect(() => {
    // 使用浏览器的 API 更新页面标题
    document.title = `You clicked ${count} times`;
  });

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

When you call useEffect, you tell react to run your side-effect function after completing the changes to the DOM. Because side-effect functions are declared in the function, they can access the component's props and state. By default, react will call the side-effect function after each rendering-including the first rendering,

The side effect function can also specify how to clear the side effect by returning a function.

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

function FriendStatus(props) {
  const [isOnline, setIsOnline] = useState(null);

  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }

  useEffect(() => {
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    <!--返回一个函数。-->
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}

React will cancel the subscription to chatAPI when the component is unregistered, and then re-execute the rendering side effect function during subsequent rendering.

Like useState, useEffect can be used multiple times in the component

function FriendStatusWithCounter(props) {
  const [count, setCount] = useState(0);
  <!--副作用函数-->
  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  const [isOnline, setIsOnline] = useState(null);
  <!--副作用函数-->
  useEffect(() => {
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }
  // ...

Help on business

  1. The dependency is the query parameter, then the access request can be made in useEffect, then as long as the query parameter changes, the list will be automatically accessed and refreshed. Note that we changed the timing of fetching from the trigger end to the receiving end.
  2. When the list is updated, re-register the drag and drop response event. The same is true. The dependent parameter is a list. As long as the list changes, the drag-and-drop response will be reinitialized, so that we can safely modify the list without worrying about the failure of the drag-and-drop event.
  3. As long as a certain data in the data stream changes, the page title is modified synchronously. Similarly, there is no need to modify the title every time the data changes, but to "monitor" the data changes through useEffect, which is a kind of "inversion of control" thinking.

useCallback

Used to solve the problem of abstracting the function outside useEffect.

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

Return a memoized callback function.

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

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

The dependency array will not be passed as a parameter to the callback function. Although conceptually it appears as: all values ​​referenced in the callback function should appear in the dependency array. In the future, the compiler will be more intelligent, and it will be possible to create an array automatically.

We recommend enabling the exhaustive-deps rule in eslint-plugin-react-hooks. This rule will issue a warning when adding a wrong dependency and give repair suggestions.

Why useCallback is better than componentDidUpdate

class demo

class Parent extends Component {
  state = {
    count: 0,
    step: 0
  };
  fetchData = () => {
    const url =
      "https://v?query=" + this.state.count + "&step=" + this.state.step;
  };
  render() {
    return <Child fetchData={this.fetchData} count={count} step={step} />;
  }
}

class Child extends Component {
  state = {
    data: null
  };
  componentDidMount() {
    this.props.fetchData();
  }
  componentDidUpdate(prevProps) {
    if (
      this.props.count !== prevProps.count &&
      this.props.step !== prevProps.step // 别漏了!
    ) {
      this.props.fetchData();
    }
  }
  render() {
    // ...
  }
}

We need to understand that props.count and props.step are used by the props.fetchData function, so in componentDidUpdate, if these two parameters have changed, the re-fetch is triggered.

However, the question is, is the cost of this understanding too high? If the parent function fetchData was not written by me, how do I know that it depends on props.count and props.step without reading the source code? What's more serious is that if fetchData relies more on the parameter params one day, downstream functions will need to cover this logic in componentDidUpdate, otherwise the data will not be re-fetched when params changes. It is conceivable that the maintenance cost of this method is huge, and it can even be said that it is almost impossible to maintain.

function demo

function Parent() {
  const [ count, setCount ] = useState(0);
  const [ step, setStep ] = useState(0);

  const fetchData = useCallback(() => {
    const url = 'https://v/search?query=' + count + "&step=" + step;
  }, [count, step])

  return (
    <Child fetchData={fetchData} />
  )
}

function Child(props) {
  useEffect(() => {
    props.fetchData()
  }, [props.fetchData])

  return (
    // ...
  )
}

It not only solves the maintenance problem, but also re-executes certain logic as long as the parameters change. It is especially suitable to use useEffect. Using this kind of thinking to think about problems will make your code more "intelligent", and use a split life cycle for processing. Thinking will make your code torn apart, and it is easy to miss all kinds of opportunities.

useMemo do PureRender

In Fucntion Component, the equivalent concept of PureComponent of Class Component is React.memo.

React.memo 的demo

const Child = memo((props) => {
  useEffect(() => {
    props.fetchData()
  }, [props.fetchData])

  return (
    // ...
  )
})
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

Returns a memoized value.

Pass the "create" function and the dependency array as parameters to useMemo, which will only recalculate the memoized value when a dependency changes. This optimization helps avoid costly calculations every time you render.

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

If no dependency array is provided, useMemo will calculate a new value each time it is rendered.

You can use useMemo as a means of performance optimization, but don't use it as a semantic guarantee. In the future, React may choose to "forget" some of the previous memoized values ​​and recalculate them in the next rendering, such as freeing memory for off-screen components. First write code that can be executed without useMemo-then add useMemo to your code to optimize performance.

The dependency array is not passed as a parameter to the "create" function. Although conceptually it appears as: all the values ​​referenced in the "create" function should appear in the dependency array. In the future, the compiler will be more intelligent, and it will be possible to create an array automatically.

We recommend enabling the exhaustive-deps rule in eslint-plugin-react-hooks. This rule will issue a warning when adding a wrong dependency and give repair suggestions.

useMemo demo

const Child = (props) => {
  useEffect(() => {
    props.fetchData()
  }, [props.fetchData])

  return useMemo(() => (
    // ...
  ), [props.fetchData])
}

useRef guarantees that time-consuming functional dependencies remain unchanged

const refContainer = useRef(initialValue);

useRef returns a variable ref object whose .current property is initialized as the passed parameter (initialValue). The returned ref object remains unchanged during the entire life cycle of the component.

A common use case is to access subcomponents imperatively:

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 store a variable value in its .current property.

You should be familiar with ref, the main way to access the DOM. If you put the ref object with

If the form is passed into the component, no matter how the node changes, React will set the .current property of the ref object to the corresponding DOM node.

However, useRef() is more useful than the ref attribute. It can easily store any variable value, which is similar to the way of using instance fields in a class.

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

Remember, useRef will not notify you when the content of the ref object changes. Changing the .current property will 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 the callback ref to do so.

useImperativeHandle

useImperativeHandle(ref, createHandle, [deps])

useImperativeHandle allows you to customize the instance value exposed to the parent component when using ref. In most cases, you should avoid using imperative codes like ref. useImperativeHandle should be used 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 rendered parent component can call inputRef.current.focus().

useLayoutEffect

It has the same structure as useEffect, except that the timing of the call is different.

Its function signature is the same as useEffect, but it will synchronously call effect after all DOM changes. You can use it to read the DOM layout and trigger re-rendering synchronously. Before the browser executes the drawing, the update plan inside useLayoutEffect will be refreshed synchronously.

Use standard useEffect as much as possible to avoid blocking visual updates.

If you are migrating code from a class component to a function component using Hook, you need to note that the call phase of useLayoutEffect is the same as componentDidMount and componentDidUpdate. However, we recommend that you use useEffect first, and only try to use useLayoutEffect when it fails.

If you use server-side rendering, please remember that neither useLayoutEffect nor useEffect can be executed before the Javascript code is loaded. This is why the React alert is triggered when the useLayoutEffect code is introduced into the server-side rendering component. To solve this problem, you need to move the code logic to useEffect (if the logic is not needed for the first rendering), or delay the component to display after the client rendering is completed (if the HTML is displayed disorderly until useLayoutEffect is executed) in the case of).

To exclude components that rely on layout effects from the HTML rendered on the server side, you can use showChild && for conditional rendering, and use useEffect(() => {setShowChild(true); }, []) to delay the display of components. In this way, before the client-side rendering is complete, the UI will not be displayed as messy as before.

useDebugValue

useDebugValue(value)

useDebugValue can be used to display the label of a custom hook in the React developer tool.

For example, the custom Hook named useFriendStatus described in the "Custom Hook" section:

function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  // ...

  // 在开发者工具中的这个 Hook 旁边显示标签
  // e.g. "FriendStatus: Online"
  useDebugValue(isOnline ? 'Online' : 'Offline');

  return isOnline;
}

We do not recommend that you add a debug value to each custom hook. It is most valuable when it is part of a shared library.

Custom hook

demo

function useFriendStatus(friendID){
    const [isOnline,setIsOnline] = useState(null);
    
    function handleStatusChange(status){
        setIsOnline(status.isOnline);
    }
    
    useEffect(()=>{
        ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
        return () => {
          ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
        }
    });
    
    return isOnline;
}

Custom Hook is more of a convention than a function. If the name of the function starts with "use" and calls other hooks, we say that this is a custom hook. The naming convention of useSomething allows our linter plugin to find bugs in the code that uses Hooks.

eslint-plugin-react-hooks

The eslint-plugin-react-hooks plug-in must be configured when writing react hooks.

Guess you like

Origin blog.csdn.net/wancheng815926/article/details/105724499