Detailed explanation of the use of React Hook

Posted by MLJJ on Fri, 21 Jan 2022 14:57:20 +0100

1: Introduction

Hook is a new feature of React 16.8. It allows you to use state and other React features without writing class.

hook introduction time (react version update event)
- 2013 May 29     v0.3.0        Facebook Open source React 
	.....
- 2014 October 28     v0.12.0      use BSD agreement+Additional patent license agreement- 2016 March 30     v0.14.0      Split into React and ReactDOM
- 2016 April 9     v15.0.0	     Change of mounting mode of components SVG Compatibility, etc
- 2017 September 25     v15.6.2	     Open source agreement changed to MIT
- 2017 September 26     v16.0.0      introduce Fiber
- 2019 February 6     v16.8.0      Hook introduce
- 2020 October 20     v17.0.0      Concurrent Mode,Technical transformation of the bottom layer and solving some historical burdens
Before Hook

Function component

function Welcome(props) {
  return <h1>hello, {props.name}</h1>
}

Class component

With Hook

Function components can perform the same functions as class components, and have their own state, life cycle and state control capabilities

let timer = null
export default function Clock() {

  const [date, setDate] = useState(new Date())

  useEffect(() => {
    timer = setInterval(() => {
      setDate(new Date())
    }, 1000)
    return () => {
      clearTimeout(timer)
    };
  }, [])

  return <div>
    <h1>Hello,world!</h1>
    <h2>It is {date.toLocaleTimeString()}.</h2>
  </div>
}

2: List of built-in hook APIs provided by React

useState
useEffect

useContext
useRe
useCallback
useMemo

useReducer
useLayoutEffect

useImperativeHandle
useDebugValue
useTransition
useDeferredValue

3: Hook API details

  1. useState
    Source code definition
export function useState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
  const dispatcher = resolveDispatcher();
  return dispatcher.useState(initialState);
}

Use example

const [a, setA] = useState(0)
 const [data, setData] = useState(function () {
    return JSON.parse(bigData)
  })

If you pass in a function, the function is only executed when the first component is initialized, and the calculation will not be repeated when the later component is update d. It is suitable for the optimization of scenarios where a large number of calculations are required for the initial value

  1. useEffect

Source code definition

export function useEffect(
  create: () => (() => void) | void,
  deps: Array<mixed> | void | null,
): void {
  const dispatcher = resolveDispatcher();
  return dispatcher.useEffect(create, deps);
}

Function:

  • Treatment side effects
  • Implementation lifecycle
  • Controllable particle size

Use example

useEffect, as a side effect processing function, can simulate the life cycle of componentDidMount and componentDidUpdate,

  1. useLayoutEffect

Source code definition

export function useLayoutEffect(
  create: () => (() => void) | void,
  deps: Array<mixed> | void | null,
): void {
  const dispatcher = resolveDispatcher();
  return dispatcher.useLayoutEffect(create, deps);
}

Function:

  • Treatment side effects
  • Implementation lifecycle
  • Controllable particle size

The use method is consistent with useEffect
Difference: it will block browser rendering and execute before useEffect

Usage scenario:
Do not want users to see the dom change process. There are dom update operations in the side effect callback function, which is suitable for use

Sample code
Compare the following two groups of codes, one is the useEffect used, and the other is the uselayouteeffect used

import { React } from '../adaptation'
// import { useEffect } from './react/packages/react'

const { useLayoutEffect, useEffect, useState } = React

export default function TestUseLayoutEffect(props) {
  const [count, setCount] = useState(0)
  
  const handleClick = () => {
    setCount(0)
  }

  useLayoutEffect(() => {
    if (count === 0) {
      // Time consuming operation start
      const arr = []
      for (let i = 0; i < 100000000; i++) {
        arr.push(i)
      }
      // Time consuming operation end
      setCount(Math.random())
    }
  }, [count])

  return <div >
    {count}
    <button onClick={handleClick}>change</button>
  </div>
}
import { React } from '../adaptation'
// import { useEffect } from './react/packages/react'

const { useEffect, useState } = React

