所以说我有一个React组件,它连接到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
上映射,该JSX使用的selectedItems
已经根据商店更改进行了更新,但是data
仍然是旧的useEffect
尚未被调用。在更改data
和获取新数据之间的时间里,我根本不想映射selectedItems
。因此,我基本上希望在这两个事件之间将loading
设置为true,但是我该怎么做呢?将其设置在useEffect
中为时已晚,因为已经渲染/更新了该组件,但是在此之前,它已经知道selectedItems
中的更改(实际上在尝试访问某些组件时会导致错误) data
中的属性基于新选择的项,但是data
仍然是旧项,因此它不包含该属性。
这似乎是React应用程序要处理的一个普遍情况,所以我认为我会很快找到解决方案的,但是还没有成功。
更新基于@brendangannon
的答案所以我可能提供了一个过于简化的示例。所谓复杂映射,是指将数据映射到JSX。首先,我在data
上映射,并且在selectedItems
上有一个嵌套映射,它创建了一个类似表格的结构,其中行基于data
,除第一列外,还有列数基于selectedItems
。
我没有提到但现在看来很相关的一件事是,实际上data
是通过另一个钩子到达组件的。
因此,我没有上面的useEffect
,而是这样的:
export default () => {
// ...
const {data, loading} = useLoadData(...someParams);
// ...
}
挂钩保留内部状态,并以自己依赖于useEffect
的{{1}}来获取数据,并且如果selectedItems
更改,则基于这些项目获取新数据。 / p>
将内部状态也保留在我的组件中是否有意义,该状态将是JSX中使用的selectedItems
的副本,现在暂时命名为data
?而且我还将添加一个具有依赖项dataToMap
的{{1}},当加载新数据基本上更新useEffect
时,它将为该组件设置状态。因此,如果更改了[data]
,则此组件将暂时重新使用旧的dataToMap
,而selectedItems
的内部dataToMap
将在内部加载新数据,同时提供我useEffect
(即将到来的渲染将用于显示“正在加载...”文本),然后在获取新的useLoadData
时,由于loading === true
依赖度的变化,我的钩子触发了,设置了新data
进入内部状态,这会触发另一个重新渲染,该渲染可以(而且由于[data]
在内部设置了dataToMap
)现在可以安全地映射到状态中的新数据。>
所以该组件看起来像这样:
loading===false
这让我有两个问题:
useLoadData
钩,这是一个好方法吗?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>
);
}
?更新2
刚刚意识到这将导致同样的问题-如果在已更改useLoadData
的情况下映射过时的useLoadData
(存在新项),它将尝试访问{{1 }}。
我正在考虑将dataToMap
内部存储在selectedItems
状态,然后将其返回给我的组件以进行映射,例如dataToMap
。当selectedItems
发生更改时,它仍将使用旧的useLoadData
进行过时数据的渲染,然后它将返回新的selectedItemsForMapping
和selectedBuckets
,这样我可以显示“正在加载...”,其余内容已在上面进行了讨论。
答案 0 :(得分:1)
我认为您将需要对设计进行一些更改。通常,“复杂映射”不应在渲染中发生-无论您如何管理状态,您都不想在每个渲染上都重新进行该映射。最好在原始数据更改时计算派生数据(在这种情况下,这是API调用的结果),并在“输入”组件时准备使用数据(在这种情况下,当它设置为状态)。将数据处理与呈现分开是一个很好的例子,它说明了单独/单独的职责,并使测试更加容易。
所以它可能看起来像:
export default () => {
const selectedItems = useSelector(state => state.itemsReducer.selectedItems);
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchData('someApi/data').then(res => {
const modifiedData = doComplexMapping(res, selectedItems);
setData(modifiedData);
setLoading(false);
});
}, [selectedItems]);
return (
<div>
{loading ? (<div>Loading...</div>) : (<div>{data}</div>)}
</div>
);
}
在这种情况下,所有render
所做的工作都是从状态中渲染某些内容,因此只要状态中存在数据(无论状态是否陈旧),它就可以安全地执行。当商店中的selectedItems
发生变化时,组件将再次呈现(显示旧数据),然后立即设置“正在加载”状态,呈现正在加载的UI,然后它将获取新数据,对其进行处理,进行设置状态,并呈现新数据。前两个渲染将足够快地进行,以至于您可能不会注意到过时数据的重新渲染。它将立即或多或少地显示“正在加载”用户界面。
但是,由于对selectedItems
所做的任何更改都会触发一个新的api调用,因此将这个api调用移出该组件并移至更改selectedItems
的操作创建器中可能是有意义的。该组件中当前发生的任何复杂映射都将移至动作创建者,而您当前在该组件中生成的派生数据将被设置在redux存储中,并且该组件将通过选择器将其拉出。
这样,对应用程序状态的更改(selectedItems
由于用户交互而发生了更改)直接(通过api调用)连接到重新获取数据,而不是通过组件渲染的副作用间接连接到状态改变的下游。通过在原始状态更改发生的同一位置更改派生数据,代码可以更清楚地反映应用程序的因果关系。这取决于整个应用程序的设计,但是在react / redux应用程序中,通常以这种方式在操作创建者级别进行数据获取,尤其是当涉及更改和呈现相关状态的多个UI组件时