React hooks模拟实现

1,096 阅读2分钟

背景:react 16.8新加入 hooks,新的逻辑复用方式(高阶组件/render props),hooks刚出来就开始使用,看了很多使用的教程,但是一直不清楚这里面的黑魔法是怎么实现的。

常用hook

useState/useEffect/useRef/useCallback/useMemo/useContext/useReducer

官方教程地址 react.docschina.org/docs/hooks-…

示例源码

今天要实现的两个hook

实际上react内部是用的链表结构,这里为了简化,使用数组来模拟

useState

简要使用方法

useState接收一个参数初始值,返回值和修改函数,修改函数setState可以传值或者修改方法,当然useState也可以接收一个函数作为参数,函数返回值作为初始参数,这个比较简单就没有实现。

const [val,setVal]=useState(0);

实现

const stateCache = [];
let stateCursor = 0;

function useState(initialState) {
  const currentCursor = stateCursor;
  stateCache[currentCursor] = stateCache[currentCursor] || initialState; //是否初次渲染
  function setState(value) {
    stateCache[currentCursor] =
      typeof value === "function" ? value(stateCache[currentCursor]) : value;
    render(); //模拟框架内部render触发
  }
  stateCursor++;
  return [stateCache[currentCursor], setState];
}

初始的状态和下标存储在全局变量里,状态值用数组存储,使用下标值获取,所以如果在判断和循环中使用,会导致下标错乱,示例中注视掉的部分,num1改变后,setNum2取到的就是原来testNum的下标和数据,setNum2也就不会生效了。

useEffect

简要使用说明

接收两个参数,第一个回调,第二个执行依赖,回调可以有返回值函数,在依赖有变化时执行,可以在里面做清理操作

  useEffect(() => {
    console.log("useEffect 执行,依赖num1");
    return () => console.log("依赖num1清理函数执行");
  }, [num1]);

实现

const effectCache = [];
let effectCursor = 0;
const effectCleanFnCache = [];
function useEffect(callback, deps) {
  if (!Array.isArray(deps)) {
    typeof effectCleanFnCache[effectCursor] === "function" &&
      effectCleanFnCache[effectCursor]();
    effectCleanFnCache[effectCursor] = callback();
    effectCursor++;
    return;
  }
  const currentCursor = effectCursor;
  const depsBefore = effectCache[currentCursor];
  if (!depsBefore) {
    //初次渲染一定执行
    effectCache[currentCursor] = deps;
    effectCleanFnCache[effectCursor] = callback();
  } else {
    if (deps.some((v, i) => v !== depsBefore[i])) {
      typeof effectCleanFnCache[effectCursor] === "function" &&
        effectCleanFnCache[effectCursor]();
      effectCleanFnCache[effectCursor] = callback();
    }
  }
  effectCursor++;
}

完整示例代码

import React from "react";
import ReactDOM from "react-dom";

const stateCache = [];
let stateCursor = 0;
const effectCache = [];
let effectCursor = 0;
const effectCleanFnCache = [];

function useState(initialState) {
  const currentCursor = stateCursor;
  stateCache[currentCursor] = stateCache[currentCursor] || initialState; //是否初次渲染
  function setState(value) {
    stateCache[currentCursor] =
      typeof value === "function" ? value(stateCache[currentCursor]) : value;
    render(); //模拟框架内部render触发
  }
  stateCursor++;
  return [stateCache[currentCursor], setState];
}

function useEffect(callback, deps) {
  if (!Array.isArray(deps)) {
    typeof effectCleanFnCache[effectCursor] === "function" &&
      effectCleanFnCache[effectCursor]();
    effectCleanFnCache[effectCursor] = callback();
    effectCursor++;
    return;
  }
  const currentCursor = effectCursor;
  const depsBefore = effectCache[currentCursor];
  if (!depsBefore) {
    //初次渲染一定执行
    effectCache[currentCursor] = deps;
    effectCleanFnCache[effectCursor] = callback();
  } else {
    if (deps.some((v, i) => v !== depsBefore[i])) {
      typeof effectCleanFnCache[effectCursor] === "function" &&
        effectCleanFnCache[effectCursor]();
      effectCleanFnCache[effectCursor] = callback();
    }
  }
  effectCursor++;
}

function App() {
  const [num1, setNum1] = useState(0);
  //   if (num1 === 0) {
  //     const [testNum] = useState("");
  //   }
  const [num2, setNum2] = useState(0);

  useEffect(() => {
    console.log("useEffect 执行,依赖[]");
  }, []);
  useEffect(() => {
    console.log("useEffect 执行,依赖空");
  });
  useEffect(() => {
    console.log("useEffect 执行,依赖num1");
    return () => console.log("依赖num1清理函数执行");
  }, [num1]);
  return (
    <div>
      <div>num1:{num1}</div>
      <div>num2:{num2}</div>
      <button onClick={() => setNum1(num1 + 1)}>add num1</button>
      <button onClick={() => setNum2(num2 => num2 + 1)}>add num2</button>
    </div>
  );
}

/**
 * 模拟react render
 */
function render() {
  ReactDOM.render(<App />, document.getElementById("root"));
  stateCursor = 0;
  effectCursor = 0;
}

render();