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

有父子两个组件 父子都有一个数组 父组件的数组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处,加上判断来执行. 但明显这么写很不友好.

阅读 991
avatarAI BotBETA

你可以通过将更新表格数据的操作放在 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

3 个回答

这其实是一个组件设计上的问题,用于渲染的表格数据只需要有一份即可,通常作为根组件的 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>
    )
}
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>
    )
}

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>
    )
}
推荐问题
logo
Microsoft
子站问答
访问
宣传栏