Javascript

简易实现React的useState

本文主要是介绍简易实现React的useState,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

setState

  1. 首次渲染组件时,App会被调用,得到虚拟dom,创建真实的dom
  2. 当点击button时,调用setN,再次调用App,得到虚拟dom,用DOM Diff算法更新dom
import React from "react";
import ReactDOM from "react-dom";
const rootElement = document.getElementById("root");

function App() {
  const [n, setN] = React.useState(0);
  console.log("App 运行了"); // App 被调用就会执行一次
  console.log(`n: ${n}`); // App 被调用后 n 每次都不一样
  return (
    <div className="App">
      <p>{n}</p>
      <p>
        <button onClick={() => setN(n + 1)}>+1</button>
      </p>
    </div>
  );
}

ReactDOM.render(<App />, rootElement);
复制代码

nApp被调用后每次都会变化,但是setN()却不会改变n

  • setN一定会修改数据x,将n + 1存入x
  • setN一定会触发App重新渲染`
  • useState肯定会从x读取n的最新值
  • 每个组件都有自己的数据x,我们将其命名成state

自己来实现setState

  • 声明一个myUseState,接收一个初始值initialValue
  • 将初始值initialValue赋值给一个中间变量state
  • 内部声明一个函数setState,接收一个newValue,再将newValue赋值给state,并执行App
  • 返回statesetState
import React from "react";
import ReactDOM from "react-dom";
const rootElement = document.getElementById("root");

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

const render = () => {
  ReactDOM.render(<App />, rootElement);
};

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

ReactDOM.render(<App />, rootElement);
复制代码

但是这样有个问题每次执行setN时,都会把state设置为初始值,因为每次执行setN都会传入一个初始值0

解决这个问题就是将state变成全局变量

import React from "react";
import ReactDOM from "react-dom";
const rootElement = document.getElementById("root");

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

const render = () => {
  ReactDOM.render(<App />, rootElement);
};

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

ReactDOM.render(<App />, rootElement);
复制代码

如果一个组件使用了两个useState

function App() {
  const [n, setN] = myUseState(0);
  const [M, setM] = myUseState(1); // 会出问题,第一个会被覆盖
  return (
    <div className="App">
      <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>
  );
}
复制代码

由于所有数据都存在一个_state中,所以会冲突。

可以使用数组去解决_state重复问题。

  • _state声明为[],同时声明一个索引index = 0
  • myUseState方法内部声明一个临时变量currentIndex,用来保存索引index
  • 用索引去初始化_state
  • setState时也将通过索引去操作_state
  • index += 1
  • 返回_state[currentIndex]setState
  • 每次调用render方法是将index重置为0
import React from "react";
import ReactDOM from "react-dom";
const rootElement = document.getElementById("root");

let _state = [];
let index = 0;
const myUseState = initialValue => {
  const currentIndex = index;
  _state[currentIndex] =
    _state[currentIndex] === undefined ? initialValue : _state[currentIndex];

  const setState = newValue => {
    _state[currentIndex] = newValue;
    render();
  };
  index += 1;
  return [_state[currentIndex], setState];
};

const render = () => {
  index = 0;
  ReactDOM.render(<App />, rootElement);
};

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

ReactDOM.render(<App />, rootElement);
复制代码

useState调用顺序

  • 若第一渲染时n是第一个,m是第二个,k是第三个
  • 则第二次渲染时必须保证顺序完全一致
  • React不允许出现如下代码
function App() {
  const [n, setN] = myUseState(0);
  let m, setM;
  if (n % 2 === 1) [m, setM] = myUseState(0); // 报错
  return (
    <div className="App">
      <p>{n}</p>
      <p>
        <button onClick={() => setN(n + 1)}>N+1</button>
      </p>
      <p>{m}</p>
      <p>
        <button onClick={() => setM(m + 1)}>M+1</button>
      </p>
    </div>
  );
}
复制代码

报错信息:React has detected a change in the order of Hooks called by App. This will lead to bugs and errors if not fixed.

问题:

App用了_stateindex那其他组件用什么?

  • 给每个组件创建一个_stateindex

放在全局作用域重名了咋整

  • 放在组件对象的虚拟节点对象上

感觉会是bug

function App() {
  const [n, setN] = myUseState(0);
  const log () => setTimeout(() => console.log(`n: ${n}`), 3000)
  return (
    <div className="App">
      <p>{n}</p>
      <p>
        <button onClick={() => setN(n + 1)}>N+1</button>
        <button onClick={log}>log</button>
      </p>
    </div>
  );
}
复制代码

先点击N+1时,再点击log,输出是没有问题

如果先点击log,在点击N+1,就会发现输出的居然是0。难道N+1后输出不是1么。

因为setN是不会改变n,而是生成一个新的n

解决上面那个感觉是bug

  • 全局变量,window.xxx
  • useRef不仅可以用于div,还能用于任意数据
  • useContext不能能贯穿始终,还能贯穿不同组件
function App() {
  const nRef = React.useRef(0);   // { current: 0 }
  const log () => setTimeout(() => console.log(`n: ${React.useRef(0)}`), 3000);
  const update = React.useState(null)[1];
  return (
    <div className="App">
      <p>{nRef.current}</p>
      <p>
        <button onClick={() => { 
          nRef.current += 1;  // 这里不能实时更新
          update(nRef.current;
          )}}>N+1</button>
        <button onClick={log}>log</button>
      </p>
    </div>
  );
}
复制代码

React.current += 1不会让App重新渲染。

总结

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

Tips

  1. 这里是对React做了简化,方便理解。
  2. React对象节点应该是FiberNode
  3. _state的真实名称为memorizedStateindex的实现用到了链表

另外可添加微信ttxbg180218交流

这篇关于简易实现React的useState的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!