Added Hooks in react 16.8.

What are Hooks?

Which is used in the following ways:

  • Enhancements to functional components
  • Let functional components store state
  • Can have the ability to deal with side effects

Let developers implement the same functionality in function components without using class components. React officially advocates using function components, so it is necessary to learn Hooks

What are side effects? As long as it’s not code that converts data into views, it’s a side effect. Getting DOM elements, setting events for DOM elements, setting timers, making Ajax requests, and so on are side effects. In class components, these side effects are often handled through lifecycle functions

Class component deficiencies

  1. Lack of logical reuse mechanism

In order to reuse logic and increase components without actual rendering effect, the module level display rate is increased by ten modules, which increases the difficulty of debugging and reduces the operating efficiency

  1. Class components can often become too complex to maintain

Split a set of coherent business logic into multiple lifecycle functions, where there are multiple unrelated business logic within a lifecycle function

  1. The class member methods do not guarantee the correctness of the this pointer

Use of React Hooks

React Hooks are a set of hook functions that enhance functional components. React Hooks provide different functions.

  • useState()
  • useEffects()
  • useReducer()
  • useRef()
  • useCallback()
  • useContext()
  • useMemo()

useState

Its usage and comparison with class components can be described in detail on the official website.

The advantages of using function components as opposed to class components are:

  • It’s cleaner, it’s less code
  • You don’t have to use this

An example of using useState:

// Introduce the hook function useState
import { useState } from "react";
function App() {
  // Call useState to add an initial value for count
  // Returns the initial value of count, and the setCount function is used for updating
  const [ count, setCount ] = useState(0)
  return (
    <div className="App">
      <p> You  clicked {count} times </p>
      <button onClick =  {()= > setCount(count + 1)}>click me</button>
    </div>)}Copy the code

From the above code we can conclude the following two points:

  1. Accepts a unique parameter, the initial value of the state. The initial value can be any data type.

  2. The return value is an array. The array stores the state value and the method that changes the state value. The method name convention begins with set followed by the state name.

  3. Method can be called multiple times to hold different state values.


function App() {
  // Call useState to add an initial value for count
  // Returns the initial value of count, and the setCount function is used for updating
  const [ count, setCount ] = useState(0)
  // Call useState again to create a new object state
  const [ person, setPerson ] = useState({name: 'huan_zai'.age: 18})
  return (
    <div className="App">
      <p> You  clicked {count} times </p>
      <button onClick={()= > setCount(count + 1)}>click me</button>
      <p>UserName: {person.name} Age: {person.age}</p>
      <button onClick={()= > setPerson({name: 'linhuan', age: 18})} >change name</button>
    </div>)}Copy the code
  1. The argument can be a function, the initial state is whatever the function returns, and the function will only be called once, if the initial value is a dynamic value.
const [ count, setCount ] = useState(() = > 100)
Copy the code

Gets the dynamic value as the initial value

const propsCount = props.count || 0
const [ count, setCount ] = useState(propsCount)
Copy the code

Although the above code will still work, the first line of code, which will be executed every time the count is updated and the component is re-rendered, does not make sense, so we can pass useState a function as an argument that will only be executed once

const [ count, setCount ] = useState(() = > {
    return props.count || 0
  })
Copy the code

Two details about the setXXX method for setting state values:

  1. The parameter to the set status method can be either a value or a function

function handleCount() {
    // Now setCount takes a function
    setCount(count= > count + 1)}// The original setCount argument was a value
  <button onClick={() = > setCount(count + 1)}>click me</button>
  / / now
  <button onClick={handleCount}>click me</button>
Copy the code
  1. The method that sets the status-value method is itself asynchronous
function handleCount() {
    setCount(count= > count + 1)
    document.title = count
  }
Copy the code

When you click to update the count value, the title value is the same as the previous value, so you can see that the method that sets the status value method is itself asynchronous

Optimize the function so that title and count are synchronized

function handleCount() {
    setCount(count= > { 
      var newCount = count + 1
      document.title = newCount
      return newCount
    })
    
  }
Copy the code

Handwriting useState implementation

Based on the use of useState above, let’s implement it step by step.

First, we can build a basic shelf based on its input and return values:

function useState(initialState) {
  // Receive the initial state
  let state = initialState
  // Update the status
  let setState = newState= > {
    state = newState
    // re-render the view
    render()
  }
  return [state, setState]
}
Copy the code
import  ReactDOM from "react-dom";

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

Once the basic shelf is set up, we can try to invoke it and see if it works.

