当前位置: 首页 > 知识库问答 >
问题:

前端 - 在set了useState之后,如何保证代码在由set引发的useEffect之后执行?

景阳曜
2023-10-11

有父子两个组件 父子都有一个数组 父组件的数组list更新, 子组件的数组tableData会跟着更新,并显示在表格上.
现在有个新增的按钮, 要求在新增时 先更新一下表格 再插入新的一行. 但是更新表格是在父组件请求,子组件通过useEffect跟着更新,导致新增时插入的一行被覆盖了(useEffect更新的逻辑比插入逻辑晚导致被覆盖),要怎么处理.

// 父组件const Father = () => {    const [list, setList] = useState([])    const getListData = async () => {        // 请求获取list数据        const res = await getList().catch(() => {})        setList(res.data)    }    return (<Son list={list} getListData={getListData} />)}
// 子组件const Son = ({list, getListData}) => {    const [tableData, setTableData] = useState([])    // 当父组件的数据更新时 子组件也跟着更新    useEffect(() => {        setTableData(list)    }, [list])    // 新增    const onAdd = async () => {        // 在新增之前先获取一下最新的表格数据list 调用父组件的请求函数 因为useEffect 当前子组件的tableData也会更新一下        await getListData()        // 给当前组件的表格数据添加一行新增的空行(不改变父组件的数据) 但是由于这里会先于useEffect执行 所以会被覆盖 没有效果        setTableData([{空行数据}, ...tableData])    }    return (        <div>            <Table dataSource={tableData} />            <Button onClick={onAdd}>新增</Button>        </div>    )}

一个办法是把新增一行的逻辑移到useEffect处,加上判断来执行. 但明显这么写很不友好.

共有4个答案

贺亦
2023-10-11

Son 组件的 list 发生变化以后会 setTableData,导致覆盖了 onAdd 里已经 set 的值。
我们可以这么做,把 onAdd 的值单独放到一个 state 里,然后在渲染时结合 props 里的 list 属性计算 dataSource 的值

