background

To those of us who are used to the front end of the life cycle, there are some things that are difficult to understand. How to call component lifecycle hooks? 3 Why and how to manually optimize performance? . React Hooks use scenarios, react performance optimizations, and some concepts introduced by React. Learn from the use of Hooks in React.

side effect

React is a bad effect and should be avoided. However, there is a very good example to explain side effect:

let counter = 0

// with side effect
function fn1() {
  ++counter
  return counter
}

// without side effect
function fn2() {
  return counter + 1
}
Copy the code

Fn1 and fn2 both return the value of counter+1, but fn1 creates a side effect. Side effect(not necessarily unexpected, not necessarily negative, or even expected behavior). In the React example, for example, we changed the value of count by clicking on the event, resulting in a side effect:

import React, { useState } from 'react'

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

React component rendering process

Render the React functional Component (myComponent); render the react function (myComponent); 2. Reconciliation: virtualDom before and after; 3 commit: update the changed DOM on the premise of 2; The following scenario will cause re-render(re-execute the above rendering process) to update the DOM (note that dom updates are related to virtualDom, and even re-render may not update the DOM):

1 Modification of props; 2 state update; Update the context object. 4 re-render the parent element;Copy the code

useState

The simplest and most commonly used hook is to use useState to get an initial state value and a method to modify the state. Changing state triggers the component’s re-render(not if the value is the same as before) to update the DOM. Re-render optimization is one of the most important performance optimizations of the React framework itself, which will be explained in more detail later.

useEffect

React provides us with a side effect hook. UseEffect takes two arguments, the first of which may contain the side effect function, and the second is the dependency that triggers the first argument: 1 Obtain server data (componentDidMount, triggered after initial rendering), including LLDB

const [name, setName] = useState('')

useEffect(() => {
  //get async data
  getData()
  .then(data => {
    // doSomething
  })
},[])
Copy the code

2 Monitor the changes of one or more states and perform corresponding actions, namely, LLDB.

const [count, setCount] = useState(0)

useEffect(() => {
  console.log(`current count is ${count}`)
}, [count])
Copy the code

Destroy some component-related events (e.g.) before component destruction (e.g Polling, rolling event listening, etc.), LLDB.

const [count, setCount] = useState(0)

useEffect(() => {
  let timmer = setInterval(() => {
    // doSomething
  }, 1000)
  return () => {
    clearInterval(timmer)
  }
}, [count])
Copy the code

LLDB is expected to be triggered every time a component is updated.

const [count, setCount] = useState(0)

useEffect(() => {
  console.log(`current count is ${count}`)
})
Copy the code

useContext

UseContext receives the hook from the upper-layer component passing the context. React is the hook that provides communication between components. The parent component creates the createContext, initializes it, and exports it. The descendant component passes the value down through the provider. The descendant component imports the context object of the parent component and consumes the value, LLDB, through the useContext.

// Parent export const SomeContext = createContext(null) const [name, setName] = useState('vb') const [count, setCount] = useState(0) return ( <SomeContext.Provider value={{name, Count}}> <Children /> </SomeContext.Provider>) import {SomeContext} from 'Parent' let {name, count} = useContext(SomeContext)Copy the code

Exporting a context object from its parent element is a very strange thing to do. You would create a Context Manager file in the parent directory to manage the context object.

useReducer

An alternative to useState. (LLDB) is a hook to modify state, another use of useState.

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>
    </>
  )
}
Copy the code

useCallback && useMemo && memo

UseCallback and useMemo are both hooks that optimize performance. The difference is that useCallback returns a Memoized function and useMemo returns a Memoized value. Memo is HOC(Higher Order Component) provided by React, which mainly provides memoized components. The reason why MEMO (which is not hooks) is because they can both be used to cache components. e.g.

// memo function Children(props) { return ( <div>my name is ${props.name}</div> ) } function areEqual(prevProps, NextProps) {// Calculate and return Boolean if(prevProps. Name === nextProps. Name) {return true} return false} Export default React. Memo (Children,areEqual) // useMemo function Children(props) {return useMemo(() => ( <div>my name is ${props.name}</div> ), [props.name]) }Copy the code

In addition, the useMemo can replace the useCallback to implement the cache function, namely, LLDB.

useCallback(fn, deps) === useMemo(() => fn, deps)
Copy the code