export default function TestUseEffect(props) {

  const [count, setCount] = useState(0)
  const handleClick = () => {
    setCount(0)
  }

  useEffect(() => {
    if (count === 0) {
      // Time consuming operation start
      const arr = []
      for (let i = 0; i < 100000000; i++) {
        arr.push(i)
      }
      // Time consuming operation end
      setCount(Math.random())
    }
  }, [count])

  useEffect(() => {
    console.log('xxx123')
  })
  return <div >
    {count}
    <button onClick={handleClick}>change</button>
  </div>
}
  1. useRef

Source code definition

export function useRef<T>(initialValue: T): {| current: T |} {
  const dispatcher = resolveDispatcher();
  return dispatcher.useRef(initialValue);
}

Example

const { useRef } = React

export default function TestRef() {
  const myRef = useRef(null)
  const handleClick = () => {
    console.log(myRef.current)
  }
  return <div>
    <button ref={myRef}  onClick={handleClick}>click</button>
  </div>
}


useRef vs createRef()

Similar function
useRef remains referenced throughout its lifecycle. Modifying the value of useRef current does not trigger re rendering
The createRef value changes each time you re render

  1. useMemo and useCallback

Source code definition

export function useCallback<T>(
  callback: T,
  deps: Array<mixed> | void | null,
): T {
  const dispatcher = resolveDispatcher();
  return dispatcher.useCallback(callback, deps);
}

export function useMemo<T>(
  create: () => T,
  deps: Array<mixed> | void | null,
): T {
  const dispatcher = resolveDispatcher();
  return dispatcher.useMemo(create, deps);
}

useMemo avoid double counting values when updating
useCallback avoids redefining functions

Example:
In the following example, the genWelcomeStr method will not be executed no matter how many times the name is updated

import { React } from '../adaptation'

const { useMemo } = React

export default function TestMemo(props) {
  const { name, count } = props

  const genWelcomeStr = () => {
    console.log('genWelcomeStr called')
    return 'Hello,' + name
  }
  const welcomeStr = useMemo(genWelcomeStr, [name])
  // const welcomeStr = genWelcomeStr()

  return <div>
    {count}
    {welcomeStr}
  </div>
}

In the following two sets of codes, the parent-child components useCallback and useMemo cooperate. As long as the name remains unchanged, the function direction of the useCallback package will not change. The child components listen for the incoming method through useMemo. As long as the method direction remains unchanged, the calculation will not be repeated

import { React } from '../adaptation';
// import TestMemo from './TestComponents/TestMemo'
import TestUseCallback from './TestUseCallback';

const { useState, useCallback } = React

function TestUseCallbackInApp() {
  const [count, setCount] = useState(0)
  const [name, setName] = useState('bob')
  
  const handleClick = (count) => {
    setCount(++count)
  }

  const handleGenStr = useCallback(() => {
    return 'name: ' + name
  }, [name])

  return <div className="App">
    <TestUseCallback handleGenStr={handleGenStr} />
    {count}
    <button onClick={() => handleClick(count)}>add</button>
    <button onClick={() => setName('jack' + Date.now())}>changeName</button>
  </div>
}

export default TestUseCallbackInApp;

import { React } from '../adaptation'

const { useMemo } = React

export default function TestUseCallback(props) {
  const { handleGenStr } = props

  const renderStr = useMemo(() => {
    console.log('handleGenStr called')
    return handleGenStr()
  }, [handleGenStr])

  console.log('child comp render')
  return <div >
    {renderStr}
  </div>
}

useMemo and useCallback are suitable for optimization in extreme situations such as large data volume. Otherwise, the optimization effect is not great under ordinary data volume. Even due to a lot of monitoring logic, the code volume and performance become worse, and even cause bug s. The speed of the underlying engine can offset this performance difference.

  1. useReducer
    Source code definition
export function useReducer<S, I, A>(
  reducer: (S, A) => S,
  initialArg: I,
  init?: I => S,
): [S, Dispatch<A>] {
  const dispatcher = resolveDispatcher();
  return dispatcher.useReducer(reducer, initialArg, init);
}

