According to the principle of react hooks, write a simple useeffect

Time:2022-5-15

According to the principle of react hooks, a simpleuseState
Then, of course, we should also implement a simple useeffect
First review the usage of useeffect

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

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

  // Similar to componentDidMount and componentDidUpdate:
  useEffect(() => {
    console.log(`You clicked ${count} times`);
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

Summary: useeffect receives two parameters. The first parameter is a callback function and the second parameter is a dependency. The callback function is executed at different stages according to different dependencies

Also preset an execution environment

let onClick;
let onChange;

function render() {
    // _ Resetting IDX to 0 is also in line with react. Each hook is updated from the hooks header node every time it is updated
    _idx = 0;
    const [count, setCount] = useState(0);
    const [name, setName] = useState("77");
    useEffect(()=>{
        console.log("effect —— count", count);
        console.log("effect —— name", name);
    }, [name])
    //Use onclick and onchange to simply simulate the update operation
    onClick = () => { setCount(count + 1) };
    onChange = (name) => { setName(name) };
}

render();
console.log("-------------");
onClick();
onClick();
console.log("-------------");
onChange("kiana")
onChange("kiana_k423")

The above code simulates one rendering, two clicks and two modifications. Meanwhile, the dependency of useeffect is name
Note: usestate is simulated in the previous article,Click to view

According to different dependencies, we can execute callback according to different situations

let _ memoizedState = []; //  Multiple hooks are stored in this array
let _ idx = 0; //  Current memoizedstate subscript
/**
 *Simulated implementation of useeffect
 *// different DEPs correspond to different situations,
    // 1.  When DEPs does not exist: every time the state is updated, a callback needs to be executed
    // 2.  When DEPs exists, but the array is empty, you only need to execute callback when mounting, that is, when rendering for the first time
    // 3.  If DEPs exists and has dependencies, the callback is executed only when the corresponding dependency is updated
 *@ param {function} callback callback function
 *@ param {array} DEPs dependency
 */
function useEffect(callback, deps) {
    //If there are no dependencies, callback is executed every time
    if(!deps) {
        callback();
    } else {
        //First, get the original dependency stored in the current location in the global hooks list according to the current subscript
        const memoizedDeps = _memoizedState[_idx];
        if(deps.length === 0) {
            //Through current_ Whether there are DEPs at the subscript position of memoizedstate to judge whether it is the first rendering
            !memoizedDeps && callback();
            //At the same time, you should also update the data of the dependencies of the current subscript of the global hooks list
            _memoizedState[_idx] = deps;
        } else {
            //If it is the first rendering, call callback directly
            //Otherwise, judge whether the dependency has been updated
            memoizedDeps && deps.every((dep, idx) => dep === memoizedDeps[idx]) || callback();
            //Update the data of the dependency of the current subscript
            _memoizedState[_idx] = deps;
        }
        _idx++;
    }
}

The results are as follows:

According to the principle of react hooks, write a simple useeffect

You can see that the results are printed only during the initial rendering and name update

In addition, let’s change the dependencies and experiment with other results:

According to the principle of react hooks, write a simple useeffect

The dependency is an empty array, and callback is only executed in the render phase
According to the principle of react hooks, write a simple useeffect

If no dependency is passed in, callback is executed every time the update is made

It seems that everything is going well here. However, the callback function of useeffect has another important feature, that is, it can return a function, which is executed in the willunmount phase.

In the improved version, the returned function of the useeffect callback is executed when the component is destroyed

The idea is also very simple, that is, during the initial rendering, the callback of each useeffect will be executed, and then if the callback execution result has a return value and the return value is a function, push it into a global effectdestroy array, and then execute the destroy function in turn when the component willunmount. The specific implementation is as follows:

const _ memoizedState = []; //  Multiple hooks are stored in this array
let _ idx = 0; //  Current memoizedstate subscript
const _ effectDestroy = []; //  Store functions returned by multiple useeffect callback functions
/**
 *Simulated implementation of useeffect
 *// different DEPs correspond to different situations,
    // 1.  When DEPs does not exist: every time the state is updated, a callback needs to be executed
    // 2.  When DEPs exists, but the array is empty, you only need to execute callback when mounting, that is, when rendering for the first time
    // 3.  If DEPs exists and has dependencies, the callback is executed only when the corresponding dependency is updated
 *@ param {function} callback callback function
 *@ param {array} DEPs dependency
 */
function useEffect(callback, deps) {
    //First, get the original dependency stored in the current location in the global hooks list according to the current subscript
    const memoizedDeps = _memoizedState[_idx];
    //If not currently, it will prove to be the first rendering, and a callback will be executed in any case
    if(!memoizedDeps) {
        const destroy = callback();
        //Update dependencies at the same time
        _memoizedState[_idx] = deps;
        //If the return value of callback is a function, the function is stored in the global destory array first, and then executed successively in the willunmount stage
        if(typeof destroy === "function") {
            _effectDestroy.push(destroy);
        }
    //Otherwise, it is the stage of re rendering
    } else {
        //Execute callback directly without dependencies
        if(!deps) {
            callback();
        } else {
            //Only when the dependency is not an empty array and the dependency is updated can the callback be executed 
            deps.length !== 0 && !deps.every((dep, idx) => dep === memoizedDeps[idx]) && callback();
            //Don't forget to update dependencies
            _memoizedState[_idx] = deps;
        }
    }
    _idx++;
}

The simulated react running environment is as follows:

let onClick;
let onChange;
const willUnMount = () => {
    for(let destroy of _effectDestroy) {
        destroy();
    }
}

function render() {
    // _ IDX indicates the location of the hooks currently executed
    // _ Resetting IDX to 0 is also in line with react. Each hook is updated from the hooks header node every time it is updated
    _idx = 0;
    const [count, setCount] = useState(0);
    const [name, setName] = useState("77");
    useEffect(()=>{
        console.log("effect —— count", count);
        return () => {
            console.log("count Effect Destroy");
        }
    },[count])
    useEffect(()=>{
        console.log("effect —— name", name);
    },[name])
    //Use onclick and onchange to simply simulate the update operation
    onClick = () => { setCount(count + 1) };
    onChange = (name) => { setName(name) };
}


console.log("-----render--------------");
render();
console.log("-----countChanged--------");
onClick();
onClick();
console.log("-----nameChanged---------");
onChange("kiana")
onChange("kiana_k423")
console.log("-----willUnMount---------");
willUnMount();

The operation results are as follows:

According to the principle of react hooks, write a simple useeffect

When count and name are updated, useeffect executes its own callback respectively. Finally, in the willunmount stage, execute the effect destroy of callback with return function

summary

This simple implementation is quite different from the source code of react, mainly because react has many situations to consider, such as asynchronous update, priority scheduling, custom hook and other scenarios. The react source code adopts the linked list structure, and then the data structure of each node in the linked list is defined as follows:

const effect: Effect = {
    Tag, // used to identify whether the dependency has changed
    Create, // the function body passed in by the user using useeffect
    Destroy, // the function generated after the above function body is executed to remove side effects
    DEPs, // dependency list
    Next: (null: any), // point to the next effect
};

In order to simplify complex problems, this paper adopts array structure, and then only focuses on the core functions. However, the simple implementation of this article is also in line with the idea of react implementation. First, judge whether the current is the initial mount or update stage, and then save it if there is a function to remove side effects in callback. Different processing is carried out through different dependencies. Finally, before destruction, the previously saved functions to remove side effects are executed in turn. In addition, you can take a look at the previousAbout the simple usestate implementation

All codes are as follows:

const _ memoizedState = []; //  Multiple hooks are stored in this array
let _ idx = 0; //  Current memoizedstate subscript
const _ effectDestroy = []; //  Store functions returned by multiple useeffect callback functions

/**
 *Simulate the implementation of usestate
 *@ param {any} defaultstate default
 *@ returns state and setstate methods
 */
function useState(defaultState) {
    //Check whether the current position has a value
    _memoizedState[_idx] = _memoizedState[_idx] || defaultState;
    //Once again, using closures, let setstate update the state of the corresponding location
    const curIdx = _idx;
    function setState(newState) {
        //Update the state of the corresponding location
        _memoizedState[curIdx] = newState;
        //Trigger the rendering function after the update
        render();
    }

    //Returns the current state in_ Location of memoizedstate
    return [_memoizedState[_idx++], setState];
}

/**
 *Simulated implementation of useeffect
 *// different DEPs correspond to different situations,
    // 1.  When DEPs does not exist: every time the state is updated, a callback needs to be executed
    // 2.  When DEPs exists, but the array is empty, you only need to execute callback when mounting, that is, when rendering for the first time
    // 3.  If DEPs exists and has dependencies, the callback is executed only when the corresponding dependency is updated
 *@ param {function} callback callback function
 *@ param {array} DEPs dependency
 */
function useEffect(callback, deps) {
    //First, get the original dependency stored in the current location in the global hooks list according to the current subscript
    const memoizedDeps = _memoizedState[_idx];
    //If not currently, it will prove to be the first rendering, and a callback will be executed in any case
    if(!memoizedDeps) {
        const destroy = callback();
        //Update dependencies at the same time
        _memoizedState[_idx] = deps;
        //If the return value of callback is a function, the function is stored in the global destory array first, and then executed successively in the willunmount stage
        if(typeof destroy === "function") {
            _effectDestroy.push(destroy);
        }
    //Otherwise, it is the stage of re rendering
    } else {
        //Execute callback directly without dependencies
        if(!deps) {
            callback();
        } else {
            //Only when the dependency is not an empty array and the dependency is updated can the callback be executed 
            deps.length !== 0 && !deps.every((dep, idx) => dep === memoizedDeps[idx]) && callback();
            //Don't forget to update dependencies
            _memoizedState[_idx] = deps;
        }
    }
    _idx++;
}


let onClick;
let onChange;
const willUnMount = () => {
    for(let destroy of _effectDestroy) {
        destroy();
    }
}

function render() {
    // _ IDX indicates the location of the hooks currently executed
    // _ Resetting IDX to 0 is also in line with react. Each hook is updated from the hooks header node every time it is updated
    _idx = 0;
    const [count, setCount] = useState(0);
    const [name, setName] = useState("77");
    useEffect(()=>{
        console.log("effect —— count", count);
        return () => {
            console.log("count Effect Destroy");
        }
    },[count])
    useEffect(()=>{
        console.log("effect —— name", name);
    },[name])
    //Use onclick and onchange to simply simulate the update operation
    onClick = () => { setCount(count + 1) };
    onChange = (name) => { setName(name) };
}

console.log("-----render--------------");
render();
console.log("-----countChanged--------");
onClick();
onClick();
console.log("-----nameChanged---------");
onChange("kiana")
onChange("kiana_k423")
console.log("-----willUnMount---------");
willUnMount();