美文网首页
useState 原理

useState 原理

作者: sweetBoy_9126 | 来源:发表于2019-11-07 16:47 被阅读0次

    useState用法


    点击button会发生什么?

    分析:

    • setN
      setN一定会修改数据x, 将n+1存入x
      setN一定会触发<App/>重新渲染(render)

    • useState
      useState肯定会从x读取n的最新值

    • x
      每个组件有自己的数据x, 我们将其命名为state

    最简单的useState的实现

    https://codesandbox.io/s/admiring-bash-utvzo

    问题:由于我们所有的数据都放在_state,如果一个组件用了两个useState就会冲突
    比如:

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

    这样我们修改一个另一个也会跟着变

    改进思路:

    • 把_state做成一个对象
      比如_state = {n:0, m: 0}
      不行,因为useState(0)并不知道变量叫n还是m

    • 把_state做成数组
      比如_state = [0,0]
      这样我们每次通过对应的索引来修改就可以

    let _state = [];
    let index = 0;
    const myUseState = num => {
      // 之所以要把index赋给一个currentIndex变量是因为,
      // 我们render完后紧接着要对index+1,这样就可以调用几次useState数组里面就有多少个state,
      // 也就是index就会和state对应
      const currentIndex = index;
      _state[currentIndex] =
        _state[currentIndex] === undefined ? num : _state[currentIndex];
      const setN = num1 => {
        _state[currentIndex] = num1;
        render();
      };
      index += 1;
      return [_state[currentIndex], setN];
    };
    function render() {
      // 之所以render前先置为0是因为如果不置0就会一直累加下去,而页面根本没有那么多state
      index = 0;
      ReactDOM.render(<App />, rootElement);
    }
    function App() {
      const [n, setN] = myUseState(0);
      const [m, setM] = myUseState(0);
      const x = () => {
        setN(n + 1);
      };
      const y = () => {
        setM(m + 1);
      };
      return (
        <div>
          {n}
          <button onClick={x}>+1</button>
          <br />
          {m}
          <button onClick={y}>+1</button>
        </div>
      );
    }
    
    const rootElement = document.getElementById("root");
    ReactDOM.render(<App />, rootElement);
    

    在线运行地址:https://codesandbox.io/s/autumn-hooks-resfq

    _state数组方案的缺点

    • useState调用顺序
      若第一次渲染时n是第一个,m是第二个,k是第三个,则第二次渲染时必须保持顺序完全一致,所以我们不能写成下面这样
    const [n, setN] = myUseState(0);
      let m, setM
      if (n % 2 === 1) {
        [m, setM] = myUseState(0);
      }
    

    上面代码一开始默认页面中只有一个myUseState,然后我们对n操作这时候又变成了两个,它两次渲染不一致就会出现问题,所以我们如果在react中这样写就会直接报错

    现在的代码还有一个问题:
    App用了_state和index, 那其他组件用什么?
    解决办法:给每个组件创建一个_state和index
    放在全局作用域里重名了咋办?
    解决办法:放在组件对应的虚拟节点对象上

    总结

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

    使用useRef和useContext得到一个贯穿始终的状态

    1. useState每次修改都会生成一个新的state
    function App() {
      const [n, setN] = React.useState(0)
      const log = () => {
        setTimeout(() => console.log(`n: ${n}`), 3000)
      }
      return (
        <div className="App">
          <p>{n}</p>
          <p>
            <button onClick={() => setN(n + 1)}>+1</button>
            <button onClick={log}>log</button>
          </p>
        </div>
      )
    }
    

    上面的代码有两种操作
    1). 点击+1再点击log--> 得到的n的值就是当前的值+1
    2). 点击log再点击+1--> 得到的值就是上一个n的值

    原因:因为有多个n

    上图中有两条线
    第一条就是我们正常操作先点+1再点log,它会先触发setN,setN执行就出触发render,这时候就会第二次渲染App,生成一个新的n,n是1,再点击三秒后就会log(1);
    第二条就是我们先点log,它会三秒后打印出第一次这个n,这时候的n的值还是0,然后点击+1触发render,第二次渲染App,生成一个新的n=1,然后三秒钟到打印出第一次的n也就是0
    总结:我们每次修改state都不会修改当前的state,而是会重新生成一个新的state,旧的state和新的state有可能同时存在,之后旧的state会被垃圾回收掉

    在线代码运行:https://codesandbox.io/s/icy-firefly-imvfu

    1. 如何实现一个贯穿始终的状态
      1). 全局变量
      用window.xxx即可,但太low了

    2). useRef
    useRef不仅可以用于div,还能用于任意数据

    function App() {
      const nRef = React.useRef(0); // React.useRef(0)就返回一个{current: 0}
      const log = () => {
        setTimeout(() => console.log(`n: ${nRef.current}`), 3000);
      };
      return (
        <div className="App">
          <p>{nRef.current}</p>
          <p>
            <button onClick={() => (nRef.current += 1)}>+1</button>
            <button onClick={log}>log</button>
          </p>
        </div>
      );
    }
    

    上面的代码不管是先点+1还是先点log结果都一样
    问题:但是我们视图里的0并没有改变,可控制台里的n却一直在变
    原因:nRef.current += 1不会触发render不会重新渲染App
    解决办法:通过修改state强制更新(不推荐使用)

    const [n, setN] = React.useState(-1);
    <button
       onClick={() => {
         nRef.current += 1;
         setN(nRef.current);
       }}
    >
     +1
    </button>
    

    上面其实我们并没有用到n,所以我们可以简化一下

    const update = React.useState(-1)[1];
    <button
       onClick={() => {
          nRef.current += 1;
          update(nRef.current);
       }}
    >
    

    在线代码运行:https://codesandbox.io/s/crazy-mcclintock-1rb3l

    3). useContext
    useContext不仅能贯穿始终,还能贯穿不同组件

    案例:https://codesandbox.io/s/wizardly-grothendieck-ek0i5

    总结

    • 每次重新渲染,组件函数就会执行
    • 对应的所有state都会出现「分身」
    • 如果你不希望出现分身,可以使用useRef/useContext

    相关文章

      网友评论

          本文标题:useState 原理

          本文链接:https://www.haomeiwen.com/subject/gcsvbctx.html