function App() {
  const [count, setCount] = useState(0)

  return (
    <div className="App">
      <p> You  clicked {count} times </p>
      <button onClick={()= > setCount(count + 1)}>click me</button>
      
    </div>)}Copy the code

【 Result 】 : Click the button, count does not increase and remains 0.

SetCount -> setState -> render causes the component to rerender -> App() is recalled -> useState is recalled -> state is reassigned to 0

Change the state declaration from inside useState to outside

let state = null
function useState(initialState) {
  // Receive the initial state
  state = state || initialState
  // Update the status
  let setState = newState= > {
    state = newState
    // re-render the view
    render()
  }
  return [state, setState]
}
Copy the code

So far, only to achieve the basic single call, that many calls, the above code is inadequate, therefore, the next to improve the above code, support multiple calls.

We have no idea how many times useState will be called, so we can only use arrays to store all the states and corresponding update methods

// Store status
let state = []
// Store the update method corresponding to the status
let setters = []
// Index the update method corresponding to the status
let stateIndex = 0
Copy the code

Creating a closure method on the index ensures that the state and its corresponding update method correspond exactly

function createSetter(index) {
  return function (newState) {
    state[index] = newState
    render()
  }
}
Copy the code

Reset the index after rerendering, otherwise the index will accumulate out of the array

function render() {
  // Reset the index after rerendering, otherwise the index will accumulate out of the array
  stateIndex = 0
  ReactDOM.render(<App />.document.getElementById('root'))}Copy the code

Complete code after completion:

import './App.css';
import  ReactDOM from "react-dom";
// Store status
let state = []
// Store the update method corresponding to the status
let setters = []
// Index the update method corresponding to the status
let stateIndex = 0 
function useState(initialState) {
  
  state[stateIndex] = state[stateIndex] || initialState
  setters.push(createSetter(stateIndex))
  let value = state[stateIndex]
  let setState = setters[stateIndex]
  stateIndex++
  return [value, setState]
}
function render() {
  // Reset the index after rerendering, otherwise the index will accumulate out of the array
  stateIndex = 0
   // Empty the data, otherwise the array will multiply repeatedly
  setters = []
  ReactDOM.render(<App />.document.getElementById('root'))}function createSetter(index) {
  return function (newState) {
    state[index] = newState
    render()
  }
}
function App() {
  const [count, setCount] = useState(0)
  // const [person, setPerson] = useState({name: 'huan_zai', age: '18'})
  const [name, setName] = useState('huan_zai')
  return (
    <div className="App">
      <p> You  clicked {count} times </p>
      <button onClick={()= > setCount(count + 1)}>click me</button>
      <p>UserName: {name} </p>
      <button onClick={()= > setName('linhuan')} >change name</button>
    </div>)}export default App;

Copy the code

Start debugging, then open the screen

If we click the “change Name” button, we will find that the text userName does not change after we click the “change Name” button once. We need to click it again. Try clicking the button chenge Person is the same way. What’s going on here? I print console.log(setters) from the console and debug breakpoints:

oh! My god! That’s the strangest thing I’ve ever seen in my life! Print the result of the array is not expanded 3 items, expanded, there are 6 items!! After some debugging, I just found that the App() method is called twice. The reason for this is unexpectedly:

Strict mode is turned on. In strict mode, the App method is called twice, rendering twice

useContext

What it does: Simplifies code for retrieving data when retrieving data across component hierarchies

useEffect

Function: Gives functional components the ability to handle side effects, similar to life cycle functions of classes

UseEffect is componentDidMount, componentDidUpdate, and componentWillUnmount

UseEffect (() => {}) analog => componentDidMount, componentDidUpdate

UseEffect (() => {}, []) analogy => componentDidMount

UseEffect (() => () => {}) Analogy => componentWillUnmount

Case study:

import './App.css';
import { useEffect, useState } from "react";
import  ReactDOM from "react-dom";

function App() {
  function onscroll() {
    console.log('Page scroll')}// 1. Add a scroll event to the window object
  // Add events after the mount is complete and destroy events before the component is unmounted
  useEffect(() = > {
    window.addEventListener('scroll', onscroll )
    return () = > {
      window.removeEventListener('scroll', onscroll)
    }
  }, [])
  // 2. Set a timer so that the count value increases by 1 every second
  const [count, setCount] = useState(0)
  useEffect(() = > {
   const timeId = setInterval(() = > {
     setCount(count= > {
      document.title = count 
      return count + 1})},1000)
   return () = > {
     clearInterval(timeId)
   }

  })
  return (
    <div className="App">
      <span>{ count }</span>
      <button onClick={()= >ReactDOM. UnmountComponentAtNode (document. GetElementById (" root "))} > uninstall components</button>
    </div>)}export default App;
