react在一个点击事件函数里获取不到最新的useState设置的x值

新手上路,请多包涵

useState设置某个值x成功以后,在useEffect里也是打印的最新的x值,但是几秒后在一个函数(点击外部按钮执行的)里打印这个x却是初始值。为什么会出现这个情况?

阅读 9.6k
1 个回答

要理解原因很简单,因为每次setState之后都会导致函数组件重新执行,而如果你在外部引用调用的函数,是前几轮更新时创建的,所以他引用的state可能还在时前几轮更新时值
我写了一个80行的简易代码方便让你知道setState之后发生了什么,和为什么state没有更新

const React = {
  createElement(type, props, ...children) {
    return { type }
  },
}

let currentlyRendererFiber = null
let fiberRoot = null
let firstMount = true

const dispatcherResolver = () => {
  if (firstMount) {
    firstMount = false
    return dispatcherOnMount
  } else {
    return dispatcherOnUpdate
  }
}

const useState = (state) => {
  return dispatcherResolver().useState(state)
}

const performWorkOnRoot = () => {
  const { type: Component } = currentlyRendererFiber

  Component()
}

const ReactDOM = {
  render(element) {
    fiberRoot = element
    currentlyRendererFiber = element

    performWorkOnRoot()
  },
}

function dispatchAction(hook, action) {
  hook.baseState = action

  performWorkOnRoot()
}

const dispatcherOnMount = {
  useState(initState) {
    const hook = { baseState: initState, update: null }
    currentlyRendererFiber.hook = hook

    return [initState, dispatchAction.bind(null, hook)]
  },
}

const dispatcherOnUpdate = {
  useState(_) {
    const hook = currentlyRendererFiber.hook

    return [hook.baseState, dispatchAction.bind(null, hook)]
  },
}

let hasRegistered = false
let handler = null

const App = () => {
  const [state, setState] = useState(0)

  //尝试分别注释下面的其中一种做法,看看他们之间有什么区别

  //错误的做法,相当于在一个deps数组为空的useEffect中绑定事件,此时绑定
  //的hanlder永远为第一轮更新是的handler
  //state捕获的是第一轮渲染时state的值所以state永远0,不管点击都少
  //次都相当于setState(1)
  {
    if (!hasRegistered) {
      hasRegistered = true
      document.addEventListener('click', () => {
        setState(state + 1)
      })
    }
  }

  {
    //正确的做法,相当于在deps数组中加入了state(useEffect(() => {}, [state]))
    //每当所以每轮state导致的更新都会重新绑定handler所以handler里面的state捕获的永远是
    //最新值
    if (handler) {
      document.removeEventListener('click', handler)
    }

    handler = () => {
      setState(state + 1)
    }

    document.addEventListener('click', handler)
  }

  console.log('state', state)
}

ReactDOM.render(React.createElement(App, null, null))

如果你不想知道原理可以直接看这里的解决办法
如果你还想知道更多的细节可以查看我的项目tiny-react
他会教你怎么用几千行代码实现React17核心功能的以及React18才开放的ConcurrentMode

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题