如何在useEffect中使调度调用同步?
How to make dispatch call synchronous in useeffect?
我正在尝试使用 CoreUI Table 组件对来自我的其余服务器的数据进行分页。
我在 useEffect 中发送请求后从 redux store 获取更新数据时遇到问题,我正在使用 redux thunk,我知道发送是异步的,但是有没有办法等待发送完成?我厌倦了让调度成为一个承诺,但它没有用。
我成功地从 action 和 reducer 获得了更新的结果,但是在 ProductsTable 它是前一个,我检查了 redux devtools 扩展,我可以看到状态正在改变。
我从来没有从商店得到最新的价值。
我在控制台中看到调度被调用了很多次window,它不是无限循环,它会在一段时间后停止。
const ProductsTable = (props) => {
const store = useSelector((state) => state.store);
const dispatch = useDispatch();
const [items, setItems] = useState([]);
const [loading, setLoading] = useState(true);
const [page, setPage] = useState(1);
const [pages, setPages] = useState(1);
const [itemsPerPage, setItemsPerPage] = useState(5);
const [fetchTrigger, setFetchTrigger] = useState(0);
useEffect(() => {
setLoading(true);
const payload = {
params: {
page,
},
};
if (page !== 0)
dispatch(getAllProducts(payload));
console.log("runs:" + page)
console.log(store.objects)
if(!(Object.keys(store.objects).length === 0)){
setItems(store.objects.results)
setPages(store.objects.total.totalPages)
setLoading(false)
} else{
console.log("error")
setFetchTrigger(fetchTrigger + 1);
}
}, [page, fetchTrigger]);
return (
<CCard className="p-5">
<CDataTable
items={items}
fields={["title", "slug", {
key: 'show_details',
label: '',
_style: { width: '1%' },
sorter: false,
filter: false
}]}
loading={loading}
hover
cleaner
sorter
itemsPerPage={itemsPerPage}
onPaginationChange={setItemsPerPage}
<CPagination
pages={pages}
activePage={page}
onActivePageChange={setPage}
className={pages < 2 ? "d-none" : ""}
/>
</CCard>
)
}
export default ProductsTable
首先你不需要任何同步来实现你想要的结果,你必须切换你的代码,这样它就不会使用反应状态,因为你已经在使用某种全局存储(我假设 redux );您需要做的是直接从商店中获取所有商品,不要对组件执行额外的逻辑(阅读关注点分离);此外,我建议在服务器端进行分页,而不仅仅是在前端对数据进行分页。 (getAllProducts() 方法打开只获取一页结果而不是所有产品);您的代码有很多分派,因为您使用 page 和 fetchTrigger 作为 useEffect 挂钩的依赖项,这意味着每次页面或 fetchTrigger 值更改时,useEffect 中的代码将再次 运行 导致另一个分派;
这里是你的代码的一个稍微修改的部分,你需要在你的动作中添加一些额外的东西,并在你的全局状态中添加一个加载参数
const ProductsTable = (props) => {
const dispatch = useDispatch();
// PUT THE LOADING IN THE GLOBAL STATE OR HANDLE IT VIA A CALLBACK OR ADD A GLOBAL MECHANISM TO HANLE LOADINGS INSIDE THE APP
const loading = useSelector(() => state.store.loading)
const items = useSelector((state) => state.store.objects?.results); // ADD DEFAULT EMPTY VALUES FOR objects smthg like : { objects: { results: [], total: { totalPages: 0 } }}
const pages = useSelector((state) => state.store.objects?.total?.totalPages);
const [page, setPage] = useState(1);
const [itemsPerPage, setItemsPerPage] = useState(5);
const [fetchTrigger, setFetchTrigger] = useState(0); // I DONT UNDERSTAND THIS ONE
const fetchData = () => dispatch(getAllProducts({ params: { page }}));
useEffect(() => {
fetchData();
}, []);
return (
<CCard className="p-5">
<CDataTable
items={items}
fields={["title", "slug", {
key: 'show_details',
label: '',
_style: { width: '1%' },
sorter: false,
filter: false
}]}
loading={loading}
hover
cleaner
sorter
itemsPerPage={itemsPerPage}
onPaginationChange={setItemsPerPage}
<CPagination
pages={pages}
activePage={page}
onActivePageChange={setPage}
className={pages < 2 ? "d-none" : ""}
/>
</CCard>
)
}
export default ProductsTable
ProductsTable
始终具有先前状态数据的原因是因为您用于更新 ProductsTable
的效果缺少 store
作为依赖项或更具体地说 store.objects.results
;当 page
和 fetchTrigger
改变时,效果变得陈旧,因为它不知道当这些依赖关系改变时效果应该改变。
useEffect(() => {
// store.objects is a dependency that is not tracked
if (!(Object.keys(store.objects).length === 0)) {
// store.objects.results is a dependency that is not tracked
setItems(store.objects.results);
// store.objects.total.totalPages is a dependency that is not tracked
setPages(store.objects.total.totalPages);
setLoading(false);
}
// add these dependencies to the effect so that everything works as expected
// avoid stale closures
}, [page, fetchTrigger, store.objects, store.objects.results, store.objects.total.totalPages]);
调度被调用多次,因为你有一个递归的情况,其中 fetchTrigger
是效果的依赖,但你也从效果中更新它。通过删除该依赖项,您将看到对此效果的调用更少,即仅在页面更改时调用。我不知道你需要那个值做什么,因为我没有看到它在你共享的代码中使用,但如果你确实需要它,我建议使用 setState 的 callback
版本,以便你可以引用该值您需要的 fetchTrigger
个,无需将其添加为依赖项。
useEffect(() => {
// code
if (!(Object.keys(store.objects).length === 0)) {
// code stuffs
} else {
// use the callback version of setState to get the previous/current value of fetchTrigger
// so you can remove the dependency on the fetchTrigger
setFetchTrigger(fetchTrigger => fetchTrigger + 1);
}
// remove fetchTrigger as a dependency
}, [page, store.objects, store.objects.results, store.objects.totalPages]);
解释了这些问题后,最好不要为 items
、pages
或 loading
添加新状态,而是从 redux 存储中获取它,因为看起来就是这样。
const items = useSelector((state) => state.store.objects?.results);
const pages = useSelector((state) => state.store.objects?.total?.totalPages);
const loading = useSelector((state) => !Object.keys(state.store.objects).length === 0);
并完全移除效果以支持添加到 onActivePageChange
事件的功能。
const onActivePageChange = page => {
setPage(page);
setFetchTrigger(fetchTrigger => fetchTrigger + 1);
dispatch(getAllProducts({
params: {
page,
},
}));
};
return (
<CPagination
// other fields
onActivePageChange={onActivePageChange}
/>
);
但是对于初始结果,您仍然需要一些方法来获取,您可以使用仅在安装组件时运行一次的效果来执行此操作。这应该这样做,因为调度不应该改变。
// on mount lets get the initial results
useEffect(() => {
dispatch(
getAllProducts({
params: {
page: 1,
},
})
);
},[dispatch]);
加上建议的更改后看起来像这样:
const ProductsTable = props => {
const items = useSelector(state => state.store.objects?.results);
const pages = useSelector(state => state.store.objects?.total?.totalPages);
const loading = useSelector(state => !Object.keys(state.store.objects).length === 0);
const dispatch = useDispatch();
const [page, setPage] = useState(1);
const [itemsPerPage, setItemsPerPage] = useState(5);
const [fetchTrigger, setFetchTrigger] = useState(0);
// on mount lets get the initial results
useEffect(() => {
dispatch(
getAllProducts({
params: {
page: 1,
},
})
);
},[dispatch]);
const onActivePageChange = page => {
setPage(page);
setFetchTrigger(fetchTrigger => fetchTrigger + 1);
dispatch(
getAllProducts({
params: {
page,
},
})
);
};
return (
<CCard className="p-5">
<CDataTable
items={items}
fields={[
'title',
'slug',
{
key: 'show_details',
label: '',
_style: { width: '1%' },
sorter: false,
filter: false,
},
]}
loading={loading}
hover
cleaner
sorter
itemsPerPage={itemsPerPage}
onPaginationChange={setItemsPerPage}
/>
<CPagination
pages={pages}
activePage={page}
onActivePageChange={onActivePageChange}
className={pages < 2 ? 'd-none' : ''}
/>
</CCard>
);
};
export default ProductsTable;
我正在尝试使用 CoreUI Table 组件对来自我的其余服务器的数据进行分页。 我在 useEffect 中发送请求后从 redux store 获取更新数据时遇到问题,我正在使用 redux thunk,我知道发送是异步的,但是有没有办法等待发送完成?我厌倦了让调度成为一个承诺,但它没有用。
我成功地从 action 和 reducer 获得了更新的结果,但是在 ProductsTable 它是前一个,我检查了 redux devtools 扩展,我可以看到状态正在改变。
我从来没有从商店得到最新的价值。
我在控制台中看到调度被调用了很多次window,它不是无限循环,它会在一段时间后停止。
const ProductsTable = (props) => {
const store = useSelector((state) => state.store);
const dispatch = useDispatch();
const [items, setItems] = useState([]);
const [loading, setLoading] = useState(true);
const [page, setPage] = useState(1);
const [pages, setPages] = useState(1);
const [itemsPerPage, setItemsPerPage] = useState(5);
const [fetchTrigger, setFetchTrigger] = useState(0);
useEffect(() => {
setLoading(true);
const payload = {
params: {
page,
},
};
if (page !== 0)
dispatch(getAllProducts(payload));
console.log("runs:" + page)
console.log(store.objects)
if(!(Object.keys(store.objects).length === 0)){
setItems(store.objects.results)
setPages(store.objects.total.totalPages)
setLoading(false)
} else{
console.log("error")
setFetchTrigger(fetchTrigger + 1);
}
}, [page, fetchTrigger]);
return (
<CCard className="p-5">
<CDataTable
items={items}
fields={["title", "slug", {
key: 'show_details',
label: '',
_style: { width: '1%' },
sorter: false,
filter: false
}]}
loading={loading}
hover
cleaner
sorter
itemsPerPage={itemsPerPage}
onPaginationChange={setItemsPerPage}
<CPagination
pages={pages}
activePage={page}
onActivePageChange={setPage}
className={pages < 2 ? "d-none" : ""}
/>
</CCard>
)
}
export default ProductsTable
首先你不需要任何同步来实现你想要的结果,你必须切换你的代码,这样它就不会使用反应状态,因为你已经在使用某种全局存储(我假设 redux );您需要做的是直接从商店中获取所有商品,不要对组件执行额外的逻辑(阅读关注点分离);此外,我建议在服务器端进行分页,而不仅仅是在前端对数据进行分页。 (getAllProducts() 方法打开只获取一页结果而不是所有产品);您的代码有很多分派,因为您使用 page 和 fetchTrigger 作为 useEffect 挂钩的依赖项,这意味着每次页面或 fetchTrigger 值更改时,useEffect 中的代码将再次 运行 导致另一个分派;
这里是你的代码的一个稍微修改的部分,你需要在你的动作中添加一些额外的东西,并在你的全局状态中添加一个加载参数
const ProductsTable = (props) => {
const dispatch = useDispatch();
// PUT THE LOADING IN THE GLOBAL STATE OR HANDLE IT VIA A CALLBACK OR ADD A GLOBAL MECHANISM TO HANLE LOADINGS INSIDE THE APP
const loading = useSelector(() => state.store.loading)
const items = useSelector((state) => state.store.objects?.results); // ADD DEFAULT EMPTY VALUES FOR objects smthg like : { objects: { results: [], total: { totalPages: 0 } }}
const pages = useSelector((state) => state.store.objects?.total?.totalPages);
const [page, setPage] = useState(1);
const [itemsPerPage, setItemsPerPage] = useState(5);
const [fetchTrigger, setFetchTrigger] = useState(0); // I DONT UNDERSTAND THIS ONE
const fetchData = () => dispatch(getAllProducts({ params: { page }}));
useEffect(() => {
fetchData();
}, []);
return (
<CCard className="p-5">
<CDataTable
items={items}
fields={["title", "slug", {
key: 'show_details',
label: '',
_style: { width: '1%' },
sorter: false,
filter: false
}]}
loading={loading}
hover
cleaner
sorter
itemsPerPage={itemsPerPage}
onPaginationChange={setItemsPerPage}
<CPagination
pages={pages}
activePage={page}
onActivePageChange={setPage}
className={pages < 2 ? "d-none" : ""}
/>
</CCard>
)
}
export default ProductsTable
ProductsTable
始终具有先前状态数据的原因是因为您用于更新 ProductsTable
的效果缺少 store
作为依赖项或更具体地说 store.objects.results
;当 page
和 fetchTrigger
改变时,效果变得陈旧,因为它不知道当这些依赖关系改变时效果应该改变。
useEffect(() => {
// store.objects is a dependency that is not tracked
if (!(Object.keys(store.objects).length === 0)) {
// store.objects.results is a dependency that is not tracked
setItems(store.objects.results);
// store.objects.total.totalPages is a dependency that is not tracked
setPages(store.objects.total.totalPages);
setLoading(false);
}
// add these dependencies to the effect so that everything works as expected
// avoid stale closures
}, [page, fetchTrigger, store.objects, store.objects.results, store.objects.total.totalPages]);
调度被调用多次,因为你有一个递归的情况,其中 fetchTrigger
是效果的依赖,但你也从效果中更新它。通过删除该依赖项,您将看到对此效果的调用更少,即仅在页面更改时调用。我不知道你需要那个值做什么,因为我没有看到它在你共享的代码中使用,但如果你确实需要它,我建议使用 setState 的 callback
版本,以便你可以引用该值您需要的 fetchTrigger
个,无需将其添加为依赖项。
useEffect(() => {
// code
if (!(Object.keys(store.objects).length === 0)) {
// code stuffs
} else {
// use the callback version of setState to get the previous/current value of fetchTrigger
// so you can remove the dependency on the fetchTrigger
setFetchTrigger(fetchTrigger => fetchTrigger + 1);
}
// remove fetchTrigger as a dependency
}, [page, store.objects, store.objects.results, store.objects.totalPages]);
解释了这些问题后,最好不要为 items
、pages
或 loading
添加新状态,而是从 redux 存储中获取它,因为看起来就是这样。
const items = useSelector((state) => state.store.objects?.results);
const pages = useSelector((state) => state.store.objects?.total?.totalPages);
const loading = useSelector((state) => !Object.keys(state.store.objects).length === 0);
并完全移除效果以支持添加到 onActivePageChange
事件的功能。
const onActivePageChange = page => {
setPage(page);
setFetchTrigger(fetchTrigger => fetchTrigger + 1);
dispatch(getAllProducts({
params: {
page,
},
}));
};
return (
<CPagination
// other fields
onActivePageChange={onActivePageChange}
/>
);
但是对于初始结果,您仍然需要一些方法来获取,您可以使用仅在安装组件时运行一次的效果来执行此操作。这应该这样做,因为调度不应该改变。
// on mount lets get the initial results
useEffect(() => {
dispatch(
getAllProducts({
params: {
page: 1,
},
})
);
},[dispatch]);
加上建议的更改后看起来像这样:
const ProductsTable = props => {
const items = useSelector(state => state.store.objects?.results);
const pages = useSelector(state => state.store.objects?.total?.totalPages);
const loading = useSelector(state => !Object.keys(state.store.objects).length === 0);
const dispatch = useDispatch();
const [page, setPage] = useState(1);
const [itemsPerPage, setItemsPerPage] = useState(5);
const [fetchTrigger, setFetchTrigger] = useState(0);
// on mount lets get the initial results
useEffect(() => {
dispatch(
getAllProducts({
params: {
page: 1,
},
})
);
},[dispatch]);
const onActivePageChange = page => {
setPage(page);
setFetchTrigger(fetchTrigger => fetchTrigger + 1);
dispatch(
getAllProducts({
params: {
page,
},
})
);
};
return (
<CCard className="p-5">
<CDataTable
items={items}
fields={[
'title',
'slug',
{
key: 'show_details',
label: '',
_style: { width: '1%' },
sorter: false,
filter: false,
},
]}
loading={loading}
hover
cleaner
sorter
itemsPerPage={itemsPerPage}
onPaginationChange={setItemsPerPage}
/>
<CPagination
pages={pages}
activePage={page}
onActivePageChange={onActivePageChange}
className={pages < 2 ? 'd-none' : ''}
/>
</CCard>
);
};
export default ProductsTable;