useState hooks, this.setState( ) 不及时更新问题

4,171 阅读5分钟

image.png

本篇文章是主要围绕着React16React.useState更新展开话题来揭秘一些面试中常见问题,本文章是带着问题找答案的方式进行探讨。

我们先看下面一道题,先不要看答案,自己思考一下分别点击一次,a与b的值是多少,a:? b:?

const [a, setA] = useState(0)
const [b, setB] = useState(0)

  const addTwice1 = () => {
    setA(a => a + 1)
    setA(a => a + 1)
  }
  
    const addTwice2 = () => {
    setB(b + 1)
    setB(b + 1)
  }
  
    return (
    <div>
      <div onClick={addTwice1}>a: {a}</div>
      <div onClick={addTwice2} >b: {b}</div>
    </div>
  )

答案 a:2 b:1


再抛出一个问题? hooks中的useState怎么做到像setState的第二个参数(回调)取到更新后的值


this.setState('learn',  (state) => console.log('这是回调函数,可以取到更新后的state'))

// 那么我们在react hooks中怎么能够实现同样的效果?
const [name, setName] = useState('')
setName('faith')
console.log(name) // 此刻打印出来的可能不是最新的值

如果我们看了上面的2道题,都比较迷惑,那么恭喜你,接下来我们从浅到深,通过6个例子学习

setCount(函数)

我们邀请明星技师111号进行教学准备

  // 例子 111号
  // dsc: hooks,怎么拿到最新的更新state?
  // 为什么执行完 addTwice1 count = 2
  const addTwice1 = () => {
    // 如果接受的是一个函数的话,我们可以理解【传入了一个接收上一次更新后state的函数】
    setCount(prevCount => prevCount + 1)

    setCount(preState => {
      // 此刻打印的 preState 永远都是上一次 count更新的结果
      console.log('faith=============preState', preState)
      return preState + 1
    })
  }

总结:如果接受的是一个函数的话,我们可以理解【传入了一个接收上一次更新后state的函数】

setCount(常量)

我们邀请明星技师222号进行教学准备

  // 例子 222号
  // 为什么执行完 addTwice2 count = 1
  // dsc: React会将多个setState的调用合并为一个来执行,也就是说,当执行setState的时候,state中的数据并不会马上更新,
  // addTwice2调用后结果是1应该很好理解,setCount是异步执行,第一个setCount执行时count是0所以执行的是setCount(1),
  // 由于异步此时继续执行第二个setCount,此时count还是0,因为第一个setCount是异步所以count还未更新,所以此时第二个执行的也是setCount(1),所以最终的count是1;
  const addTwice2 = () => {
    setCount(count + 1)
    setCount(count + 1)
  }

setTimeout与setCount(函数)

我们邀请明星技师333号进行教学准备

  // 例子 333号
  // 点击多少次,count就是几
  // 原因是setCount接收的是函数,函数的入参一定是上一次count的更新
  // preState 默认值= 0
  const addTwice4 = () => {
    setTimeout(() => {
      setCount(preState => preState + 1)
    }, 3000)
  }
  • 1、问,在3秒之内,快速立即触发addTwice4函数5次,结果count是什么?
  • 2、问,在3秒之内,快速立即触发addTwice4函数5次,3秒到5秒之间,再触发2次addTwice4函数,那么最后count是多少?

答案:

1、count = 5
2、count = 7

解释:不分时间,因为我们使用了更新状态传入的是函数, 222号技术告诉我们,使用函数,【传入了一个接收上一次更新后state的函数】,因此我们一定要关注更新传入的是常量还是函数

setTimeout与setCount(常量)

我们邀请明星技师888号进行教学准备

  const addTwice3 = () => {
    setTimeout(() => {
      setCount(count + 1)
    }, 3000)
  }

  • 1、问,在3秒之内,快速立即触发addTwice4函数5次,结果count是什么?
  • 2、问,在3秒之内,快速立即触发addTwice4函数5次,3秒到5秒之间,再触发2次addTwice4函数,那么最后count是多少?
1、count = 1
2、count = 2

如果在 3s 内点击 任意次, count 都是1, 因为setCount是异步的,React会将多个setState的调用合并为一个来执行,也就是说,当执行setState的时候,state中的数据并不会马上更新。

