React Hooks series 1: Use of common apis

Author: Sleeping pigeon

preface

Hook is a new feature in React 16.8. It resolves the limitations of function components that can only render views and allows you to use state and other React features without writing classes.

advantages

  1. You can avoid this in the class component
  2. With custom hooks, logic reuse is much easier
  3. The code is cleaner and reduces the additional layers of DOM nesting created in class components using higher-order components

disadvantages

  1. Because hooks mostly store state in closures, they often don’t get the latest state in asynchronous code or callbacks
  2. Cross-dependencies on effect can cause unexpected misrenders that take more time to resolve

API

1. useState

At present, a major feature of the front-end framework is data-driven view, so defining data items and modifying data items is the basis of the foundation. Here, class components and function components are used to complete a small function respectively. Every time you click the “+1” button, the number on the page will add one

import React, { Component } from 'react'
import styles from './index.module.css'
export default class ComA extends Component {
  state = {
    count: 0
  }
  onClick = () = > {
    const { count } = this.state
    this.setState({ count: count + 1 })
  }
  render () {
    const { count } = this.state
    return (
      <div className={styles.main}>
        <div className={styles.count}>{count}</div>
        <button className={styles.btn} onClick={this.onClick}>+ 1</button>
      </div>)}}Copy the code

// useState, to address the lack of state in function components
import { useState } from 'react'
import styles from './index.module.css'
export default function ComB () {
  The useState function takes a default value for the state and returns an array of data items. The first value is the state item and the second value is the method that changed the state
  const [count, setCount] = useState(0)
  const onClick = () = > {
    setCount(count + 1)}return (
    <div className={styles.main}>
      <div className={styles.count}>{count}</div>
      <button className={styles.btn} onClick={onClick}>+ 1</button>
    </div>)}Copy the code

UseState summary

As you can see, the function component with hooks has fewer lines of code, and gets rid of this, if you want to increase state continue to use useState

2. useEffect

The useEffect function is the life cycle of a function component. Once again, let’s add a simple useEffect to see how it works

import { useState, useEffect } from 'react'
import styles from './index.module.css'
export default function ComB () {
  const [count, setCount] = useState(0)
  const onClick = () = > {
    setCount(count + 1)}// useEffect takes two arguments. The first argument is mandatory for the function, and the return value is optional. If so, a function must be returned, and the second argument is an array
  // Start with the simplest effect. The other parameters will be explained later
  useEffect(() = >{
    console.log('111')})console.log('Render view')
  return (
    <div className={styles.main}>
      <div className={styles.count}>{count}</div>
      <button className={styles.btn} onClick={onClick}>+ 1</button>
    </div>)}Copy the code

