Basic use of Hooks

A problem fixed by Hooks

  1. A functional component cannot have its own state. Function components before hooks are stateless and get the state of the parent component through props, but hooks provide useState to maintain state inside function components.
  2. Function components cannot listen to the lifecycle of a component. UseEffect aggregates multiple lifecycle functions.
  3. The class component life cycle is complex (it changed from version 15 to version 16)
  4. Class component logic is hard to reuse (HOC Render props)

Hooks compare the benefits of class

  1. It’s a little bit more concise

The class components

class ExampleOfClass extends Component {
  constructor(props) {
    super(props)
    this.state = {
      count: 1
    }
  }
  handleClick = () = > {
    let { count } = this.state
    this.setState({
      count: count+1})}render() {
    const { count } = this.state
    return (
      <div>
        <p>you click { count }</p>
        <button onClick={this.handleClick}>Click on the</button>
      </div>)}}Copy the code

hooks

function ExampleOfHooks() {
    const [count, setCount] = useState(0)
    const handleClick = () = > {
        setCount(count + 1)}return (
      <div>
        <p>you click { count }</p>
        <button onClick={handleClick}>Click on the</button>
      </div>)}Copy the code
  1. Business code is more aggregated

When using class components, it is common for a function to appear in two lifecycle functions, which can sometimes be forgotten if written separately. Such as:

let timer = null
componentDidMount() {
    timer = setInterval(() = > {
        // ...
    }, 1000)}// ...
componentWillUnmount() {
    if (timer) clearInterval(timer)
}
Copy the code

Since adding the timer and scavenger is in two different lifecycle functions, there may be a lot of other business code in between, so it is possible to clear the timer, if not adding a scavenger function when the component is unloaded, it may cause a memory leak, and the network is always requesting it.

But using hooks makes code easier to manage, easier to forget.

useEffect(() = > {
    let timer = setInterval(() = > {
        // ...
    }, 1000)
    return () = > {
        if (timer) clearInterval(timer)
    }
}, [/ /... ] )
Copy the code
  1. Logic reuse convenience

React hooks provide custom multiplexing logic for class components using both render props and HOC.

Take the logical reuse of the location of the mouse on the page as an example.

Class component render props

import React, { Component } from 'react'

class MousePosition extends Component {
  constructor(props) {
    super(props)
    this.state = {
      x: 0.y: 0
    }
  }

  handleMouseMove = (e) = > {
    const { clientX, clientY } = e
    this.setState({
      x: clientX,
      y: clientY
    })
  }

  componentDidMount() {
    document.addEventListener('mousemove'.this.handleMouseMove)
  }

  componentWillUnmount() {
    document.removeEventListener('mousemove'.this.handleMouseMove)
  }

  render() {
    const { children } = this.props
    const { x, y } = this.state
    return(
      <div>
        {
          children({x, y})
        }
      </div>)}}/ / use
class Index extends Component {
  constructor(props) {
    super(props)
  }

  render() {
    return (
      <MousePosition>
        {
          ({x, y}) => {
            return (
              <div>
                <p>x:{x}, y: {y}</p>
              </div>)}}</MousePosition>)}}export default Index
Copy the code

Custom hooks reuse

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

function usePosition() {
  const [x, setX] = useState(0)
  const [y, setY] = useState(0)

  const handleMouseMove = (e) = > {
    const { clientX, clientY } = e
    setX(clientX)
    setY(clientY)
  } 

  useEffect(() = > {
    document.addEventListener('mousemove', handleMouseMove)
    return () = > {
      document.removeEventListener('mousemove', handleMouseMove)
    }
  })
  return [
    {x, y}
  ]
}

/ / use
function Index() {
  const [position] = usePosition()
  return(
    <div>
      <p>x:{position.x},y:{position.y}</p>
    </div>)}export default Index
Copy the code

It is clear that using hooks is much easier to reuse logic and much clearer when used.

Hooks common API use

  1. useState

grammar

const [value, setValue] = useState(0)

This syntax is ES6’s data structure, where the first value of the array declares the state and the second value is the state-changing function.