setInterval与setCount(常量)

我们邀请明星技师777号进行教学准备

    useEffect(() => {
      setInterval(() => {
        // 会一直打印  faith=============, 但是 count 一直是 1
        // 原因是形成了闭包
        console.log('faith=============')
        setCount(count + 1)
      }, 2000)
    }, [])

问,count 值是多少? 答案,一直是 1 思考,为什么888号, 再次点击还是能够触发更新,而777号的一直不可以更新了?

原因是 888再次点击会触发函数,重新创建 setTimeout形成新的作用域闭包 但是 setInterval 被创建就没被销毁,闭包一直存在

setInterval与setCount(函数)

我们邀请明星技师999号进行教学准备

  useEffect(() => {
    setInterval(() => {
      setCount(preState => preState + 1)
    }, 2000)
  }, [])

2022-04-02 16.33.47.gif

好了,以上通过各位技术的教学,大概了解。

hooks中的useState怎么做到像setState的第二个参数(回调)取到更新后的值

在React16 怎么模拟class组件的setState的第二个参数,回调函数拿到最新值?

一共有2种方式,自己实现一个自定义的useState hook, 通过Promis结合useState实现

先看下效果图,在回到函数拿到最新的值。

2022-04-02 17.01.44.gif

自定hook方式

import { Button } from 'antd'
import React, { useState, useRef, useEffect } from 'react'
// 使用自定义hook结合useState,模拟class组件的setState的第二个参数-回调函数
const useSimulationCbWithUseStateCustom = (initState: any) => {
  const [num, setNum] = useState(initState)
  const cbRef = useRef<any>(null)

  const setState = (state: any, cbFc: any) => {
    setNum(state)
    cbRef.current = cbFc
  }

  useEffect(() => {
    typeof cbRef.current === 'function' && cbRef.current(num)
    cbRef.current = null
  }, [num])

  return [num, setState]
}

const SimulationCbWithUseStateCustom = () => {
  const [num, setNum] = useSimulationCbWithUseStateCustom(0)
  const addNumCustom = () => {
    setNum(num + 1, (res: number) => {
      console.log('faith=============', res)
    })
  }
  return (
    <div>
      SimulationCbWithUseStateCustom模式
      <Button onClick={addNumCustom}>addNum</Button>num:{num}
    </div>
  )
}
export default SimulationCbWithUseStateCustom

Promise结合useState实现

import { Button } from 'antd'
import React, { useState } from 'react'
// 使用promise结合useState,模拟class组件的setState的第二个参数-回调函数
const SimulationCbWithUseState = () => {
  const [num, setNum] = useState(0)

  const addNum = () => {
    new Promise((resolve, reject) => {
      setNum(preNum => {
        resolve(preNum + 1)
        return preNum + 1
      })
    }).then(res => {
      console.log('faith=============', res)
    })
  }

  return (
    <div>
      SimulationCbWithUseStatePromise模式
      <Button onClick={addNum}>addNum</Button>num:{num}
    </div>
  )
}
export default SimulationCbWithUseState

最后练习

  const [count, setCount] = useState(0)

  const addTwice1 = () => {
    setCount(prevCount => prevCount + 1)
    setCount(prevCount => prevCount + 1)
  }
  // 触发一次 addTwice1 count是多少?
  // 2

  const addTwice2 = () => {
    setCount(count + 1)
    setCount(count + 1)
  }
  //  触发一次 addTwice2 count是多少?
  // 1

  const addTwice3 = () => {
    setTimeout(() => {
      setCount(count + 1)
    }, 3000)
  }
  // 在3秒内,立即触发5次,count是多少?
  // 1

  const addTwice4 = () => {
    setTimeout(() => {
      setCount(preState => preState + 1)
    }, 3000)
  }
  // 在3秒内,立即触发5次,count是多少?
  // 5
  // 在3秒内,立即触发3次,在4秒立即触发3次,count是多少?
  // 6

  useEffect(() => {
    setInterval(() => {
      setCount(count + 1)
    }, 2000)
  }, [])
  // count一直是1

  useEffect(() => {
    setInterval(() => {
      setCount(preState => preState + 1)
    }, 2000)
  }, [])
  // count2秒+1