关于 React setState 的最新理解

时间:2021-1-8 作者:admin

众所周知,React 提供的 setState() 是一个“异步”执行的函数,为什么要加引号呢?因为 setState() 并不是真正意义上的异步函数,这里的“异步”指的是把多个 setState 合并到一起进行一次性的更新,而不是像 setTimeout() 这类的函数。下面我们先来看一个例子:

例1:

export default function App() {
  const [state, setState] = useState(0)
  const add3 = () => {
    setState(state+1)
    setState(state+1)
    setState(state+1)
  }
  return (
    <div className="App">
      <button onClick={add3}>+3</button>  
      <p>{state}</p>
    </div>
  );
}
  • 请问这段代码中,点击 +3 的按钮,最后 state 会加多少呢?
  • 答:+1
  • 原因:React 会把 setState() 合并为一次批量更新,那么这三个 setState() 会被合并为一个 setState() 而其中,state 是没有发生变化的,三个 setState() 中的 state 的值都是 0,所以每次点击都只能完成 +1 的操作,而不是想要的 +3 的操作

那么有时候会遇到这样的情况:有一个 input,当 input 框内的值变化时,想要把其中的值 log 出来

export default function App() {
  const [state, setState] = useState(0)

  return (
    <div className="App">
      <input onChange={(value) => {
        setState(value.target.value)
        console.log(state)
      }}/>
      <p>{state}</p>
    </div>
  );
}
  • 但是这时候会出现这样的情况:

  • console.log() 的结果与输入的值不一致,每次打出的值都会落后一步
  • 这也是因为 setState() 的“异步”导致的,setState() 在接受一个值后,并不会立刻去改变其中的 state,而是需要去比较前后变化的内容最后再去更新 UI,可以理解为是很耗时间的事情(相对于 console.log 来说)

React 自己封装的 onChange、onClick 等模块中,使用 setState() 都会出现这样的”异步”的情况,想要避免异步,让 setState() 变为同步更新,有下面几种方法

1、不使用 React 自带的 onClick 等方法,而使用原生 js 的 addEventListener 来添加事件监听函数

export default function App() {
  const [state, setState] = useState(0)
  const add3 = () => {
    setState(state => state+1)
    setState(state => state+1)
    setState(state => state+1)
  }
  useEffect(() => {
    const btn = document.getElementById('btn')
    btn.addEventListener('click', add3)
  },[])

  return (
    <div className="App">
      <button id="btn">+3</button>  
      <p>{state}</p>
    </div>
  );
}

3、使用函数式来调用 setState()

export default function App() {
  const [state, setState] = useState(0)
  const add3 = () => {
    setState(state => state+1)
    setState(state => state+1)
    setState(state => state+1)
  }
  return (
    <div className="App">
      <button onClick={add3}>+3</button>  
      <p>{state}</p>
    </div>
  );
}

目前遇到这样的需求,在点击按钮 +3 后,a 执行 +3 的操作,同时需要把 b = a*100。理想情况是,点击后,a = 3,b = 300,a = 6, b = 600 。但是结果往往不如人意,这时候出现了一个延迟的问题

例2:

export default function App() {
  const [a, setA] = useState(0)
  const [b, setB] = useState(0)
  const add3 = () => {
      setA(a => a+1)
      setA(a => a+1)
      setA(a => a+1)
      setB(b => a*100)
  }
  return (
    <div className="App">
      <button onClick={add3}>+3</button>  
      <p>{a}</p>
      <p>{b}</p>
    </div>
  );
}

  • b 的值的增长永远比 a 慢一步,与预想中,b = a*100 的情况不一样,b = 上一次 a 的值 *100
  • 由此得出结论,在一个函数中,不能使用 setState 获取到的值进行相互计算

例3:

export default function App() {
  const [a, setA] = useState(1)
  const [b, setB] = useState(1)
  const add3 = () => {
      setB(b => 10)
      setA(a => a*b)
  }
  return (
    <div className="App">
      <button onClick={add3}>+3</button>  
      <p>{a}</p>
    </div>
  );
}
  • 上面的代码同样存在一个明显的 bug,当第一次点击 +3 的按钮后,a 的值并不会 *3,而是 1,这就是和上面的问题一样,存在一个延迟,第二次再点击 a 才会 *3
  • 要解决这个问题就简单一些,只需要把 setB(b => 10) 放到外部就可以了
export default function App() {
  useEffect(() => {
    setB(b => 10)
  }, [])
  const [a, setA] = useState(1)
  const [b, setB] = useState(1)
  const add3 = () => {
      setA(a => a*b)
  }
  return (
    <div className="App">
      <button onClick={add3}>+3</button>  
      <p>{a}</p>
    </div>
  );
}
声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。