Copy the code

UseEffect resolves the problem

  1. Categorize code by purpose (group coherent logic into the same side effect function)

  2. Simplify repetitive code and make the internal code of components clearer. UseEffect (() => {}) => componentDidMount, componentDidUpdate, useEffect (() => {}) => componentDidMount, componentDidUpdate Here just use useEffect (() => {})

UseEffect is the second parameter

Function:

  • The second argument is an empty array, indicating that the side effect function is executed only once after the mount is complete, at which point updates are not monitored
  • The second argument specifies an array of values, indicating that the side effect function is executed only if the specified data changes

UseEffect combines asynchronous functions

The argument function in useEffect cannot be an asynchronous function because useEffect returns a function that cleans up the resource. If it is an asynchronous function, it returns a Promise

Handwriting useEffect implementation

First, let’s have a basic version of the main functions:

  • UseEffect calls the callback function when there is only one argument (the callback function)
  • UseEffect when the second argument is passed, the callback function is executed only when its data changes
// Save the last data
let preArray = [] 
function useEffect(callback, depsArray) {
  // Whether callback is a function
  if(typeofcallback ! = ='function') {
    throw new Error('first params must be function!! ')}// Check if depsArray is passed
  if(typeof depsArray === 'undefined') {
    callback()
  } else {
    // Check whether it is an array
    if(Object.prototype.toString.call(depsArray) ! = ='[object Array]') throw new Error('second params must be Array! ')
    // Compare the current dependency array with the previous one to see if it has changed
    // Call the callback function if there are changes
    const hasChange = preArray.length === 0 || depsArray.every((dep, index) = > dep === preArray[index]) === false
    if(hasChange) {
      callback()
    }
    // Synchronize dependent values
    preArray = depsArray
  }
}
Copy the code

Test the

function App() {
  const [count, setCount] = useState(0)
  const [name, setName] = useState('huan_zai')
  const [person, setPerson] = useState({name: 'huan_zai'.age: '18'})
  useEffect(() = > {
    console.log('hai')
  }, [count])
  return (
    <div className="App">
      <p> You  clicked {count} times </p>
      <button onClick={()= > setCount(count + 1)}>click me</button>
      <p>UserName: {name} </p>
      <button onClick={()= > setName('linhuan')} >change name</button>
      <p>UserName: {person.name} Age: {person.age}</p>
      <button onClick={()= > setPerson({name: 'linhuan', age: '28'})} >change person</button>
    </div>)}Copy the code

As a result, the useState side effect function is triggered only when the Click Me button is clicked, as expected.

However, when we want to monitor multiple different data, the above code does not work

Such as:

useEffect(() = > {
    console.log('hai')
  }, [count])
  useEffect(() = > {
    console.log('hai name')
  }, [name])
Copy the code

Result: useEffect is executed whenever the data is changed, and this is a problem. Next we need to optimize the code

Optimized code:


// Save the last data
let preDepsArray = []
let effectIndex = 0
function useEffect(callback, depsArray) {
  // Whether callback is a function
  if(typeofcallback ! = ='function') {
    throw new Error('first params must be function!! ')}// Check if depsArray is passed
  if(typeof depsArray === 'undefined') {
    callback()
  } else {
    // Check whether it is an array
    if(Object.prototype.toString.call(depsArray) ! = ='[object Array]') throw new Error('second params must be Array! ')
    // Compare the current dependency array with the previous one to see if it has changed
    // Call the callback function if there are changes
    const preDep = preDepsArray[effectIndex]
    let hasChange = preDep ? depsArray.every((dep, index) = > dep === preDep[index]) === false : false

    if(hasChange) {
      callback()
    }
    // Synchronize dependent values
    preDepsArray[effectIndex] = depsArray
    effectIndex++
  }
}
Copy the code

Handwriting useReducer implementation

function useReducer(reducer, initialState) {
  const [ state, setState ] = useState(initialState)
  function dispatch(action) {
    const newState = reducer(state, action)
    setState(newState)
  }
  return [state, dispatch]
}
Copy the code


function App() {
 
  function reducer (state, action) {
    switch (action.type) {
      case 'increment':
          return state + 1
      case 'decrement':
          return state - 1
      default:
          return state
    }
  }
  const [count, dispatch] = useReducer(reducer, 0)

  return (
    <div className="App">
      <p> {count}  </p>
      <button onClick={()= > dispatch({type: 'increment'})}>increment</button>
      <button onClick={()= > dispatch({type: 'decrement'})}>decrement</button>
    
    </div>)}Copy the code