// 子组件const Son = ({list, getListData}) => {    // const [tableData, setTableData] = useState([])    // 当父组件的数据更新时 子组件也跟着更新    // useEffect(() => {    //     setTableData(list)    // }, [list])    const [newItem, setNewItem] = useState();    // 新增    const onAdd = async () => {        // 在新增之前先获取一下最新的表格数据list 调用父组件的请求函数 因为useEffect 当前子组件的tableData也会更新一下        await getListData()        setNewItem({空行数据})    }    return (        <div>            <Table dataSource={newItem ? [newItem,...list] : list} />            <Button onClick={onAdd}>新增</Button>        </div>    )}
耿和韵
2023-10-11
const Son = ({list, getListData}) => {    const [tableData, setTableData] = useState([]);    const [isUpdating, setIsUpdating] = useState(false);    useEffect(() => {        if (isUpdating) {            setTableData([空行数据, ...list]);            setIsUpdating(false); // 重置状态        } else {            setTableData(list);        }    }, [list]);    const onAdd = async () => {        setIsUpdating(true);        await getListData();    }    return (        <div>            <Table dataSource={tableData} />            <Button onClick={onAdd}>新增</Button>        </div>    )}
雷方伟
2023-10-11

这其实是一个组件设计上的问题,用于渲染的表格数据只需要有一份即可,通常作为根组件的 state 是没问题的。但你这里子组件又维护了一份 state,对于组件的渲染来说形成了一个双数据源,不仅容易造成你描述的这种问题,也无端增加了开发过程的心智负担。

正确的做法是 子组件 直接使用父组件传过来的 list 去渲染,不用再存一个 state,如果存在子组件需要更新父组件的 state 的情况,应该从父组件传一个用于更新的方法给子组件。

总之保证 渲染数据的单一性 以及 更新数据方式的唯一性 基本上就能避免这种问题的出现

比较简单的处理方式如下

// 父组件const Father = () => {    const [list, setList] = useState([])    const getListData = async () => {        // 请求获取list数据        const res = await getList().catch(() => {})        setList(res.data)    }    return (<Son list={list} setList={setList} getListData={getListData} />)}
// 子组件const Son = ({list, setList, getListData}) => {    // 新增    const onAdd = async () => {        await getListData()        setList((prevList) => [{空行数据}, ...prevList])    }    return (        <div>            <Table dataSource={list} />            <Button onClick={onAdd}>新增</Button>        </div>    )}
海宁
2023-10-11

你可以通过将更新表格数据的操作放在 useEffect 内部来解决这个问题。这样的话,当表格数据更新后,新增的行也会被正确显示出来。以下是你可以尝试的修改后的代码:

父组件代码不变:

// 父组件const Father = () => { const [list, setList] = useState([]); const getListData = async () => { // 请求获取list数据 const res = await getList().catch(() => {}); setList(res.data); }; return (<Son list={list} getListData={getListData} />);};

子组件的代码如下:

// 子组件const Son = ({list, getListData}) => { const [tableData, setTableData] = useState([]); // 当父组件的数据更新时 子组件也跟着更新 useEffect(() => { setTableData(list); }, [list]); // 新增 const onAdd = async () => { // 在新增之前先获取一下最新的表格数据list 调用父组件的请求函数 因为useEffect 当前子组件的tableData也会更新一下 await getListData(); }; useEffect(() => { // 在新增后,再次获取数据,并且插入新的空行 const newTableData = [...tableData, {空行数据}]; setTableData(newTableData); }, []);  // 注意这里传入的是一个空数组,这样这个useEffect就只会在组件挂载和更新时执行,不会在onAdd点击时执行 return ( <div> <Table dataSource={tableData} /> <Button onClick={onAdd}>新增</Button> </div> );};

在这个修改后的代码中,新增的数据是通过 useEffect 插入的,而不是在 onAdd 函数中插入的。这样就保证了在 useEffect 更新表格数据后,新增的行会被正确显示出来。注意在 useEffect 中我们使用的是 [tableData],这样每次 tableData 更新时,useEffect 就会执行。在 useEffect 中我们获取新的数据并将新的空行添加进去,然后设置新的 tableData

 类似资料:
  • 请帮助理解ExecutorService#AwaitTermination(timeout)行为。 当我的代码中有: 在本例中,pool中的任务是否在同一线程中shutdownAndAwaitTermination()之后的任何其他调用之前完成? 看看在生产过程中发生的事情,我相信答案是否定的,但我只想了解如何确保在调用shutdownAndAwaitTermination()后放置的任何代码将在

  • 问题内容: 我正在编写一个swing应用,当执行某些方法时,我希望有“ wait”光标。我们可以这样: 我想要实现的是一个Java批注,该批注将在方法执行之前设置等待游标,并在执行后将其设置回正常状态。所以前面的例子看起来像这样 我怎样才能做到这一点?也欢迎提出有关解决此问题的其他方法的建议。谢谢! PS-我们在项目中使用Google Guice,但我不知道如何使用它来解决问题。如果有人为我提供类

  • 问题内容: 我想知道以下代码的行为背后的机制是什么: 我的理解是不 返回 函数,而是 关闭连接/结束请求 。这可以解释为什么我仍然可以在命令后执行代码(我查看了快速源,但它似乎不是异步函数)。 还有其他我可能会想念的东西吗? 问题答案: 当然可以结束HTTP响应,但是它对您的代码没有做任何特殊的事情。 即使您已结束回复,也可以继续做其他事情。 但是,您 无法 做的是利用进行任何有用的操作。由于响应

  • 问题内容: ExpressJS中间件,,有像钩子和。 但是我正在寻找和方法的挂钩。 问题答案: 和中间件可用于“ 之前 ”,而and 事件的组合可用于“ 之后” 。 中间件就是一个例子,默认情况下,它将在响应后追加到日志中。 只需确保先使用此“ 中间件 ”即可,因为顺序很重要。

  • 因为我们知道progressdialog需要一个参数上下文或获取片段的活动,但在解除对话框后,它转到了另一个片段,我将其设置为mainactivity默认片段为homepage 这是我课堂上使用的方法

  • 我正在Java的Android应用程序中工作,其中调用类并在OnCreateView方法中进行测量。然而,一旦完成,我必须自动发送他的测量结果。但我不能。是否有继承自:java.lang.对象android.app.片段的方法,可以在OncreateView进程之后执行函数。因为目前数据已经发送,但还没有时间填充。 谢谢你的帮助