这一次,学会手写useEffect

1,612 阅读2分钟

关于原生的useEffect的基本用法,可以看我的这篇文章这一次,彻底搞懂useEffect,简洁易懂。

原生useEffect具备的几个特点

  1. useEffect可以多次调用。
  2. useEffect根据传入参数的不同,具有不同的执行方法。

手写useEffect的步骤

第一步:使用数组来存储不同的effect

// 以前的依赖值
let preArray = [];
// 定义effect的索引
let effectId = 0;

第二步:判断传入参数是否正确

  • 如果第一个参数传入的不是函数则报错
// 如果第一个参数不是一个函数则报错
if (Object.prototype.toString.call(callback) !== '[object Function]') {
    throw new Error('第一个参数不是函数')
}
  • 判断第二个参数
    • 没传的话,按照componentDidMount 和 componentDidUptate处理
    • 传的话,判读是不是数组,不是则报错
    • 获取前一个effect,如果没有则等同于compoentDidMout,直接执行callback,如果有则判断是否与以前的依赖值一样,如果不一样则执行callback,这样就实现了第二个参数数组内的元素发生变化的时候才执行callback.
// 如果第二个参数不传,相当于componentDidMount和componentDidUpdate
if (typeof array === 'undefined') {
    callback();
} else {
    // 判断array是不是数组
    if (Object.prototype.toString.call(array) !== '[object Array]') throw new Error('useEffect的第二个参数必须是数组')
    // 获取前一个的effect
    if (preArray[effectId]) {
        // 判断和以前的依赖值是否一致,一致则执行callback
        let hasChange = array.every((item,index) => item === preArray[effectId][index]) ? false : true;
        if (hasChange) {
            callback();
        }
    } else {
        callback()
    }

第三步:更新依赖值

注意,本次实现的useEffect是需要render函数执行的时候,将effectId置为0的。

// 更新依赖值
preArray[effectId] = array;
effectId++;

全部代码

import React from 'react'
import ReactDOM from 'react-dom'

// 自定义Hook
// 自定义useState
let states = [];
let setters = [];
let stateid = 0;
function render() {
    stateid = 0;
    effectId = 0;
    ReactDOM.render(<App />,document.querySelector('#root'));
}
function createSetter(stateid) {
    return function (newState) {
        states[stateid] = newState;
        render()
    }
}
function myUseState(initialState) {
    states[stateid] = states[stateid] ? states[stateid] : initialState;
    setters.push(createSetter(stateid));
    let value = states[stateid];
    let setter = setters[stateid];
    stateid++;
    return [value,setter]
}
// 以前的依赖值
let preArray = [];
// 定义effect的索引
let effectId = 0;
function myUseEffect(callback,array) {
    // 如果第一个参数不是一个函数则报错
    if (Object.prototype.toString.call(callback) !== '[object Function]') {
        throw new Error('第一个参数不是函数')
    }
    // 如果第二个参数不传,相当于componentDidMount和componentDidUpdate
    if (typeof array === 'undefined') {
        callback();
    } else {
        // 判断array是不是数组
        if (Object.prototype.toString.call(array) !== '[object Array]') throw new Error('useEffect的第二个参数必须是数组')
        // 获取前一个的effect
        if (preArray[effectId]) {
            // 判断和以前的依赖值是否一致,一致则执行callback
            let hasChange = array.every((item,index) => item === preArray[effectId][index]) ? false : true;
            if (hasChange) {
                callback();
            }
        } else {
            callback()
        }
        // 更新依赖值
        preArray[effectId] = array;
        effectId++;
    }
}
function App() {
    const [count,setCount] = myUseState(0);
    const [name,setName] = myUseState('张三');
    myUseEffect(() => {
        console.log('这是count');
    },[count]);
    myUseEffect(() => {
        console.log('这是name');
    },[name]);
    return (
        <div>
            <h1>当前求和为:{count}</h1>
            <button onClick={() => setCount(count + 1)}>点我+1</button>
            <h1>当前姓名为:{name}</h1>
            <button onClick={() => setName('李四')}>点我修改姓名</button>
        </div>
    )
}

ReactDOM.render(<App />, document.querySelector('#root'));

在线实现