If you are memoized, then you are memoized. If you are memoized, you are memoized. If you are memoized, you are memoized. What is memoization? You can think of it as a cache. Memoization is too long, and it’s not just a way to optimize React. It’s a performance optimization method that can be used in all projects, and I’ve written a separate article about it in detail. 3 Why cache the function (useCallback)? I can understand the cache value, re-render does not need to execute the function /render, reduce calculation waste. But regardless of whether there is a cache, the function is always executed again, so what’s the point of caching? React to shallow dependency comparison results (LLDB).

// parent
function Parent() {
  const [name, setName] = useState('vb')
  const onChildClick = useCallback(() => {
    console.log('Children clicked')
  }, [name])

  return (
    <Child click={onChildClick}>
  )
}
// children
function Children({onChildClick}) {
  return (
    <div onClick={onChildClick}></div>
  )
}
export default React.memo(Children)
Copy the code

Without useCallback, every re-render of the parent will result in a re-render of the child, since onChildClick creates a new reference every time the parent renders. 3. Why optimization? When re-render a component, some components (or a calculation within the component, or a subcomponent) may not need to be completely reexecuted, and additional re-render will cause performance degradation. 4 Which scenarios are used respectively (useCallback, useMemo, memo)? In fact, LIKE Array objects (forEach, map, filter, etc.), I think we need to use different methods in different contexts.

Memo: Cache component; UseMemo: caches calculated values. UseCallback: cache function;Copy the code

useRef

UseRef returns a mutable ref object whose.current property is initialized as the parameter passed in. The ref object returned persists throughout the life of the component. The full life cycle means that even render will be able to read the value of the previous render, which is used in two scenarios: first, cache initialization of the calculated value of the last render, which is also very common in custom hooks. The e.g. component is used to show the value of the distance from the last time:

Function MyComponent(props) {// initialize const preCounter = useRef(0) const [distance, setDistance] = useState(0) useEffect(() => { setDistance(props.count - preCounter) preCounter.current = props.count }, []) return ({distance}</div>)}Copy the code

LLDB = ref (LLDB)

const inputRef = useRef()

return (
  <div ref={inputRef}></div>
)
Copy the code

When the DOM is updated, React sets the.current property of the ref object to the corresponding DOM node.

useImperativeHandle

UseImperativeHandle allows you to customize the instance value exposed to the parent component when using a ref. In most cases, imperative code like ref should be avoided. UseImperativeHandle should be used together with the forwardRef. (LLDB) Functional Component does not support directly mounting refs on child components.

// error: Property 'ref' does not exist on type 'IntrinsicAttributes & MyTestProps & { children? : ReactNode; }' function Parent() { const childrenDom = createRef() return ( <Children ref={childrenDom}></Children> ) }Copy the code

The forwardRef needs to create child components to accept refs forwarding, LLDB.

// children cosnt Children = forwardRef((props, ref) => { return ( <div ref={ref}>my name is vb</div> ) }) // parent function Parent() { const childrenRef = createRef()  return ( <Children ref={childrenRef}></Children> ) }Copy the code

Returning to useImperativeHandle, we can do this when we want the parent component to call the child component’s method or instance:

// children
cosnt Children = forwardRef((props, ref) => {
  useImperativeHandle(ref, () => ({
    test: () => {console.log('my name is vb')}
    myName: 'vb'
  }))
  return (
    <div ref={ref}>my name is vb</div>
  )
})
// parent
function Parent() {
  const childrenRef = useRef()
  
  useEffect(() => {
    childrenRef.current.test() // my name is vb
    console.log(childrenRef.current.myName) // vb
  }, [])

  return (
    <Children ref={childrenRef}></Children>
  )
}
Copy the code

useLayoutEffect

Similar to useEffect, there are two main differences between the two: 1. UserEffect is asynchronous, while useLayoutEffect is synchronous; 2 Differences in execution time: useEffect: executed after browser rendering is complete; UseLayoutEffect: before the browser renders and after the synchronization is complete. UseLayoutEffect blocks the rendering process and renders it after the final result is computed; UseEffect does not block, but flicker may occur (flicker may be a normal requirement, e.g Time display), but does not block the browser’s rendering process. UseEffect is recommended whenever possible. I have not used useLayoutEffect in business for the time being, and I will explain it further if I encounter it later.

useDebugValue

Install a Google plugin called React Developer Tools for easy debugging on Chrome.

custom hook

Custom hooks have only two rules: 1 use function; 2. Call React hooks on top of custom hooks. We can customize hooks for any common function that involves state (using custom hooks is more in line with react development style). 1 Obtain the value of the previous state, namely, LLDB.

function usePrevious(state) {
    const preValRef = React.useRef()
  
    React.useEffect(() => {
      preValRef.current = state
    }, [])
  
    return preValRef.current
}
Copy the code

2. We need to distinguish between general functions and custom hook functions. In hook functions, state changes will be updated to the caller, e.g Custom hooks for asynchronous requests:

function getData() { return new Promise((resolve) => { setTimeout(() => { resolve({name: 'vb'}) }, 3000) }) } function useRequest() { const [data, setData] = useState({}) useEffect(() => { getData() .then(data => { setData(data) }) }, []) return data} function MyComponent() {const data = useRequest() useEffect(() => {console.log(data.name) // Print first [data]) return (<div>{data.name}</div>)}Copy the code

Note the following: 1. When different components call the same hook, they do not share the state and are all separate instances.

Matters needing attention

1 Do not use useState/useReduce hooks to initialize the props property to a state. This is only performed at initialization, and does not change state when props is modified, which also creates an additional burden. 2. In asynchronous methods, it is important to note that multiple state changes may not be processed together; e.g.

const onClick = async () => { setLoading(true) setData(null) // batched render happen const res = await getData() setLoading(false) // render with bad state setData(res) }; console.log("render", loading, data) //out put render false null // click render true null render false null // <-- bad render call render false {time:  1545903880314} // click render true null render false null // <-- bad render call render false {time: 1545904102818}Copy the code

Source: github.com/facebook/re…

The problem record

Why do react hooks no longer implement life cycle hooks for componentWillMount, which let us request asynchronous data before rendering? React componentWillMount,vue beforeCreated, etc. We are used to get data in the initialization (e.g. react componentWillMount,vue beforeCreated), this is actually a thinking trap, habit let us think that the request data before render only once, but in fact, asynchronously obtain data, No matter how fast the network and server are, the render will still be re-rendered after the initial rendering based on asynchronous data. 3 Why does useState hook change not cause useEffect to go into an infinite loop for primitive types (the second argument is null and useEffect is executed twice), but does for objects?

Const [name, setName] = useState(") useEffect(() => {console.log(name) // side effect executes twice const [name, setName] = useState(") useEffect(() => {console.log(name) // ", 'vb elegantly-named setName (' vb')}) / / infinite loop const/name, elegantly-named setName = useState ({name: "'}) useEffect (() = > {elegantly-named setName ({name:" vb "})})Copy the code

If object is the same reference, there will be no dead loop, LLDB

let user = {name: ''}
const [name, setName] = useState(user)

useEffect(() => {
  user.name = 'vb'
  setName(user)
})
Copy the code

The problem here is whether side effect will be triggered. There is no relevant introduction article for this part. I can only read the source code (the latest version of React is all kinds of fiber and lane, and it is written by Flow, which is too difficult for me to read).

If the new state is the same as the current state, we may be able to bail out entirely. If the new state is the same as the current state, it may (along with special handling of null values) be discardedCopy the code

The Object. Is method is used for comparison. If it is a reference type, it needs to be the same reference Object; if it is a basic type, it needs to be ===. 6 Why doesn’t React implement re-render optimizations within the framework instead of developers handling them manually? Not found yet #TODO

// Possible cause 1 It takes a lot of calculation to calculate all possible re-render methods (whether the props, state, and context objects are changed), which affects react performance. Re-render is not an issue in most scenarios (virtual does prevent dom rendering from being too expensive); 2 It is possible that hooks are not developed in a way that allows partial re-render checks (such as whether updates to parent elements trigger updates to all child elements);Copy the code

7 Should I wrap all inline-functions with useCallback? I heard someone say that it’s not necessary to wrap all inline-functions with useCallback. UseCallback is just a caching function to avoid shallow comparisons causing re-render. Use usecallBack only if you need shallow comparisons and don’t want the function to cause re-render; 8 about the application scenario of useEffect, I used useEffect for serialization occasionally. e.g.

const [name, setName] = useState('')

useEffect(() => {
  setName(serialize(name))
},[name])

return (
  <div>my name is {name}</div>
)
Copy the code

Aside from causing extra re-render, the most important thing to understand is that side effect should be a scene caused by state changes.

Reference documentation

1 stackoverflow.com/questions/4… 2 A Complete Guide to useEffect: Overreacted. IO/A-complete -… 3 React Hooks: Async function in the useEffect: dev. To /danialdezfo… 4 React Hook createContext & useContext Cross-component transparent Context and performance optimization: www.ptbird.cn/react-creat… 5 React Hooks – Understanding Component Re-renders: medium.com/@guptagarud… 6 Your Guide to React. UseCallback (): dmitripavlutin.com/dont-overus…