React useState的两种赋值办法区别?

感谢各位朋友进来观看,哪怕只是了解并未解惑。
若闲代码多,小弟也可以略微解释一下。
主要的问题就是:setPage后,useEffect能监听到page发生了变化。可在loadData函数内部使用时,拿到的还是未更改时的page值。

下面是具体代码:

import { useState, useEffect } from 'react'

export default function () {
  const [page, setPage] = useState({ current: 1, pageSize: 10 });
  function loadData() {
    console.log({ current: page.current })
  }

  useEffect(() => {
    console.log('发生了改变', page)
  }, [page])

  function onClick() {
    setPage((pre) => ({ ...pre, current: pre.current + 1 }))
    setTimeout(() => {
      console.log('加载数据时', page)
      loadData()
    }, 1000)
  }
  return <>
    <div>
      <p>测试页面</p>
      <button onClick={onClick}>添加</button>
    </div>
  </>
}

感谢各位朋友能完整的看完代码,若有看法或者其他都可以说出来,谢谢各位

回复
阅读 623
4 个回答

问题原因:函数组件里通过 useState 获取的 pageloadData 里的 page 形成一个闭包。点击按钮后,函数组件重新执行,此时 useState 返回的了一个新的 page,注意区分这个新 page 不是之前形成闭包的旧 page,而 loadDatapage 绑定的是旧的 page,所以你拿到的是未更改的值。

解决方案:将 setTimeout 这种带有副作用的函数放在 useEffect 中处理:

import { useState, useEffect } from "react";

export default function App() {
  const [page, setPage] = useState({ current: 1, pageSize: 10 });
  function loadData() {
    console.log({ current: page.current });
  }

  useEffect(() => {
    console.log("发生了改变", page);
  }, [page]);

// 带有副作用的函数放在 useEffect 处理
  useEffect(() => {
    const id = setTimeout(() => {
      console.log("加载数据时", page);
      loadData();
    }, 1000);
    return () => clearTimeout(id);
  }, [page]);

  function onClick() {
    setPage((pre) => ({ ...pre, current: pre.current + 1 }));
    // setTimeout(() => {
    //   console.log("加载数据时", page);
    //   loadData();
    // }, 1000);
  }
  return (
    <>
      <div>
        <p>测试页面</p>
        <button onClick={onClick}>添加</button>
      </div>
    </>
  );
}

这是 React 的经典闭包问题,回答的比较简略,希望对你有所帮助。

function onClick() {
    // 这个函数体内,page都是修改之前的,setPage是异步的,loadData先执行,page值后修改
    setPage((pre) => ({ ...pre, current: pre.current + 1 }))
    loadData();
  }

本质原因是闭包,因为使用的是 函数式组件,因此你的 hooks 的值发生改变时,就会重新执行整个函数,函数中的内容会重新被执行,但是你的 setTimeout 中使用了其回调函数外部的自由变量产生了闭包,因此你的 setTimeout 的回调真正被执行时拿到的就是旧的值。

本文参与了SegmentFault 思否面试闯关挑战赛,欢迎正在阅读的你也加入。

通过修改旧 state 的方式是不可靠的,比如

const [value, setValue] = useState(0)

const onClick = () => {
   setValue(value + 1)
   setValue(value + 1)
   setValue(value + 1)
}

这个函数调用后会将 value 修改为 3 吗?不会,因为 value 始终是 0,那么最后新的 value 就是 0+1=1.

而使用回调的方式:

const [value, setValue] = useState(0)

const onClick = () => {
   const updater = (prev: number) => prev + 1
   setValue(updater)
   setValue(updater)
   setValue(updater)
}

这时候你传入的是一个函数 updater 。谁来负责调用这个函数 updater?React!所以 React 知道将最近的值传入进去进行计算,然后将这个函数 updater 返回的值当作最新的state。

这就是控制反转.

推荐问题
宣传栏