美文网首页
useState原理

useState原理

作者: 我是Msorry | 来源:发表于2021-01-07 13:59 被阅读0次
    const App = () => {
      const [n, setN] = useState(0)
      return (
        <div className="App">
          <p>{n}</p>
          <p>
            <button onClick={() => setN(n + 1)}>+1</button>
          </p>
        </div>
      )
    }
    export default App;
    

    首次渲染<App />——调用App(),得到虚拟DOM1,把虚拟DOM1渲染成真实DOM
    用户点击button后,调用setN(n+1),要更新页面,一定会再次渲染<App/>——调用App(),根据虚拟DOM1得到虚拟DOM2,经过DOM Diff对比,局部更新(patch)真实DOM

    两次调用App(),都会运行useState(0)
    执行setN会发生什么?n变了吗?App会重新执行吗?
    每次App执行,useState(0),n的值每次一样吗?
    console.log()获得答案

    分析

    setState

    • setState一定会修改中间变量state,将n+1存入state
    • setState一定会触发<App/>重新渲染(re-render)

    useState

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

    myUseState

    这是错误的写法,每次点击button,n的值没变化
    错误的原因:每次点击button,state的值变为1,调用render(),由于state在myUseState内部声明,每次初始值0都会覆盖

    const myUseState = (initialState) => {
      let state = initialState
      const setState = (newState) => {
        state = newState
        render()
      }
      return [state,setState]
    }
    const render = () => {
      ReactDOM.render(<App/>,root);
    }
    
    const App = () => {
      const [n, setN] = myUseState(0)
      return (
        <div className="App">
          <p>{n}</p>
          <p>
            <button onClick={() => setN(n + 1)}>+1</button>
          </p>
        </div>
      )
    }
    

    利用闭包储存上次的数据,防止被覆盖

    let _state
    const myUseState = (initialState) => {
      _state = _state === undefined ? initialState : _state
      const setState = (newState) => {
        _state = newState
        render()
      }
      return [_state, setState]
    }
    const render = () => {
      ReactDOM.render(<App/>, root);
    }
    
    const App = () => {
      const [n, setN] = myUseState(2)
      return (
        <div className="App">
          <p>{n}</p>
          <p>
            <button onClick={() => setN(n - 1)}>-1</button>
          </p>
        </div>
      )
    }
    

    setState是一个回调函数
    不能写成_state = _state || initialState,当state为0时,判定为falsy值,自动返回initialState的值

    如果一个组件,用两个myUseState怎么办?
    由于所有的数据都放在_state里,后面的会覆盖前面的_state,发生冲突

    多个myUseState

    • 把_state变成一个对象

    每次传值都得传递一个属性名,例如:const [n, setN] = myUseState('m':2)
    但是原来的react并没有传递属性名,所以_state不是对象

    • 把_state做成一个数组
    • 例如_state=[0,0],看着还可以,试一试
    let _state = []
    let index = 0
    const myUseState = (initialState) => {
      const currentIndex = index//保证数组的下标固定
      _state[currentIndex] = _state[currentIndex] === undefined ? initialState : _state[currentIndex]
      const setState = (newState) => {
        _state[currentIndex] = newState
        console.log(_state)
        render()
      }
      index += 1
      return [_state[currentIndex], setState]
    }
    const render = () => {
      index = 0//index重置
      ReactDOM.render(<App/>, root);
    }
    

    const currentIndex = index每次调用myUseState时,myUseState内的_state下标固定,不被闭包影响
    思路:每次调用render(),只不过是载入的_state发生变化,其他都要保持不变
    每次render都是重新调用<App/>,所以index重置在调用<App/>之前

    现在代码存在的问题

    • _state数组会改变顺序,与useState不符合

    React里,useState是有调用顺序,必须完全一样的调用,也不能有条件的调用
    例如:若第一次渲染时,n是第一个,m是第二个,k是第三个
    下次渲染时,必须保证n,m,k的顺序完全一样
    Vue3借鉴React,但是完全克服这个问题

    • App组件用了_state和index,那么其他的组件用什么?

    解决办法:给每个组件创建一个_state和index,放到组件对应的虚拟DOM节点对象上

    总结

    • 每个函数组件对应一个FiberNode(虚拟节点)
    • 每个节点都保存着state和index
    • useState会读取对应节点的state[index]
    • index由useState调用(出现)的顺序决定
    • 在setState里修改state和触发更新
    • React里state的名字叫memorizedState,index是用链表实现的

    每个组件都有一个虚拟节点,虚拟节点保存一个自己的_state和index,每次操作后,得到一个新的虚拟节点,DomDiff后,更新虚拟节点,更新虚拟DOM,渲染到页面
    新的虚拟节点根据旧的虚拟节点得到,不然无法理解setState的接受函数和改变对象的区别

    相关文章

      网友评论

          本文标题:useState原理

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