从头实现React.useState

6/2/2023


Image

根据 useState 的定义,写出基础框架

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

const [count, setCount] = useState(1);
console.log(count);
setCount(2);
console.log(count);

输出:

1
1

重新暴露内部变量

在上面的代码中,setCount 修改了内部变量的值,我们把state变成一个 getter 函数,重新把内部变量暴露出来。

function useState(initVal) {
  let _val = initVal;
  const state = () => _val; // 最容易的一种方式
  const setState = (newVal) => {
    _val = newVal;
  };
  return [state, setState];
}

const [count, setCount] = useState(1);
console.log(count()); // updated
setCount(2);
console.log(count()); // updated

输出:

1
2

使用立即执行函数引入 React 模块

const React = (function () {
  function useState(initVal) {
    let _val = initVal;
    const state = () => _val;
    const setState = (newVal) => {
      _val = newVal;
    };
    return [state, setState];
  }

  return { useState };
})();

const [count, setCount] = React.useState(1);
console.log(count());
setCount(2);
console.log(count());

输出:

1
2

定义一个HelloWorld组件来使用useState:

function HelloWorld() {
  const [count, setCount] = React.useState(1);

  return {
    render: () => console.log(count), //模拟JSX
    click: () => setCount(count + 1), // 模拟点击
  };
}

教会 React 如何渲染一个组件

const React = (function () {
  function useState(initVal) {
    let _val = initVal;
    const state = () => _val;
    const setState = (newVal) => {
      _val = newVal;
    };
    return [state, setState];
  }

  // ===新加一个render函数
  function render(Component) {
    const C = Component();
    C.render();
    return C;
  }
  // ===

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

重新组织一下目前的代码,并使用 React.render 方法

const React = (function () {
  let _val; // 使_val变成React的私有变量
  function useState(initVal) {
    const state = _val || initVal; // 通过useState来访问_val
    const setState = (newVal) => {
      _val = newVal;
    };
    return [state, setState];
  }

  function render(Component) {
    const C = Component();
    C.render();
    return C;
  }

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

function HelloWorld() {
  const [count, setCount] = React.useState(1);

  return {
    render: () => console.log(count),
    click: () => setCount(count + 1),
  };
}

var App = React.render(HelloWorld);
App.click();
var App = React.render(HelloWorld);
App.click();
var App = React.render(HelloWorld);
App.click();
var App = React.render(HelloWorld);
App.click();
var App = React.render(HelloWorld);

输出:

1
2
3
4
5

到目前为止,看上去已经比较完美了。

组件中使用多个state

function HelloWorld() {
  const [count, setCount] = React.useState(1);
  const [text, setText] = React.useState("apple"); //增加一个state

  return {
    render: () => console.log({ count, text }),
    click: () => setCount(count + 1),
    type: (word) => setText(word), // 增加一个设置方法
  };
}

var App = React.render(HelloWorld);
App.click();
var App = React.render(HelloWorld);
App.type("banana");
var App = React.render(HelloWorld);

输出:

{count: 1, text: "apple"}
{count:2, text: 2}
{count: "apple", text: "apple"}

这是由于我们从头到尾都只有一个变量_val,接下来我们来解决这个问题。

引入hooks数组和当前索引idx

const React = (function () {
  let hooks = []; // updated
  let idx = 0; // updated
  function useState(initVal) {
    const state = hooks[idx] || initVal; //_val被hooks[idx]替代
    const setState = (newVal) => {
      hooks[idx] = newVal; //_val被hooks[idx]替代
    };
    idx++; // 每调用一次useState,idx自增1
    return [state, setState];
  }

  function render(Component) {
    const C = Component();
    C.render();
    return C;
  }

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

输出:

{count: 1, text: "apple"}
{count:2, text: "apple"}
{count: "pear", text: "apple"}

看输出还是没有达到我们预期的效果。

重置idx

const React = (function () {
  let hooks = [];
  let idx = 0;
  function useState(initVal) {
    const state = hooks[idx] || initVal;
    const setState = (newVal) => {
      hooks[idx] = newVal;
    };
    idx++;
    return [state, setState];
  }

  function render(Component) {
    idx = 0; //每次render的时候重置idx
    const C = Component();
    C.render();
    return C;
  }

  return { useState, render };

输出:

{count: 1, text: "apple"}
{count: 1, text: "apple"}
{count: 1, text: "apple"}

这还是不对,setState 是在 React.render 之后执行,那个时候idx已经被重置为 0。

锁住idx的值

const React = (function () {
  let hooks = [];
  let idx = 0;
  function useState(initVal) {
    const state = hooks[idx] || initVal;
    const _idx = idx; // updated
    const setState = (newVal) => {
      hooks[_idx] = newVal;
    };
    idx++;
    return [state, setState];
  }

  function render(Component) {
    idx = 0;
    const C = Component();
    C.render();
    return C;
  }

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

输出:

{count: 1, text: "apple"}
{count: 2, text: "apple"}
{count: 2, text: "pear"}

最终使用不到 50 行代码完成了 React.useState()和 demo

const React = (function () {
  let hooks = [];
  let idx = 0;
  function useState(initVal) {
    const state = hooks[idx] || initVal;
    const _idx = idx; // updated
    const setState = (newVal) => {
      hooks[_idx] = newVal;
    };
    idx++;
    return [state, setState];
  }

  function render(Component) {
    idx = 0;
    const C = Component();
    C.render();
    return C;
  }

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

function HelloWorld() {
  const [count, setCount] = React.useState(1);
  const [text, setText] = React.useState("apple");

  return {
    render: () => console.log({ count, text }),
    click: () => setCount(count + 1),
    type: (word) => setText(word),
  };
}

var App = React.render(HelloWorld);
App.click();
var App = React.render(HelloWorld);
App.type("banana");
var App = React.render(HelloWorld);

输出:

{count: 1, text: "apple"}
{count: 2, text: "apple"}
{count: 2, text: "pear"}