Each frame has its own state

In my understanding, the closure method is adopted to realize the independent state of each frame.

function Example() {
  const [val, setVal] = useState(0)
  const timeoutFn = () = > {
      setTimeout(() = > {
        // The value obtained is the state of the button clicked, not the latest state
          console.log(val)
      }, 1000)}return (
      <>
          <p>{val}</p>
          <button onClick={()= >setVal(val+1)}>+</button>
          <button onClick={timeoutFn}>alertNumber</button>
      </>)}Copy the code

When the props or props state of the component is updated, the function component is re-rendered, and each rendering is independent of its props and state, without affecting other renderings.

Initial implementation of useState

let lastState
function useState(initialState) {
  lastState = lastState || initialState
  function setState(newState) {
    lastState = newState
    render()
  }
  return [lastState, setState]
}
// set state's index to 0 every time we render

function render () {
  index = 0
  ReactDOM.render(<App />.document.getElementById('root'))}Copy the code

It can be seen from the sector code that state and index are strongly correlated. Therefore, setSate can no longer be used under judgment conditions such as if and while, which will result in state update disorder.

  1. useEffect

grammar

useEffect(() = > {
    //handler function...
    
    return () = > {
        // clean side effect}},//dep... ] )

Copy the code

UseEffect receives a callback and its dependencies, which are executed when the dependency changes. UseEffect is similar to the didMount, didUpdate, and willUnmount lifecycle functions of the Class component.

Pay attention to the point

  1. UseEffect is asynchronous and not executed until the component has been rendered
  2. UseEffect’s callback number can only return a handler that clears the side effect or none
  3. The function inside useEffect is executed only once if the dependency passed in is an empty array

Implement useEffect

let lastDependencies
function useEffect(callback, dependencies) {
  if (lastDependencies) {
    letchanged = ! dependencies.every((item, index) = > item == lastDependencies[index])
    if (changed) {
      callback()
      lastDependencies = dependencies
    }
  } else {
    // First render
    callback()
    lastDependencies = dependencies
  }
}
Copy the code
  1. UseMemo, useCallback

The useMemo and useCallback are used to reduce the number of component updates and optimize component performance.

  1. UseMemo receives a callback function and its dependencies, and reexecutes the callback function only if the dependency changes.
  2. UseCallback receives a callback function as well as the dependency and returns the intended version of the function, recalculating the new meorize version only if the dependency changes again

grammar

const memoDate = useMemo(() = > data, [//dep... ] )
const memoCb = useCallback(() = > {/ /... }, [//dep...] )
Copy the code

PureComponent should perform a shouldUpdate comparison to determine if it needs to be updated. We usually use React. Memo for function components. With react hooks, however, the page will be rerendered even if react. memo is used because each render update is independent.

For example, if the name value of a child component is changed, the child component is also re-rendered because the parent component generates a new value each time it is updated (the addAge function changes).

function Parent() {
  const [name, setName] = useState('cc')
  const [age, setAge] = useState(22)

  const addAge = () = > {
    setAge(age + 1)}return (
    <>
      <p>The parent component</p>
      <input value={name} onChange={(e)= > setName(e.target.value)} />
      <p>age: {age}</p>
      <p>-------------------------</p>
      <Child addAge={addAge} />
    </>)}const Child = memo((props) = > {
  const { addAge } = props
  console.log('child component update')
  return (
    <>
      <p>Child components</p>
      <button onClick={addAge}>click</button>
    </>)})Copy the code

Optimize using useCallback

function Parent() {
  const [name, setName] = useState('cc')
  const [age, setAge] = useState(22)

  const addAge = useCallback(() = > {
    setAge(age + 1)
  }, [age])

  return (
    <>
      <p>The parent component</p>
      <input value={name} onChange={(e)= > setName(e.target.value)} />
      <p>age: {age}</p>
      <p>-------------------------</p>
      <Child addAge={addAge} />
    </>)}const Child = memo((props) = > {
  const { addAge } = props
  console.log('child component update')
  return (
    <>
      <p>Child components</p>
      <button onClick={addAge}>click</button>
    </>)})Copy the code

The memorize function is regenerated only if the useCallback’s dependencies change. So when you change the state of name to addAge it doesn’t change.

Implement useMemo, ueCallBack

Main function: cache variable values, so return the return value of a function

let lastMemo
let lastMemoDependencies
function useMemo(callback, dependencies) {
  if (lastMemoDependencies) {
    // Render when update
    // Determine whether the dependency has changed
    letchanged = ! dependencies.every((item, index) = > item == lastMemoDependencies[index])
    if (changed) {
      lastMemo = callback()
      lastMemoDependencies = dependencies
    }
  } else {
    / / initialization
    lastMemo = callback()
    lastMemoDependencies = dependencies
  }
  return lastMemo
}
Copy the code

Implement useCallBack

Main function: cache function

let lastCallback
let lastCallbackDependencies
function useCallback(callback, dependencies) {
  if (lastCallbackDependencies) {
    // Render when update
    // Determine whether the dependency has changed
    letchanged = ! dependencies.every((item, index) = > item == lastCallbackDependencies[index])
    if (changed) {
      lastCallback = callback
      lastCallbackDependencies = dependencies
    }
  } else {
    / / initialization
    lastCallback = callback
    lastCallbackDependencies = dependencies
  }
  return lastCallback
}
Copy the code
  1. useRef

UseRef is similar to react. CreateRef.

const node = useRef(initRef)
Copy the code

UseRef returns a mutable ref object. The current property is initialized as the passed argument (initRef).

It works on the DOM

const node = useRef(null)
<input ref={node}/>
Copy the code

This DOM element can be accessed through the Node.current attribute

It is important to note that the objects created by useRef remain the same throughout the life of the component, which means that every time a function component is re-rendered, the ref object is returned. (Using react.createref, refs are re-created each time the component is re-rendered.)

The realization of the useRef

let lastRef
function useRef(initialRef) {
  lastRef = lastRef || initialRef
  return {
    current: lastRef
  }
}
Copy the code
  1. useReducer

The useReducer is similar to reducer in Redux

grammar

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

The useReducer passes in a calculation function and initializes the state, similar to redux. The state returned by the useReducer can be accessed, and the state can be modified through dispatch.

const initstate = 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 Counter(){
    const [state, dispatch] = useReducer(reducer, initstate);
    return (
        <>
          Count: {state.number}
          <button onClick={()= > dispatch({type: 'increment'})}>+</button>
          <button onClick={()= > dispatch({type: 'decrement'})}>-</button>
        </>)}Copy the code

Custom useReducer

let lastState
function useReducer(reducer, initialState) {
  lastState = lastState || initialState
  function dispatch(action) {
    lastState = reducer(lastState, action)
    render()
  }
  return [lastState, dispatch]
}
Copy the code
  1. useContext

UseContext makes it easier to get the context provided by the upper-layer component

The parent component

import React, { createContext, Children } from 'react'
import Child from './child'

export const MyContext = createContext()

export default function Parent() {

  return (
    <div>
      <p>Parent</p>
      <MyContext.Provider value={{name: 'cc', age: 21}} >
        <Child />
      </MyContext.Provider>
    </div>)}Copy the code

Child components

import React, { useContext } from 'react'
import { MyContext } from './parent'

export default function Parent() {
  const data = useContext(MyContext) // Get the context provided by the parent component
  console.log(data)
  return (
    <div>
      <p>Child</p>
    </div>)}Copy the code

Using the step

  • The parent component is created and exportedcontext:export const MyContext = createContext()
  • Parent component useproviderandvalueProvide value:<MyContext.provide value={{name: 'cc', age:'22'}}/>
  • The child component imports the parent componentcontext: import {MyContext} from './parent'
  • Get the value provided by the parent component:const data = useContext(MyContext)

However, in most cases we do not recommend using the context because it increases the coupling of the component

Use hooks to simplify code. Custom hooks make it easy to reuse logic and get rid of the this problem of class components, but use them with care because they cause closure problems.