【发布时间】:2020-01-30 23:18:29
【问题描述】:
假设我有一个连接到 redux 存储的反应组件,但也管理一些内部状态。
内部状态包含要获取的data 和loading 标志,用于呈现“正在加载...”文本或获取后的数据。
我还需要selectedItems,这是我从 redux 商店获得的。这些项目也用于应用程序的其他部分,例如侧边栏显示当前选择的项目,因此用户始终知道哪些项目被选中,并可以使用侧边栏选择或删除项目。
在这个组件中,selectedItems 用于帮助映射我获取的数据。
所以组件看起来像这样:
export default () => {
const selectedItems = useSelector(state => state.itemsReducer.selectedItems);
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchData('someApi/data').then(res => {
setData(res);
setLoading(false);
});
}, [selectedItems]);
return (
<div>
{loading ? (<div>Loading...</div>) : (<div>{data}</div>)}
</div>
);
}
现在假设在<div>{data}</div> 部分我实际上做了一些复杂的映射,我使用来自selectedItems 的信息来正确映射数据。
我遇到的问题是:如果用户通过侧边栏添加了一个项目,然后更改了商店中的selectedItems,这意味着当前的data 不再有效,我需要获取新数据。因此,在商店更改后,我想立即将loading 设置为true 以显示“正在加载...”文本,然后在组件安装后获取新数据,因此在useEffect 中。所以,我知道 useEffect 只有在组件渲染后才会被调用。但是,我在 JSX 中映射 data,它使用已经根据商店更改更新的 selectedItems,但 data 仍然是旧的 useEffect 尚未被调用。在更改selectedItems 并获取新数据之间的时间段内,我根本不想映射data。所以我基本上希望在这两个事件之间将loading 设置为true,但是我将如何去做呢?在useEffect 中设置它为时已晚,因为组件已经被渲染/更新,但在此之前,它已经知道selectedItems 中的更改(这实际上会导致错误,因为它试图访问@987654343 中的某些属性@ 基于新选择的项目,但 data 仍然是旧项目,因此不包含该项目。
这整个事情似乎是一个反应应用程序要处理的常见情况,所以我想我会很快找到解决方案,但是,还没有成功。
更新基于@brendangannon 的回答
所以我可能提供了一个过于简单的例子。复杂映射是指将数据映射到 JSX。首先我映射到data 并且在selectedItems 上有一个嵌套映射,它创建了一个类似表格的结构,其中行基于data,除了第一列之外,列数基于@987654348 @。
我没有提到但现在看起来很相关的一件事是,实际上data 通过另一个钩子到达组件。
所以我有这样的东西,而不是上面的useEffect:
export default () => {
// ...
const {data, loading} = useLoadData(...someParams);
// ...
}
钩子保持一个内部状态,并在它自己的依赖于selectedItems的useEffect中获取数据,如果selectedItems发生变化,则根据这些项目获取新数据。
在我的组件中也保留内部状态是否有意义,这将是data 的副本,我们现在将其命名为dataToMap,它将在 JSX 中使用?我还会添加一个useEffect 和一个依赖[data],它会在加载新数据时设置这个组件的状态,基本上更新dataToMap。因此,如果selectedItems 更改,此组件将在使用陈旧的dataToMap 时重新渲染,然后useLoadData 的内部useEffect 将开始在内部加载新数据,同时给我loading === true,即将到来的渲染将用于显示“正在加载...”文本,然后当获取新的data 时,我的钩子由于[data] 的依赖变化而触发,将新的dataToMap 设置为内部状态,这会触发另一个重新渲染现在可以(并且将由于useLoadData 在内部设置loading===false)安全地映射到状态中的新数据。
所以组件看起来像这样:
export default () => {
// ...
const selectedItems = useSelector(state => state.itemsReducer.selectedItems);
const {data, loading} = useLoadData(...someParams);
const [dataToMap, setDataToMap] = useState([]);
useEffect(() => {
if (loading === false) {
// new data fetched
setDataToMap(data);
}
}, [data]);
return (
<div>
{loading ? (<div>Loading...</div>) : (
<div>
{dataToMap.map(dataEl => {
return (
<>
// render some row stuff
{selectedItems.map(selItem => {
// render some column stuff
})}
</>
)
})}
</div>
)}
</div>
);
}
这给我留下了两个问题:
- 如果我保留
useLoadData挂钩,这是一个好方法吗? - 考虑可能删除
useLoadData,是否有更好的方法?
更新 2
刚刚意识到这会导致同样的问题 - 当陈旧的 dataToMap 被映射而 selectedItems 已经更改(存在新项目)时,它会尝试访问不在 dataToMap 中的数据。
我想我将在内部将selectedItems 存储在useLoadData 状态并将其返回到我的组件以进行映射,例如selectedItemsForMapping。当selectedBuckets 更改时,它仍会使用旧的selectedBucketsForMapping 来渲染陈旧的数据,然后它会返回新的selectedItemsForMapping 和loading===true,这样我就可以显示“正在加载...”和其余的已经在上面讨论过了。
【问题讨论】:
标签: reactjs redux react-hooks use-effect