从零开始实现 React hook useState

491 阅读2分钟

什么是hook

hook可以让你在不编写class组件的情况下使用state以及其他的React特性

具体的使用方法可见官网

实现useState

我们都知道useState会返回一个状态变量和修改它的函数,就像这样

const [state, setState] = useState();

单一状态

那么针对一个变量的情况就简单很多了,只需要用一个全局变量就能简单的实现了

const state = null;

const useState = defaultState => {
  if (!state) {
    state = defaultState;
  }
  const setState = newState => {
    state = newState;
  };
  return [state, setState()];
};

const singleState = () => {
  const [count, setCount] = useState(0);
  console.log(count);
  setCount(count + 1);
};

多个状态

而对于多个状态可能就需要一个格外的变量num来标记,我们用的哪一 state

const states = {};
let num = 0;

const useState = defaultState => {
  if (!states[num]) {
    states[num] = defaultState;
  }
  const setState = newState => {
    states[num] = newState;
  };
  const result = [states[num], setState];
  num += 1;
  return result;
};

const withHook = renderFunc => {
  return (...args) => {
    // 重置
    num = 0;
    return renderFunc(...args);
  };
};

withHook(function multipleState() {
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(0);

  console.log(count1, count2);
  setCount1(count1 + 1);
  setCount2(count2 + 2);
});

注意每次调用函数组件的时候应该把num重置

多个函数

对于多个函数组件的话,函数相互调用会打乱我们num的顺序,那应该怎么保持有序执行呢?这里就要用一个stack,像我们平时调试程序的时候会在浏览器控制台里面看到一个call stack调用栈,每次运行一个函数就把它入栈,执行完毕就出栈,这样就保证了顺序

const contextStack = [];

const useState = defaultState => {
  const context = contextStack[contextStack.length - 1];
  const { num, states } = context;

  if (!states[num]) {
    states[num] = defaultState;
  }
  const setState = newState => {
    states[num] = newState;
  };
  const result = [states[num], setState];
  context.num += 1;
  return result;
};

const withHook = renderFunc => {
  return (...args) => {
    contextStack.push({ num: 0, states: [] });
    const result = renderFunc(...args);
    contextStack.pop();
    return result;
  };
};

withHook(function state1() {
  const [count1, setCount1] = useState(0);

  console.log(count1);
  setCount1(count1 + 1);
});

withHook(function state2() {
  const [count2, setCount2] = useState(0);

  console.log(count2);
  setCount2(count2 + 2);
});

React结合起来

React是基于Fiber来实现,对于16.6以下的版本,我们用类组件来实现

根据上面的代码,我们只需要在setState里面去更新函数就可以了,代码如下

import { Component } from 'react';

const contextStack = [];

const useState = defaultState => {
  const context = contextStack[contextStack.length - 1];
  const { component } = context;
  const states = Object.values(component.state || {});

  if (component.firstRender) {
    states[context.num] = defaultState;
  }

  const setState = num => val => {
    component.setState({ [num]: val });
  };

  const result = [states[context.num], setState(context.num, component)];
  context.num += 1;
  return result;
};

const withHook = renderFunc => {
  const HooksComponent = class extends Component {
    constructor(props) {
      super(props);
      // 标记组件是否是第一次渲染,对于useState初始值的优化
      this.firstRender = true;
    }

    componentDidMount() {
      this.firstRender = false;
    }

    render() {
      contextStack.push({ num: 0, component: this });
      const result = renderFunc(this.props);
      contextStack.pop();
      return result;
    }
  };
  HooksComponent.displayName = renderFunc.name;
  return HooksComponent;
};

export { withHook, useState };

总结

整个实现过程还是比较容易,先从最简单的一个函数一个状态到多个函数多个状态循序渐进

实现过程参考这篇文章