published on
tags: react

模拟 React Hooks

首先 我们先模拟一个useState

function useState(initVal) {
  let _val = initVal;
  const state = _val;
  function setState(newVal) {
    _val = newVal;
  }

  return [state, setState];
}

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

console.log(count);

setCount(2);

console.log(count);

但是log 出来的count并不是我们所期望的 0 2,而是 0 0; 为什么呢? 因为在[count, setCount] = useState(0)count 复制的是 initVal, 修改 _val 的值, 而 count 仍然是initVal的值

如何让他恢复正常呢,我们可以使用方法来获取state

function useState(initVal) {
  let _val = initVal;
  const state = () => _val;
  function setState(newVal) {
    _val = newVal;
  }

  return [state, setState];
}

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

console.log(count());

setCount(2);

console.log(count());

但是如果我们想真正模拟Hooks,就不能这样做。

我们可以用如下代码模拟 React, 主要使用闭包和数组模拟Hook,使用 hook[index] 存放 state 或者 依赖

// module pattern
const React = (function() {
  let hooks = []; // 存放所以使用的 hook
  let idx = 0; // hook 指针,来保证 hook 顺序的正常调用
  function useState(initVal) {
    const state = hooks[idx] || initVal;
    const _idx = idx; // _idx 确保 setState 修改的是同一个state 
    function setState(newVal) {
      hooks[_idx] = newVal;
    }

    idx++; // 如果再使用 useState, 指向 hook 数组的下一个

    return [state, setState];
  }

  function useEffect(callback, deps) {
    const oldDeps = hooks[idx];
    let hasChange = true;

    if (oldDeps) {
      hasChange = deps.some((dep, i) => !Object.is(dep, oldDeps[i]));
    }

    if (hasChange) callback();
    hooks[idx] = deps;
    idx++; // 如果再使用 useEffect, 指向 hook 数组的下一个
  }

  function render(Component) {
    const C = Component();
    C.render();
    idx = 0; // 每次 render 重置,确保 hooks 指针顺序
    return C;
  }

  return {
    Component,
    render,
    useState,
    useEffect
  };
})();

const { useState, useEffect, render } = React;

function Component() {
  const [count, setCount] = useState(0);
  const [type, setType] = useState("");

  useEffect(() => {
    console.log("jack");
  }, [count]);

  return {
    render: () => {
      document.body.innerHTML = "";
      const div = document.createElement("div");
      div.innerText = count;
      document.body.appendChild(div);
    },
    click: () => setCount(count + 1),
    type: () => setType("apple"),
    say: () => console.log(count, type)
  };
}

let App = render(Component);

setInterval(() => {
  App.click();
  App = render(Component); // 模拟 rerender
}, 1000);

效果看下面连接

mock react hooks