ComponentDidMount () {” 111 “is printed and” render page “is printed before” 111 “.

Let’s go ahead and click the “+1” button a few times to see if we can print out the useEffect for componentDidUpdate()

We delete the extraneous code, add a return value (which must be a function) to the first argument to effect, and print the current count value

import { useState, useEffect } from 'react'
import styles from './index.module.css'
export default function ComB () {
  const [count, setCount] = useState(0)
  const onClick = () = > {
    setCount(count + 1)
  }
  useEffect(() = > {
    console.log('111')
    return () = > {
      console.log(count)
    }
  })
  return (
    <div className={styles.main}>
      <div className={styles.count}>{count}</div>
      <button className={styles.btn} onClick={onClick}>+ 1</button>
    </div>)}Copy the code

Refresh the page, discarding the first “111” printed when first loaded, and print the last count value every time the button is clicked, and then print “111”, which means that every time effect is triggered, the return value of the last effect is executed, which is not available in the class component

When we remove the function component (switch to the class component), we see that the 3 is printed out, and the return value appears to be componentWillUnmount()

Now to add the second parameter to effect, we need to pass an array, starting with an empty array

import { useState, useEffect } from 'react'
import styles from './index.module.css'
export default function ComB (props) {
  const [count, setCount] = useState(0)
  const onClick = () = > {
    setCount(count + 1)
  }
  useEffect(() = > {
    console.log('111')
    return () = > {
      console.log('222')}}, [])return (
    <div className={styles.main}>
      <div className={styles.count}>{count}</div>
      <button className={styles.btn} onClick={onClick}>+ 1</button>
    </div>)}Copy the code

Enter the refresh page for the first time, print “111”, and then no matter how we click “+1” button, there is no response

After we remove the function component (switch to the class component), “222” was also printed before removing the component. It seems that effect will not be triggered by the change of the data item when passing in an empty array. It will still be triggered when creating and removing the component

We then add “count” to the empty array and see that effect behaves exactly as if the second parameter had not been passed

import { useState, useEffect } from 'react'
import styles from './index.module.css'
export default function ComB (props) {
  const [count, setCount] = useState(0)
  const onClick = () = > {
    setCount(count + 1)
  }
  useEffect(() = > {
    console.log('111')
    return () = > {
      console.log('222')
    }
  }, [count])					// If the second parameter is not passed, it is equivalent to passing an array of all data items
  return (
    <div className={styles.main}>
      <div className={styles.count}>{count}</div>
      <button className={styles.btn} onClick={onClick}>+ 1</button>
    </div>)}Copy the code

UseEffect can replace componentDidMount(), componentWillUnmount(), componentDidUpdate() by adding a return value to the first component. Let’s simulate an interface request and do a list rendering to get a feel for it

import { useState, useEffect } from 'react'
import styles from './index.module.css'
// Simulate the interface
const getListApi = () = > {
  return new Promise((resolve, reject) = > {
    setTimeout(() = > {
      resolve([
        { id: 1.type: 10.text: 'List1' },
        { id: 2.type: 10.text: 'List2' },
        { id: 3.type: 20.text: 'List3' },
        { id: 4.type: 20.text: 'List4' },
        { id: 5.type: 10.text: 'List5'}}]),100)})}export default function ComC () {
  const [list, setList] = useState([])
  // The simulation interface requests to render data
  useEffect(async() = > {const res = await getListApi()
    setList(res)
  }, [])								// The interface is only requested when entering the page, and the second parameter only needs to be passed the empty array
  return (
    <div className={styles.main}>
      <div className={styles.list}>
        {list.length &&
          list.map(val => (
            <div className={styles.line} key={val.id}>
              {val.text}
            </div>
          ))}
      </div>
    </div>)}Copy the code

The list appears to be rendering properly, but there seems to be something wrong with the code. Async declared functions return a promise, and useEffect returns a function for the first argument

When you open the console, there is a warning that we should package another layer to prevent the return value of the first parameter of useEffect from conflicting

Make a few changes to the code to make sure there are no problems with the page, and a simple mock request page is complete

import { useState, useEffect } from 'react'
import styles from './index.module.css'
// Simulate the interface
const getListApi = () = > {
  return new Promise((resolve, reject) = > {
    setTimeout(() = > {
      resolve([
        { id: 1.type: 10.text: 'List1' },
        { id: 2.type: 10.text: 'List2' },
        { id: 3.type: 20.text: 'List3' },
        { id: 4.type: 20.text: 'List4' },
        { id: 5.type: 10.text: 'List5'}}]),100)})}export default function ComC () {
  const [list, setList] = useState([])
  useEffect(() = > {
    getList()
  }, [])								// The interface is only requested when entering the page, and the second parameter only needs to be passed the empty array
  // Extract async functions to avoid collisions
  const getList = async() = > {const res = await getListApi()
    setList(res)
  }
  return (
    <div className={styles.main}>
      <div className={styles.list}>
        {list.length &&
          list.map(val => (
            <div className={styles.line} key={val.id}>
              {val.text}
            </div>
          ))}
      </div>
    </div>)}Copy the code

UseEffect summary

  1. UseEffect The first argument (function) is executed when the component is created (as is the update of the data item bound to the second argument), and its return value (function) is executed before the component is removed (the data item bound to the second argument is executed before the next change), giving the function component a life cycle
  2. UseEffect The return value of the first argument (function) must be a function to avoid hooks
  3. UseEffect can also write multiple (sequential)
  4. Avoid modifying an effect state in an effect that is already listening on; this will cause an effect to loop in an infinite loop

3. useRef

UseRef is the method to get dom elements

// Class component, this.a can get the DOM
import React from 'react'
class A extends React.Component{
    a = React.createRef()
	render() { return<div ref={a}></div>}}/ / useRef, biggest urrent
import React, { useRef } from 'react'
function B () {
    const b = useRef(null)
    return <div ref={b}><div>
}
Copy the code

Using useRef is not that difficult. After binding, you just need.current to retrieve dom objects. Here’s where useRef is more useful: it saves a variable value that can be fetched anywhere

import { useState, useEffect } from 'react'
import styles from './index.module.css'
// tab
const TITLE = [
  { code: 0.text: 'all' },
  { code: 10.text: 'Type one' },
  { code: 20.text: 'Type 2'}]// Simulate the interface
const getListApi = () = > {
  return new Promise((resolve, reject) = > {
    setTimeout(() = > {
      resolve([
        { id: 1.type: 10.text: 'List1' },
        { id: 2.type: 10.text: 'List2' },
        { id: 3.type: 20.text: 'List3' },
        { id: 4.type: 20.text: 'List4' },
        { id: 5.type: 10.text: 'List5'}}]),100)})}export default function ComC () {
  // TAB selected index
  const [checkedIdx, setCheckIdx] = useState(0)
  // The list of pages to display
  const [list, setList] = useState([])
  // All list data returned by the interface
  const [AllList, setAllList] = useState([])
  useEffect(() = > {
    getList()
  }, [])
  // Interface request, render list
  const getList = async() = > {const res = await getListApi()
    setList(res)
    setAllList(res)
  }
  // TAB toggle, trying to update
  const onClick = idx= > {
    setCheckIdx(idx)
    if (idx === 0) return setList(AllList)
    const newList = AllList.filter(val= > val.type === TITLE[idx].code)
    setList(newList)
  }
  return (
    <div className={styles.main}>
      <div className={styles.tab}>
        {TITLE.map((val, idx) => (
          <div
            onClick={()= > {
              onClick(idx)
            }}
            key={val.code}
            className={idx === checkedIdx ? styles.checked : ''}
          >
            {val.text}
          </div>
        ))}
      </div>
      <div className={styles.list}>
        {list.length &&
          list.map(val => (
            <div className={styles.line} key={val.id}>
              {val.text}
            </div>
          ))}
      </div>
    </div>)}Copy the code

To do this, the code does not use useRef, but adds an AllList state. However, if you think about it, AllList does not render the page, it is the List that actually renders the page. AllList is just a data source that provides data. There is no need to create a separate state for it, wasting performance, using useRef to modify it

import { useState, useEffect, useRef } from 'react'
import styles from './index.module.css'
const TITLE = [
  { code: 0.text: 'all' },
  { code: 10.text: 'Type one' },
  { code: 20.text: 'Type 2'}]// Simulate the interface
const getListApi = () = > {
  return new Promise((resolve, reject) = > {
    setTimeout(() = > {
      resolve([
        { id: 1.type: 10.text: 'List1' },
        { id: 2.type: 10.text: 'List2' },
        { id: 3.type: 20.text: 'List3' },
        { id: 4.type: 20.text: 'List4' },
        { id: 5.type: 10.text: 'List5'}}]),100)})}export default function ComC () {
  // TAB selected index
  const [checkedIdx, setCheckIdx] = useState(0)
  // The list of pages to display
  const [list, setList] = useState([])
  // All list data returned by the interface
  const AllList = useRef([])													/ / modify
  useEffect(() = > {
    getList()
  }, [])
   // Interface request, render list
  const getList = async() = > {const res = await getListApi()
    setList(res)
    AllList.current = res														/ / modify
  }
    // TAB toggle, trying to update
  const onClick = idx= > {
    setCheckIdx(idx)
    if (idx === 0) return setList(AllList.current)								  / / modify
    const newList = AllList.current.filter(val= > val.type === TITLE[idx].code)		/ / modify
    setList(newList)
  }
  return (
    <div className={styles.main}>
      <div className={styles.tab}>
        {TITLE.map((val, idx) => (
          <div
            onClick={()= > {
              onClick(idx)
            }}
            key={val.code}
            className={idx === checkedIdx ? styles.checked : ''}
          >
            {val.text}
          </div>
        ))}
      </div>
      <div className={styles.list}>
        {list.length &&
          list.map(val => (
            <div className={styles.line} key={val.id}>
              {val.text}
            </div>
          ))}
      </div>
    </div>)}Copy the code

UseRef summary

UseRef can be used to retrieve DOM objects, but the real purpose of useRef is to store mutable values, similar to the way instance fields are used in a class, reducing performance waste

4. UseMemo, useCallback

In terms of performance, reasonable use of useMemo and useCallback can improve efficiency and reduce performance waste. First, useMemo, we add a function to display the time of the component before the click increment by 1. The initial time format is XX /xx/xx, and the display format is XX-XX-XX

import { useEffect, useState } from 'react'
import styles from './index.module.css'
export default function ComB (props) {
  const [count, setCount] = useState(0)
  const [date, setDate] = useState(' ')
  useEffect(() = > {
    setDate('2021/03/31')}, [])function onClick () {
    setCount(count + 1)}function formatDate (date) {
    console.log('Processing data')
    return date.replace(/\//g.The '-')}return (
    <div className={styles.main}>
      <div className={styles.count}>{formatDate(date)}</div>
      <div className={styles.count}>{count}</div>
      <button className={styles.btn} onClick={onClick}>+ 1</button>
    </div>)}Copy the code

There seems to be no problem. Print ‘process data’ twice, once for component initialization and once for first effect side effect. At this point we click the button and see what happens

We noticed that every time we clicked the +1 button to update the view, we also printed ‘process data’. We didn’t need to process the time every time we updated the view. Obviously, this is a waste of time to evaluate the same value multiple times

import { useEffect, useState, useMemo } from 'react'
import styles from './index.module.css'
export default function ComB (props) {
  const [count, setCount] = useState(0)
  const [date, setDate] = useState(' ')
  UseMemo also takes two arguments. The first argument is a function. The return value of useMemo is the return value of the callback function
  // The second argument is an array, each change in the array will be re-executed the callback function buffer, no changes will not be evaluated more than once
  const formatDateMemo = useMemo(() = > formatDate(date), [date])
  useEffect(() = > {
    setDate('2021/03/31')}, [])function onClick () {
    setCount(count + 1)}function formatDate (date) {
    console.log('Processing data')
    return date.replace(/\//g.The '-')}return (
    <div className={styles.main}>
      <div className={styles.count}>{formatDateMemo}</div>
      <div className={styles.count}>{count}</div>
      <button className={styles.btn} onClick={onClick}>+ 1</button>
    </div>)}Copy the code

Because the return value is cached, we don’t count multiple clicks, much like computed in VUe2

The same functionality can be avoided using useCallback

import React, { useEffect, useState, useCallback } from 'react'
import styles from './index.module.css'
export default function ComB (props) {
  const [count, setCount] = useState(0)
  const [date, setDate] = useState(' ')
  // useCallback takes two arguments. The first argument is a callback function. UseCallback returns the cached callback function
  // The second argument is also an array, and only one of the changes in the array will be recached
  const formatDateCallback = useCallback(formatDate, [date])
  useEffect(() = > {
    setDate('2021/03/31')}, [])function onClick () {
    setCount(count + 1)}function formatDate () {
    return date.replace(/\//g.The '-')}return (
    <div className={styles.main}>
      <MyDate formatDate={formatDateCallback}></MyDate>
      <div className={styles.count}>{count}</div>
      <button className={styles.btn} onClick={onClick}>+ 1</button>
    </div>)}class MyDate extends React.PureComponent{
  render () {
    console.log('Component Rendering')
    return <div className={styles.count}>{this.props.formatDate()}</div>}}Copy the code

When the same view is updated multiple times, the pure component is not rerendered because the formatDateCallback function passed to the pure component MyDate is cached

UseMemo and useCallback

  1. UseMemo caches a value. The return value of the first argument (the callback function) is that value. The second argument is an array in which each change is recached by the callback
  2. UseCallBack caches a function. The first argument is the function to be cached. The second argument is an array
  3. UseCallback (fn, deps) equivalent to useMemo(() => fn, deps)
  4. When there is a logically complex calculation in our code, and the cost of each execution is very expensive, we can use useMemo to cache the calculation results, and recalculate them when the dependent data items change to reduce the double calculation
  5. It is recommended not to use useMemo and useCallBack frequently, but to use them after the function is completed. Because they are implemented based on closures, they also take up performance. UseState, useEffect, and useRef also have caching capabilities. Use them adequately in logical implementations, and use them only when you really need useMemo and useCallback