Example

  1. useContext

Source code definition

export function useContext<T>(
  Context: ReactContext<T>,
  unstable_observedBits: number | boolean | void,
) {
  const dispatcher = resolveDispatcher();
  if (__DEV__) {
    if (unstable_observedBits !== undefined) {
      console.error(
        'useContext() second argument is reserved for future ' +
        'use in React. Passing it is not supported. ' +
        'You passed: %s.%s',
        unstable_observedBits,
        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])
          ? '\n\nDid you call array.map(useContext)? ' +
          'Calling Hooks inside a loop is not supported. ' +
          'Learn more at https://fb.me/rules-of-hooks'
          : '',
      );
    }

    // TODO: add a more generic warning for invalid values.
    if ((Context: any)._context !== undefined) {
      const realContext = (Context: any)._context;
      // Don't deduplicate because this legitimately causes bugs
      // and nobody should be using this in existing code.
      if (realContext.Consumer === Context) {
        console.error(
          'Calling useContext(Context.Consumer) is not supported, may cause bugs, and will be ' +
          'removed in a future major release. Did you mean to call useContext(Context) instead?',
        );
      } else if (realContext.Provider === Context) {
        console.error(
          'Calling useContext(Context.Provider) is not supported. ' +
          'Did you mean to call useContext(Context) instead?',
        );
      }
    }
  }
  return dispatcher.useContext(Context, unstable_observedBits);
}

Example


Solve the problem of function component state sharing
Layer by layer transmission is avoided

  1. other
  • useDebugValue add a debug specific name to the hook
function useMyCount(num) {
  const [ count, setCount ] = useState(0);

  useDebugValue('myHook');

  const myCount = () => {
    setCount(count + 2);
  }

  return [ count, myCount ];
}

  • useImperativeHandle restricts the exposed contents of the dom instance corresponding to ref
import { React } from '../adaptation'

const { useRef,useImperativeHandle,forwardRef } = React

const TestImperative =  forwardRef((props, ref) => {
  const myRef = useRef(null)

  useImperativeHandle(ref, () => ({
    focus: () => {
      myRef.current.focus()
    }
  }))

  return <div>
    <input ref={myRef}/>
  </div>
})

export default TestImperative
import { React } from '../adaptation';
// import TestMemo from './TestComponents/TestMemo'
import TestImperative from './TestUseImperative';

const { useEffect, useRef } = React

function TestUseImperativeInApp() {
  const myRef = useRef(null)

  useEffect(() => {
    console.log(myRef)
    myRef.current.focus()
  }, [])

  return <div className="App">
    <TestImperative ref={myRef}/>
  </div>
}

export default TestUseImperativeInApp;

Operation results

  • useTransition component delay transition hook
  • useDeferredValue listens for status, delaying over update

4: Frequently asked questions

  1. Why can't you define hook s in conditional, nested, or circular statements

This is the basic structure of hook linked list:

type Hooks = {
  memoizedState: any, 
  baseState: any, 
  baseUpdate: Update<any> | null
  queue: UpdateQueue<any> | null
  next: Hook | null, // link to the next hooks and concatenate each hook through next
}


Pseudo code describes the correspondence between code and hook data structure:

hook sets the next line by line according to the linked list during initialization. If a conditional statement is inserted halfway, the order of the linked list generated during the first initialization may be inconsistent with the order defined by the actual useState at the time of update, resulting in a bug.

  1. Can the function component with Hook completely replace the Class component?
    "It is officially recommended to use hook to write components"

  2. Can hook simulate all the life cycles of a class component? Refer to the following figure:

class componentHooks component
constructorWhen useState
getDerivedStateFromPropsuseEffect configures the dependent variables and controls the update according to the dependent old and new comparison
shouldComponentUpdateReact.memo caches the value of render and configures dependency
renderThe content returned by the function
componentDidMountuseEffect dependency is configured as an empty array
componentDidUpdateuseEffect controls the execution only at the time of update by using rect to cache a variable that records the first update
componentWillUnmountThe first parameter of useEffect returns a function that will be executed when unmount

Topics: Front-end React