在Redux存储更改之后但组件呈现之前更改内部组件状态

时间:2019-10-01 16:03:00

标签: reactjs redux react-hooks use-effect

所以说我有一个React组件,它连接到redux存储,但也管理一些内部状态。 内部状态包含要提取的dataloading标志,该标志用于呈现“正在加载...”文本或获取后的数据。 我还需要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

这让我有两个问题:

  1. 如果我保留useLoadData钩,这是一个好方法吗?
  2. 是否有一种更好的方法可以考虑删除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进行过时数据的渲染,然后它将返回新的selectedItemsForMappingselectedBuckets,这样我可以显示“正在加载...”,其余内容已在上面进行了讨论。

1 个答案:

答案 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组件时