React入门--第四天--useState原理

Hooks原理解析

  • 分析useState原理和源码
  • useRef的作用
  • useContext的作用
  • Vue3对比React
function App() {
  const [n, setN] = React.useState(0);
  return (
    <div className="App">
      {n}
      <button onClick={()=>setN(n+1)}>+1<button>
    </div>
  );
}

React.render(<App />, rootElement);

首次渲染 render<App />—>调用App()—>得到虚拟div—>创建真实div—>用户点击button,调用setN(n+1)—>再次render<App />—>再次调用App()—>得到虚拟div—>DOM diff—>更新真的div
每次调用App(),都会运行useState(0)
但是每次运行useState得到的n是变化的,就是正确的n
执行setN的时候n不会变,App()会重新渲染

这个useState做了啥

分析

setN

  • setN一定会改变数据x(中间变量),将n+1存入x
  • setN一定会触发<App />重新渲染
    useState
  • useState肯定会从x读取n的最新的值
    x
  • 每个组件有自己的数据x,我们将其命名为state

尝试实现React.useState

const  myUseState = initialValue => {
  var state = initialValue;
  const setState = newState {
    state = newState;
    render();
  }
  return [state, setState];
}

//不用在意render的实现
const render = () => ReactDOM.render(<App />, rootElement);

function App() {
  const [n, setN] = myUseState(0);
  return (
    <div>
      <p>{n}</p>
      <p>
        <button onClick={()=>setN(n+1)}>+1</button>
      </p>
    </div>
  );
}

ReactDOM.render(<App />, rootElement);

上面的代码完全没有用,因为myUseState会将state重置
解决:
我们将这个变量声明在myUseState外面即可

let _state;

const  myUseState = initialValue => {
  _state = _state === undefined ? initialValue : _state;
  const setState = newState {
    _state = newState;
    render();
  }
  return [_state, setState];
}

//不用在意render的实现
const render = () => ReactDOM.render(<App />, rootElement);

function App() {
  const [n, setN] = myUseState(0);
  return (
    <div>
      <p>{n}</p>
      <p>
        <button onClick={()=>setN(n+1)}>+1</button>
      </p>
    </div>
  );
}

ReactDOM.render(<App />, rootElement);

现在就能用了。能够实现加一的功能,这是简单的实现,还有问题,要修改一下

问题

如果一个组件用了两个useState咋办

const [n, setN] = myUseState(0);
const [m, setM] = myUseState(0);

这两个组件共用一个_state就会冲突

解决:把_state做成数组

let _state = [];
let index = 0;//表示第几个

const  myUseState = initialValue => {
  const currentIndex = index;
  _state[currentIndex] = _state[currentIndex] === undefined ? initialValue : _state[currentIndex];
  const setState = newState {
    _state[currentIndex] = newState;
    render();
  }
  index += 1;
  return [_state[currentIndex], setState];
}

//不用在意render的实现
const render = () => {
    index = 0;
    ReactDOM.render(<App />, rootElement);
  }

function App() {
  const [n, setN] = myUseState(0);
  const [m, setM] = myUseState(0);
  return (
    <div>
      <p>{n}</p>
      <p>
        <button onClick={()=>setN(n+1)}>+1</button>
      </p>
      <p>{m}</p>
      <p>
        <button onClick={()=>setM(m+1)}>+1</button>
      </p>
    </div>
  );
}

ReactDOM.render(<App />, rootElement);

为什么要使用currentIndex,因为要实现先返回[_state[index], setState],再去index += 1,都返回了就不可能再加一,所以需要一个中间的变量。
然后在render中把index重置为0,免得超出数组下标。

难点

为什么n和m的下标对应了数组的下标呢,因为在App里面是按顺序执行的,index会随之改变,把当前下标赋值给n和m,setN和setM。只有重新渲染了,index才会被重新置为0,但是又会渲染App,正确的下标又被给了n和m。

新问题

上面的难点就是因为顺序是不变的,所以n和m的下标才会正确的对应上。

所以下面的代码是不允许出现的

function App() {
  const [n, setN] = React.useState(0);
  let m, setM;
  if(n%2===1) {
    [m, setM] = React.useState(0);
  }
  const [j, setJ] = React.useState(0);
  ...
}

这样会打乱顺序,如果if语句后面还要使用useState,就更会出问题了。
useState不能放在if语句里面

问题重重

App组件用了_state和index,那其他组件用什么?
解决:给组件创建一个_state和index

又有问题:放在全局作用域重名了咋整?
解决:放在组件对应的虚拟节点对象上

图示
在这里插入图片描述

总结

  • 每个组件对应一个React节点
  • 每个节点保存着state和index
  • useState会读取state[index]
  • index由useState出现的顺序决定
  • setState会修改state,并触发更新

上面的做了简化,React节点应该是FiberNode_state的真实名字称为memorizedStateindex的实现规则用到了链表
可以点击进行自行学习


版权声明:本文为weixin_44809439原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。