React hooks:

We all know that the core idea of React is to split a page into a bunch of independent, reusable components and connect them together in a top-down one-way data flow. But if you use React on large work projects, you’ll find that many react components in your projects are actually lengthy and hard to reuse. Components written as class, in particular, contain their own state, making it cumbersome to reuse such components. Hook is a new feature in React 16.8. It lets you use state and other React features without having to write a class.

Hooks related to hooks:

The hook, role
useState Initialize and set state
useEffect ComponentDidMount, componentDidUpdate and componentWillUnmount, so you can listen for useState definition changes
useContext Define a global object, like context
useReducer Functions can be enhanced to provide redux-like functionality
useCallback The first parameter is an anonymous function, which is the body of the function we want to create. The second argument is an array of variables that determine whether the function body needs to be recreated. If the value of the variable passed in remains unchanged, the result of the memory is returned. If any of these items change, a new result is returned
useMemo UseCallback returns a function, and useDemo returns a value
useRef Gets the DOM corresponding to the ref attribute
useLayoutEffect The useEffect is the same, but fires synchronously after all DOM changes

useState

  • React assumes that when you call useState multiple times, you can guarantee their * each time you render** Call order *** is constant.
  • Add some internal state to the component by calling it inside the function component. React retains this state during repeated rendering
  • The only argument to useState is the initial state
  • UseState returns an array (deconstructed from the array), a state, and a function that updates the state
    • During initial rendering, the state returned is the same as the value of the first parameter passed in
    • You can call this function in an event handler or some other place. It is similar to the this.setState of the class component, but it does not merge the new state with the old state, instead replacing it directly
