React的useState是怎么实现的

1,040 阅读2分钟

简单实现

首先捋一下state更新的过程

function App(){
  const [n,setN]=React.useState(0)
  return(
    <div className="App">
      ...
      
    </div>
  )
}
ReactDOM.render(<App/>,root)

<App/>相当于App()

当首次渲染时,调用renderrender中执行App(),得到了虚拟dom,之后再创建真实dom元素。

当用户点击按钮调用setN时,会再次render,并再次调用App(),得到虚拟dom,再dom diff更新dom

可是每次调用,都会执行useState(0),岂不是每次都重置了吗?


可以想到的是组件应该维护着一个state,每次都把最新的值存进去,

let _state;

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

const render=()=>{
  ReactDOM.render(
    <Test/>,
  document.getElementById("root")
  );
}

function Test(){
  const [state,setState]=myUseState(0)
  function add(){
    setState(state+1)
  }
  return (
   <div>
     <button onClick={()=>{add()}}>+1</button>
     {state}
  </div>
  )
}

测试代码

但是,当有多个数据时,需要一个数组来保存

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(
    <Test/>,
  document.getElementById("root")
  );
}

function Test(){
  const [n,setN]=myUseState(0)
  const [m,setM]=myUseState(0)

  function addN(){
    setN(n+1)
  }
  function addM(){
    setM(m+1)
  }
  return (
   <div>
     <button onClick={()=>{addN()}}>+1</button>
     {n}
     <button onClick={()=>{addM()}}>+1</button>
     {m}
  </div>
  )
}

代码预览

这样就实现了多个数据的状态管理

重点是useState维护着一个数组,按顺序存放着声明的所有数据。因为数组是按顺序 排列,所以每次useState内部调用render时都要重置index,每个组件都有一个自己的_state和index

useState的顺序限制

但是正因为数据都按顺序排列,如果在if中调用useState就会出问题

  const [n,setN]=React.useState(0)
  if(n%2===0){
  const [m,setM]=React.useState(0)
  }
  const [k,setK]=React.useState(0)

代码链接

这样用就会报错,提示必须在完全相同的顺序下调用hook

源码简析

在源码中,每一个react节点都对应一个FiberNode,里面存放着一个Fiber对象,这个对象以链表的形式记录了数据,就像上文中的_state数组一样

每次调用useState后,设置在memoizedState上的Hook对象是这样:

{
  baseState,
  next,
  baseUpdate,
  queue,
  memoizedState
}

每个在FunctionalComponent中调用的useState都会有一个对应的Hook对象,他们按照执行的顺序以类似链表的数据格式存放在Fiber.memoizedState

memoizedState用来记录应该返回的结果,而next指向下一次使用useState对应的对象

setState,其实是useState的第二个返回方法dispatchAction,执行时会创建一个:

var update = {
  expirationTime: _expirationTime,
  action: action,
  callback: callback !== undefined ? callback : null,
  next: null
}

这个update会被加入到queue队列中,可能有多次setStatereact会在收集完update后,调度一次react更新,这会执行一次FunctionalComponent,就会执行对应的useState,拿到Hook对象,它保存了queue对象表示有哪些更新存在,依次执行更新,拿到最新的state保存到memoizedState上并返回,最终达到setState的效果

参考

阅读源码后,来讲讲React Hooks是怎么实现的