// We can call it whatever we want, because we're returning an array
const [state, setState] = useState(initialState);
Copy the code
import React, { useState } from 'react'; Const [count, setCount] = useState(0); function Example() {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

1.1 Each render is a separate closure

  • Each render has its own Props and State
  • Each render has its own event handler
  • When the status update is clicked, the function component is called again, so that each rendering is independent and the fetched value is not affected by subsequent operations
import { useState } from 'react' import { PageContainer } from '@ant-design/pro-layout'; import { Card, Button, message } from 'antd'; const Ceshi1 = () => { let [number, setNumber] = useState(0); Const alertNumber = () => {setTimeout(() => {// alert only gets the status when the button is clicked message.success(number); }, 2000); } return ( <PageContainer> <Card> <p>{number}</p> <Button onClick={() => setNumber(number + 1)}>+</Button> <Button onClick={alertNumber}>alertNumber</Button> </Card> </PageContainer> ) } export default Ceshi1Copy the code

1.2 Functional update

  • If the new state needs to be computed using the previous state, the callback function can be passed as an argument to setState. The callback will accept the previous state and return an updated value.
import { useState } from 'react' import { PageContainer } from '@ant-design/pro-layout'; import { Card, Button, message } from 'antd'; const Ceshi1 = () => { let [number, setNumber] = useState(0); Const lazy = () => {setTimeout(() => {const lazy = () => {setTimeout(() => {const lazy = () => {setTimeout(() => {const lazy = () => {setTimeout(() => {const lazy = () => { setNumber(number => number + 1); }, 3000); } return ( <PageContainer> <Card> <p>{number}</p> <Button onClick={() => setNumber(number + 1)}>+</Button> <Button onClick={lazy}>lazy</Button> </Card> </PageContainer> ) } export default Ceshi1Copy the code

1.3 Lazy Initialization of state

  • The initialState parameter is only used in the initial rendering of the component and will be ignored in subsequent renderings
  • If the initial state needs to be obtained through complex calculations, you can pass in a function that computes and returns the initial state, which is called only during the initial render
import { useState } from 'react'
import { PageContainer } from '@ant-design/pro-layout';
import { Card, Button, message } from 'antd';

const Ceshi1 = (props: any) = > {
    let [counter, setCounter] = useState(getInitState);
    // This function is executed only once during the initial render, and will not be called again when the component is re-rendered by updating the state
    function getInitState() {
        const initNum = (4+8) /3
        return { number: initNum};
    }
    return (
        <PageContainer>
            <Card>
                <p>{counter.number}</p>
                <Button onClick={()= > setCounter({ number: counter.number + 2})}>+</Button>
                <Button onClick={()= > setCounter(counter)}>setCounter</Button>
            </Card>
        </PageContainer>)}export default Ceshi1
Copy the code

1.4 useState replaces state with object. is

  • The Hook uses object. is internally to compare whether the new/old state is equal
  • Unlike the setState method in the Class component, if you modify the state without changing the value of the passed state, you do not re-render
  • Unlike the setState method in the Class component, useState does not automatically merge update objects. You can merge updated objects using a functional setState in conjunction with the expansion operator
Function Counter(){const [Counter,setCounter] = useState({name:' Counter ',number:0}); Console. log('render Counter') // If the state is not changed when you modify it, Return (<> <p>{counter. Name}:{counter. Number}</p> <button onClick={()=>setCounter({... counter,number:counter.number+1})}>+</button> <button onClick={()=>setCounter(counter)}>++</button> </> ) }Copy the code

1.5 If other states can be calculated, there is no need to declare states separately

A state must not be computed directly from other states /props, otherwise state is not defined.

const SomeComponent = (props) = > {
  
  const [source, setSource] = useState([
      {type: 'done'.value: 1},
      {type: 'doing'.value: 2},])const [doneSource, setDoneSource] = useState([])
  const [doingSource, setDoingSource] = useState([])

  useEffect(() = > {
    setDoingSource(source.filter(item= > item.type === 'doing'))
    setDoneSource(source.filter(item= > item.type === 'done'))
  }, [source])
  
  return (
    <div>.</div>)}Copy the code

In the example above, the variables doneSource and doingSource can be calculated using the source variable, so don’t define doneSource and doingSource!

const SomeComponent = (props) = > {
  
  const [source, setSource] = useState([
      {type: 'done'.value: 1},
      {type: 'doing'.value: 2},])const doneSource = useMemo(() = > source.filter(item= > item.type === 'done'), [source]);
  const doingSource = useMemo(() = > source.filter(item= > item.type === 'doing'), [source]);
  
  return (
    <div>.</div>)}Copy the code

In general, such problems are obscure in projects and pass through layers, which is hard to see at a glance in Code Review. If the variables can be clearly defined, that’s half the battle.

1.6 Ensure unique data sources

Ensure that the same data is stored in only one place within a project.

Do not exist in redux and define a state store in the component.

Do not have a state store defined in the current component that exists in the parent component.

Do not have a URL Query and define a state store in the component.

function SearchBox({ data }) {
  const [searchKey, setSearchKey] = useState(getQuery('key'));

  const handleSearchChange = e= > {
    const key = e.target.value;
    setSearchKey(key);
    history.push(`/movie-list? key=${key}`);
  }

  return (
      <input
        value={searchKey}
        placeholder="Search..."
        onChange={handleSearchChange}
      />
  );
}
Copy the code

In the above example, the searchKey is stored in two places, both on the URL Query and with a state defined. It can be optimized as follows:

function SearchBox({ data }) {
  constsearchKey = parse(localtion.search)? .key;const handleSearchChange = e= > {
    const key = e.target.value;
    history.push(`/movie-list? key=${key}`);
  }

  return (
      <input
        value={searchKey}
        placeholder="Search..."
        onChange={handleSearchChange}
      />
  );
}
Copy the code

In actual project development, such problems are also relatively obscure, and should be paid attention to when coding.

1.7 useState is properly merged

Have you ever written code in a project like this:

const [firstName, setFirstName] = useState();
const [lastName, setLastName] = useState();
const [school, setSchool] = useState();
const [age, setAge] = useState();
const [address, setAddress] = useState();

const [weather, setWeather] = useState();
const [room, setRoom] = useState();
Copy the code

Anyway, I originally wrote that the useState split was too thin, resulting in a large useState in the code.

I suggest that variables with the same meaning can be combined into a single state to make the code much more readable:

const [userInfo, setUserInfo] = useState({
  firstName,
  lastName,
  school,
  age,
  address
});

const [weather, setWeather] = useState();
const [room, setRoom] = useState();
Copy the code

When changing a variable, make sure you include the old field, for example, if you just want to change firstName:

setUserInfo(s= > ({
  ...s,
  fristName,
}))
Copy the code

React Class state merges automatically:

this.setState({
  firstName
})
Copy the code

In Hooks, can this be used? This is possible because we can package Hooks. For example, useSetState of ahooks encapsulates similar logic:

const [userInfo, setUserInfo] = useSetState({
  firstName,
  lastName,
  school,
  age,
  address
});

// Automatic merge
setUserInfo({
  firstName
})
Copy the code

useEffect

  • Effect: Refers to logic that does not occur during the conversion of data to view, such asajaxRequest, access nativedomElement, local persistent cache, bind/unbind events, add subscriptions, set timers, log, and more.
  • UseEffect is an Effect Hook that gives function components the ability to manipulate side effects. It is in the class componentcomponentDidMount,componentDidUpdatecomponentWillUnmountA combination of three apis
  • UseEffect receives a function that is executed after the component has been rendered to the screen, with the requirement that it either returns a function that clears side effects or nothing at all
  • withcomponentDidMountcomponentDidUpdateIn contrast, Effect scheduled with useEffect does not block the browser update screen, making your application seem more responsive. In most cases, effects do not need to be executed synchronously. In individual cases (such as measuring layout), a separate useLayoutEffect Hook with the same API as useEffect can be used.
useEffect(effect, array)
Copy the code

Effect is triggered each time a render is completed, in conjunction with Array to simulate the life cycle of the class

  • If not, every time componentDidUpdate returnFunction (if present) is raised, then Effect is raised
  • Simulation componentDidMount []
  • [id] Fires only after the value of id changes
  • Clear effect: The component handles memory problems when uninstalling, such as clearing timers and clearing event listeners
useEffect(() = > {
  ChatAPI.subscribeToFriendStatus(props.id, handleStatusChange);
  return () = > {
    ChatAPI.unsubscribeFromFriendStatus(props.id, handleStatusChange);
  };
},[id]);
Copy the code

useEffect

1. First time only componentDidMount can be used to request asynchronous data… ,

UseEffect Finally, the addition of [] indicates only the first execution

UseEffect (()=>{const users = get user info ()},[])Copy the code

2. Instead of life functions such as willUpdate that are executed every render

UseEffect Finally, without [], every render is executed

UseEffect (()=>{const users = get user info every time ()})Copy the code

3. It takes a bit of money to execute every render, so:

UseEffect finally, add [], and the field in [] indicates that the field has changed before this effect is executed

UseEffect (() => {const users = (name)},[name])Copy the code

4. What if I wanted to separate name and age:

Multiple useeffects can be written

},[name]) useEffect(() => {const users = (const users =)},[name]) useEffect(() => {const users = },[age]Copy the code

5. If we subscribe to something and then unsubscribe during the willUnMount life cycle, how can we do this with useEffect:

You can unsubscribe from an effect return

UseEffect (() => {const subscription = Return () => {unsubscribe from the national people eat intelligence! }}, [])Copy the code

Why unsubscribe?

UseEffect is used after render. If useEffect has a setInterval… Every time render is rendered, useEffect creates another setInterval, and chaos ensued… You can delete the following example return and feel it

useEffect(() => { console.log('use effect... ',count) const timer = setInterval(() => setCount(count +1), 1000) return ()=> clearInterval(timer) })Copy the code

6. Some other rules for useEffect:

The value of state used in useEffect is fixed in useEffect and will not be changed unless useEffect refreshes and resets the value of state

const [count, setCount] = useState(0) useEffect(() => { console.log('use effect... ',count) const timer = setInterval(() => { console.log('timer... Count :', count) setCount(count + 1)}, 1000) return ()=> clearInterval(timer)},[]Copy the code

useLayoutEffect

  • Similar to useEffect, page flickering in some feature scenarios can be resolved by synchronizing status updates

  • This is mainly used when dealing with the DOM. You need to use this when your useEffect operation needs to process the DOM and change the style of the page, otherwise it may cause flash screen problems. The callback function in useLayoutEffect executes immediately after the DOM update is complete, but runs before the browser can do any drawing, blocking the browser’s drawing.

  • A quick word about the differences:

    UseEffect is not executed until all renderings are complete

    UseLayoutEffect is executed after the browser layout and before the painting

    Its function signature is the same as useEffect, but it calls Effect synchronously after all DOM changes

    You can use it to read DOM layouts and trigger rerenders synchronously

    The update schedule within useLayoutEffect is synchronized to refresh before the browser executes the drawing

    Use the standard useEffect whenever possible to avoid blocking view updates

useRef

  • CreateRef is used for class components and React elements, and useRef is used for function components

  • UseRef returns a mutable ref object whose current property is initialized as the passed parameter (initialValue). It is not only used to manage DOM refs, it is equivalent to this, and can store any variable, which is a good way to avoid the convenience of closures.

    Why useRef?

    It’s not just for managing DOM refs, it’s the equivalent of this, and can hold any variable, which is a great way to avoid the inconvenience of closures.

    How to use useRef?

    const [count, setCount] = useState<number>(0)
    const countRef = useRef<number>(count)
    Copy the code

UseRef returns a mutable ref object whose.current property is initialized as the passed parameter (initialValue). The ref object returned remains constant throughout the life of the component

Fix references –useRef returns the same ref object every time it renders (using react. createRef, refs are recreated every time the component is re-rendered)

Compare createRef – there is no difference between the two during the initialization phase, but there is a difference during the update phase.

We know that in a local function, the function’s variables are regenerated each time the function is updated. So every time we updated the component, we re-created the ref, and it was obviously not appropriate to continue using createRef, so useRef was introduced. The ref created by useRef acts as if it were a global variable defined outside the function and is not recreated as the component is updated. But when the component is destroyed, it also disappears without manual destruction

4.1 Solve the closure problem

It’s not just for managing DOM refs, it’s the equivalent of this, and can hold any variable, which is a great way to avoid the inconvenience of closures.

As mentioned earlier:

The value of state used in useEffect is fixed in useEffect and will not be changed unless useEffect refreshes and resets the value of state

const [count, setCount] = useState(0) useEffect(() => { console.log('use effect... ',count) const timer = setInterval(() => { console.log('timer... count:', count) setCount(count + 1) }, 1000) return ()=> clearInterval(timer) },[])Copy the code

The useEffect state value is fixed, and the solution is to use the useRef function:

The global scope is modified in one place and updated everywhere else.

4.2 Due to change.currentProperty does not cause the component to rerender. This property allows us to get the previous value of the state:

const Counter = () = > {
  const [count, setCount] = useState<number>(0)
  const preCountRef = useRef<number>(count)

  useEffect(() = > {
    preCountRef.current = count
  })

  return (
    <div>
      <p>pre count: { preCountRef.current }</p>
      <p>current count: { count }</p>
      <button onClick={()= >> add setCount (count + 1)}</button>
    </div>)}Copy the code

As you can see from the result, the previous value of the state is always displayed:

pre count: 4

current count: 5
Copy the code

useMemo

5.1 Why Use useMemo?

You know from useEffect that you can influence the execution of certain functions by passing parameters to it. React checks to see if these parameters have changed, and does so only if there is a difference.

UseMemo does something similar, assuming there are a large number of methods and you only want to run them when their parameters change, not every time a component is updated, you can use useMemo for performance optimization.

Remember that functions passed into useMemo are executed during rendering. Please do not perform non-rendering operations inside this function. Operations such as side effects are used by useEffect, not useMemo.

5.2 How do I Use useMemo?

function changeName(name) {
  return name + 'Do something to name and return a new name'
}

const newName = useMemo(() = > {
	return changeName(name)
}, [name])

Copy the code

5.3 Scenario Examples

5.3.1. Routine use, avoiding repeated execution of unnecessary methods:

Let’s start with a very simple example. Here’s the code that doesn’t use useMemo yet:

import React, { useState, useMemo } from 'react'

/ / the parent component
const Example = () = > {
  const [time, setTime] = useState<number>(0)
  const [random, setRandom] = useState<number>(0)

  return (
    <div>
      <button onClick={()= >SetTime (new Date().getTime())}> Get the current time</button>
      <button onClick={()= >SetRandom (math.random ())}> Gets the current random number</button>
      <Show time={time}>{random}</Show>
    </div>
  )
}

type Data = {
  time: number
}

/ / child component
const Show:React.FC<Data> = ({ time, children }) = > {
  function changeTime(time: number) :string {
    console.log('changeTime excuted... ')
    return new Date(time).toISOString()
  }

  return (
    <div>
      <p>Time is: { changeTime(time) }</p>
      <p>Random is: { children }</p>
    </div>)}export default Example

Copy the code

In this example, the changeTime method in the
component executes whether you click on the get current time button or the get current random number button.

In fact, clicking on the get current random number button will only change the children parameter, but our changeTime will also be re-executed due to the re-rendering of the child component, which is unnecessary and consumes irrelevant performance.

Modify our
subcomponent with useMemo:

const Show:React.FC<Data> = ({ time, children }) = > {
  function changeTime(time: number) :string {
    console.log('changeTime excuted... ')
    return new Date(time).toISOString()
  }

  const newTime: string = useMemo(() = > {
    return changeTime(time)
  }, [time])

  return (
    <div>
      <p>Time is: { newTime }</p>
      <p>Random is: { children }</p>
    </div>)}Copy the code

The changeTime function will be executed only if you click on the current time, and clicking on the current random number will no longer trigger the function.

5.3.2. UseEffect is different from useMemo
  • UseEffect is inTriggered when the DOM changesUseMemo inTriggered before DOM renders
  • UseMemo is inTriggered before DOM updateAs it is officially said, the analogy lifecycle is shouldComponentUpdate
  • UseEffect can help us in theAfter the DOM update is complete, some side effects are performedData fetching, setting up subscriptions, and manually changing the DOM in the React component
  • Not inside the useMemo functionPerform operations unrelated to rendering, such asOperations such as side effects fall under the category of useEffectInstead of useMemo
  • Used in useMemoSetState and you'll see that it creates an endless loopAnd there are warnings because useMemo isIn the renderYou operate in itDOMAfter that, it will trigger againmemo

You may be wondering, can’t useMemo do what it does with useEffect? The answer is no! If you add the following code to a child component:

const Show:React.FC<Data> = ({ time, children }) = > {
	/ /...
  
  useEffect(() = > {
    console.log('effect function here... ')
  }, [time])

  const newTime: string = useMemo(() = > {
    return changeTime(time)
  }, [time])
	/ /...
}

Copy the code

You’ll notice that the console will print the following:

> changeTime excuted...
> effect function here.Copy the code

As we said at the beginning: functions passed into useMemo are executed during rendering. Here we have to mention react. memo, which implements the Pure function of the entire component:

const Show:React.FC<Data> = React.memo(({ time, children }) => {... }Copy the code

The difference between useMemo and React. Memo is that useMemo in some cases does not want the component to perform shallow comparisons to all props, but only to implement local Pure functions, that is, only to compare specific props and decide whether to update locally or not.

5.3.3. UseMemo and React.memo

React.memo

  • The parcelThe react componentsIf the props from the parent component are not changed, the child component will not be rendered
  • The second argument can be passed as a judgeisEqual, availableprePropsandpropsReturns a Boolean value to determine whether to update the render component

useMemo

  • useMemoCan be used for processingFiner granularityIf a part of the component is cached, the callback will only be executed if the second parameter is updated.Get the latest variable/component, otherwise unchanged.
  • useCallbackThe principle is the same, the difference is, it’s to avoid double definition of the function, one kindCaching of functions
5.3.4. Use useMemo appropriately (not cost-effective for simple type calculations)

UseMemo may not be cost-effective when we are dealing with very simple base-type calculations.

const a = 1;
const b = 2;

const c = useMemo(() = > a + b, [a, b]);
Copy the code

In the example above, what is the cost of calculating a+b? Or record a/ B and compare whether a/ B changes are costly?

Obviously a plus B consumes less.

const a = 1;
const b = 2;
const c = a + b;
Copy the code

You can do this on your own, so if it’s a simple, basic type of calculation, don’t use useMemo

useCallback

6.1 useCallback Does not improve performance in most scenarios

UseCallback can remember functions and avoid repeated generation of functions, so that functions can be passed to child components without repeated rendering, improving performance.

const someFunc = useCallback(() = >{ doSomething(); } []);return <ExpensiveComponent func={someFunc} />
Copy the code

So with that in mind, when I started, whenever it was a function, I added a useCallback.

However, we should note that there is another requirement to improve performance. The child component must use shouldComponentUpdate or React. Memo to repeat rendering ignoring the same parameters.

If the ExpensiveComponent component is just a generic component, it does not have any use. Like this:

const ExpensiveComponent = ({ func }) = > {
  return (
    <div onClick={func}>
        hello
    </div>)}Copy the code

ExpensiveComponent must be covered with react. Memo to avoid repeated rendering with unchanged parameters and improve performance.

const ExpensiveComponent = React.memo(({ func }) = > {
  return (
    <div onClick={func}>
        hello
    </div>)})Copy the code

So, useCallback is going toshouldComponentUpdate/React.memoFor supporting use. Of course, I recommend that you leave performance tuning out of general projects, that is, don’t use useCallback, unless you have a very complex component that can be used alone.

6.2 useCallback makes code less readable

I’ve seen some code that looks something like this with useCallback:

const someFuncA = useCallback((d, g, x, y) = > {
   doSomething(a, b, c, d, g, x, y);
}, [a, b, c]);

const someFuncB = useCallback(() = > {
   someFuncA(d, g, x, y);
}, [someFuncA, d, g, x, y]);

useEffect(() = >{
  someFuncB();
}, [someFuncB]);
Copy the code

In the above code, variables are passed layer by layer, and it can be a headache to determine which variable changes trigger useEffect execution.

I wish I didn’t use useCallback and just wrote the function naked:

const someFuncA = (d, g, x, y) = > {
   doSomething(a, b, c, d, g, x, y);
};

const someFuncB = () = > {
   someFuncA(d, g, x, y);
};

useEffect(() = >{ someFuncB(); }, [...]. );Copy the code

6.3 Differences between useCallback and useMemo

  • useMemouseCallbackThe parameters received are the same, they are executed after their dependencies have changed, and they return cached values, with the differenceuseMemoReturns the result of the function’s execution,useCallbackThe function is returned.
const renderButton = useCallback(
     () = > (
         <Button type="link">
            {buttonText}
         </Button>
     ),
     [buttonText]    // renderButton is rerendered when buttonText changes
);

Copy the code
  • UseMemo returns a valueTo avoid costly calculations on every render
const result = useMemo(() = > {
    for (let i = 0; i < 100000; i++) {
      (num * Math.pow(2.15)) / 9;
    }
}, [num]);
Copy the code

Personal advice: Don’t use useCallback if you have nothing to do with it. Note that useCallback can be optimized using useMemo for some computation operations.

6.4 Using useMemo, React. Memo and useCallback, let’s look at a specific example:

  • The parent component will have oneValue passed to child componentsIf other values of the parent component change, the child component will also be rendered several times, resulting in performance waste; UseMemo passes the parent component toThe values of the child components are cachedThe child component is rerendered only when the state of the second parameter in the useMemo changes
  • UseMemo is used for thisCaches the result of the execution of this functionIs recalculated only if the dependency changes
Note that useMemo caches the result of the function's execution and only retracks it if [count, price] changesconst Parent = () = > {
    const [count, setCount] = useState(0);
    const [color,setColor] = useState("");
    const [price,setPrice] = useState(10);
    const handleClick = () = > {
        setCount(count + 1);
    }
    const getTotal = useMemo(() = >{
        console.log("getTotal exec ...") 
        return count * price
    },[count, price])
    
    return (<div>
        <div> <input onChange={(e)= > setColor(e.target.value)}/></div>
        <div> <input value={price}  onChange={(e)= > setPrice(Number(e.target.value))}/></div>
        <div> {count} <button onClick={handleClick}>+ 1</button></div>
        <div> {getTotal}</div>
    </div>)}Copy the code

memo

  • A change in the Parent component’s name attribute or text attribute causes the Parent function to be reexecuted, so even if the child component is passed without any changes or even if the child component doesn’t depend on any props properties,Causes child components to be re-rendered
  • When using memo to wrap child components,Subcomponents are rerendered only if props changesTo improve performance
const Child = memo((props: any) = > {
    console.log("Sub-component update..."); // Subcomponents are rerendered only when the props property changes and the name property changes
    return (
        <div>
            <h3>Child components</h3>
            <div>text:{props.name}</div>
            <div>{new Date().getTime()}</div>
        </div>)})const Parent = () = > {
    const [text, setText] = useState("")... ... <Child name ={text}/> }Copy the code
  • Use the memoAPI to cache components
import React, { memo } from "react"
const CacheComponent = memo(() = > {
  return <div>^ ^</div>  
})

Copy the code

useCallback

  • The parent component will have one【 Method 】 Pass to child componentsIf other states of the parent component change, the child component will followRender times, will cause performance waste; Usecallback is passed to the parent componentSubcomponent methods are cached, only if the state of the second parameter in the USecallback changesTo render
  • But if passed the propsContains the function, the parent component is re-rendered each timeIt's all about creating new functions, so the transfer function subcomponent will stillTo renderEven though the content of the function is still the same, we want to putFunctions are also cached, thus introducing the useCallback
const Child = memo((props: any) = > {
    console.log("Sub-component update..."); // The Parent function has something changed, the Child is re-executed, handleInputChange already points to the new function instance, so the Child component is still refreshed
    return (
        <div>
            <div>text:{props.name}</div>
            <div> <input onChange={props.handleInputChange} /></div>
        </div>)})const Parent = () = > {
    const [text, setText] = useState("")
    const [count, setCount] = useState(0)
    const handleInputChange =useCallback((e) = > {
         setText(e.target.value )
    },[]) 
    return (<div>... ...<Child name={text} handleInputChange={handleInputChange}/>
    </div>)}Copy the code
  • UseCallback is used to cache functions. The function is re-executed only when the dependency changes. The function in the parent component is passed as props to the child component.Functions as props also generate new instances, resulting in the refresh of the child component using useCallback can cache the function.It should be used with the memo
1- handleInputChange is wrapped with useCallback in the Parent component2- Currently handleInputChange does not depend on any items, so handleInputChange is only called once during initialization and is cachedconst handleInputChange =useCallback((e) = > {
     setText(e.target.value )
},[]) 

3- Add count to the dependency. If count changes, a new function is generated, changing the value of count inside the functionconst handleInputChange =useCallback((e) = > {
     setText(e.target.value )
},[count]) 
Copy the code

useContext

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 < MyContext.provider > value prop of the upper-layer component closest to the current component

When the most recent <MyContext.Provider> update is made to the component’s upper layer, the Hook triggers a rerender and uses the latest context value passed to MyContext Provider

UseContext (MyContext) is equivalent to static contextType = MyContext or < myContext.consumer > in the class component

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

const context = useContext(Context)
Copy the code

UseContext, as its name suggests, uses React Context in a Hook manner. In short, it is a producer-consumer mode

import React, { useContext, useState, useEffect } from "react";
const ThemeContext = React.createContext(null);
const Button = () => {
  const { color, setColor } = useContext(ThemeContext);
  useEffect(() => {
    console.info("Context changed:", color);
  }, [color]);
  const handleClick = () => {
    console.info("handleClick");
    setColor(color === "blue" ? "red" : "blue");
  };
  return (
    <button
      type="button"
      onClick={handleClick}
      style={{ backgroundColor: color, color: "white" }}
    >
      toggle color in Child
    </button>
  );
};
// app.js
const Ceshi2 = () => {
  const [color, setColor] = useState("blue");

  return (
    <ThemeContext.Provider value={{ color, setColor }}>
      <h3>
        Color in Parent: <span style={{ color: color }}>{color}</span>
      </h3>
      <Button />
    </ThemeContext.Provider>
  );
};

export default Ceshi2

Copy the code

useReducer

  • UseReducer is like Reducer in Redux, which is a syntactic sugar for hooks
  • UseState is internally implemented by the useReducer
  • UseState alternative, which receives a Reducer of the form (state, action) => newState and returns the current state and its accompanying dispatch method
  • UseReducer can be more useful than useState in some situations, such as when the state logic is complex and contains multiple subvalues, or when the next state depends on the previous state
let initialState = 0; // if you want the initialState to be a {number:0} // you can pass a function like this in the third argument ()=>({number:initialState}) // this function is an lazy initialization function that can be used for complex calculations, Then return the final initialState const [state, dispatch] = useReducer(Reducer, initialState, init); const initialState = 0; function reducer(state, action) { switch (action.type) { case 'increment': return {number: state.number + 1}; case 'decrement': return {number: state.number - 1}; default: throw new Error(); } } function init(initialState){ return {number:initialState}; } function Counter(){ const [state, dispatch] = useReducer(reducer, initialState,init); return ( <> Count: {state.number} <button onClick={() => dispatch({type: 'increment'})}>+</button> <button onClick={() => dispatch({type: 'decrement'})}>-</button> </> ) }Copy the code

Why use useReducer? For example, have you ever wondered what it would feel like to write a lot of useState in a component, like this code:

const [name, setName] = useState(''); // const [PWD, setPwd] = useState(" "); // password const [isLoading, setIsLoading] = useState(false); Loading const [error, setError] = useState(" "); Const [isLoggedIn, setIsLoggedIn] = useState(false); // Whether to log inCopy the code

UseState version login

Let’s take a look at the general implementation of the login page using useState:

function LoginPage() { const [name, setName] = useState(''); // const [PWD, setPwd] = useState(" "); // password const [isLoading, setIsLoading] = useState(false); // Whether to display loading, sending request const [error, setError] = useState("); Const [isLoggedIn, setIsLoggedIn] = useState(false); // whether to login const login = (event) => {event.preventdefault (); setError(''); setIsLoading(true); login({ name, pwd }) .then(() => { setIsLoggedIn(true); setIsLoading(false); }). Catch ((error) => {// Login failure: display error information, clear the input box user name, password, clear the loading flag setError(error. Message); setName(''); setPwd(''); setIsLoading(false); }); } return (// return page JSX Element)}Copy the code

In the previous Demo, we defined five states to describe the state of the page. In the login function, a series of complex state Settings are carried out when the login succeeds or fails. You can imagine that as the requirements become more complex and more states are added to the page, more setStates are scattered all over the place, and it is easy to set up errors or omissions. Maintaining such old code is a nightmare.

UseReducer version login

Let’s see how to use useReducer to modify this code. First, a brief introduction to useReducer.

    const [state, dispatch] = useReducer(reducer, initState);
Copy the code

The useReducer accepts two arguments:

The first parameter, the Reducer function, is exactly what we introduced in our last article. Second argument: initialization state. The return values are the latest state and dispatch functions (used to trigger the Reducer function to calculate the corresponding state). For complex state operation logic, nested state objects are recommended to use useReducer.

As abstract as it sounds, let’s start with a simple example:

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 (<> // useReducer will rerender Count according to the dispatch action, rerender Count: <button onClick={() => dispatch({type: 'increment'})}>+</button> <button onClick={() => dispatch({type: 'decrement'})}>-</button> </> ); }Copy the code

Use useReducer to modify the login demo.

const initState = { name: '', pwd: '', isLoading: false, error: '', isLoggedIn: false, } function loginReducer(state, action) { switch(action.type) { case 'login': return { ... state, isLoading: true, error: '', } case 'success': return { ... state, isLoggedIn: true, isLoading: false, } case 'error': return { ... state, error: action.payload.error, name: '', pwd: '', isLoading: false, } default: return state; } } function LoginPage() { const [state, dispatch] = useReducer(loginReducer, initState); const { name, pwd, isLoading, error, isLoggedIn } = state; const login = (event) => { event.preventDefault(); dispatch({ type: 'login' }); login({ name, pwd }) .then(() => { dispatch({ type: 'success' }); }) .catch((error) => { dispatch({ type: 'error' payload: { error: error.message } }); }); } return (// return page JSX Element)}Copy the code

At first glance, useReducer’s code is longer, but it is clear that the second version has better readability and we can understand the logic of state changes more clearly.

As you can see, the login function now more clearly expresses the user’s intent, starting with login, login SUCCESS, and login error. The LoginPage does not need to care about how to deal with these behaviors. That is what the loginReducer needs to care about, performance and business separation.

Another benefit is that all state processing is centralized, giving us more control over state changes and making it easier to reuse state logic change code, such as dispatch({type: ‘error’}) in other functions that also need to trigger the login error state.

UseReducer allows us to separate what from how. Dispatch ({type: ‘login’}); dispatch({type: ‘login’}); ‘logout’}), all how-related codes are maintained in reducer, and the component only needs to think about What, so that our code can be more clear like the user’s behavior.

Customize the Hook

  • Custom hooks are more of a convention than a feature. If the function name begins with use and it calls another Hook, it is a custom Hook. Generally I classify hooks into these categories

util

As the name implies, utility classes such as useDebounce, useInterval, useWindowSize, and so on. For example, useWindowSize

import React, { useState, useCallback, useEffect } from "react";

export const useWinSize = () = > {
  // 1. Use useState to initialize window size state
  const [size, setSize] = useState({
    width: document.documentElement.clientWidth,
    height: document.documentElement.clientHeight
  });

  const changeSize = useCallback(() = > {
    // useCallback caches functions and throttles them
    setSize({
      width: document.documentElement.clientWidth,
      height: document.documentElement.clientHeight }); } []);// 2. Use useEffect to listen for resize events at component creation and reset state at resize (use useCallback throttling)
  useEffect(() = > {
    // Bind once the page listener event component is unbound when it is destroyed
    window.addEventListener("resize", changeSize);
    return () = > {
      window.removeEventListener("resize", changeSize); }; } []);return size;
};
Copy the code

Using this custom hook within a component:

import React from "react";
import { useWinSize } from ".. /hooks";

export default() = > {const size = useWinSize();

  return (
    <div>Page size: '{size.width}*{size.height}</div>
  );
};
Copy the code

API

For example, if we have a public city list interface, we can put it in global public when we use Redux, otherwise we may need to copy and paste it. With hooks we just need to use them and then reuse them elsewhere

import { useState, useEffect } from 'react';
import { getCityList } from '@/services/static';
const useCityList = (params) = > {
   const [cityList, setList] = useState([]);
   const [loading, setLoading] = useState(true)
   const getList = async() = > {const { success, data } = await getCityList(params);
       if (success) setList(data);
       setLoading(false)}; useEffect(() = >{getList(); }, []);return {
       cityList,
       loading
   };
};
export default useCityList;
// bjs
function App() {
   // ...
   const { cityList, loading } = useCityList()
   // ...
}
Copy the code

logic

Logic class, for example, we have a click user profile picture to follow the user or unfollow the logic, may be used in the comment list, user list, we can do this

import { useState, useEffect } from 'react';
import { followUser } from '@/services/user';
const useFollow = ({ accountId, isFollowing }) = > {
    const [isFollow, setFollow] = useState(false);
    const [operationLoading, setLoading] = useState(false)
    const toggleSection = async () => {
        setLoading(true)
        const { success } = await followUser({ accountId });
        if(success) { setFollow(! isFollow); } setLoading(false)}; useEffect(() = > {
            setFollow(isFollowing);
        },
        [isFollowing],
    );
    return {
        isFollow,
        toggleSection,
        operationLoading
    };
};
export default useFollow;
Copy the code

conclusion

First of all, as a user who fully embraces React Hooks, I fully appreciate the optimizations that React Hooks bring. But it has to be said that Hooks are a double-edged sword in many cases and have a number of caveats when they are used.

Note: The above is my opinion on React Hooks, please point out if anything is wrong.