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

Reactjs 在redux存储更改之后但在组件呈现之前更改内部组件状态,reactjs,redux,react-hooks,use-effect,Reactjs,Redux,React Hooks,Use Effect,假设我有一个react组件,它连接到redux商店,但也管理一些内部状态。 内部状态包含要获取的数据,以及用于呈现“正在加载…”文本或获取数据后的数据的加载标志。 我还需要selectedItems,这是我从redux商店得到的东西。这些项目也用于应用程序的其他部分,例如显示当前选定项目的侧栏,因此用户始终知道选择了哪些项目,并可以使用侧栏选择或删除项目 在这个组件中,selectedItems用于帮助映射我获取的数据 所以组件看起来像这样: export default () => {

假设我有一个react组件,它连接到redux商店,但也管理一些内部状态。 内部状态包含要获取的数据,以及用于呈现“正在加载…”文本或获取数据后的数据的加载标志。 我还需要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>
  );
}
export default () => {
  // ...
  const {data, loading} = useLoadData(...someParams);
  // ...
}
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>
  );
}
钩子保持一个内部状态,并以其自身的useEffect(依赖于selectedItems)获取数据,如果selectedItems发生更改,则根据这些项获取新数据

将内部状态也保留在我的组件中有意义吗?组件将是数据的副本,现在我们将其命名为dataToMap,它将在JSX中使用?我还将添加一个带有依赖项[data]的useEffect,它将在加载新数据时设置该组件的状态,基本上更新dataToMap。因此,如果selectedItems发生更改,该组件将在当前使用过时数据映射时重新渲染,然后useLoadData的内部useEffect将开始在内部加载新数据,同时为我提供loading==true,即将进行的渲染将使用该值显示“loading…”文本,然后在获取新数据时,由于[data]而触发我的挂钩Dependency更改会将新dataToMap设置为内部状态,这会触发另一个重新渲染,由于useLoadData在内部加载===false设置,该重新渲染现在会安全地映射到状态中的新数据

因此,该组件的外观如下所示:

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>
  );
}
export default () => {
  // ...
  const {data, loading} = useLoadData(...someParams);
  // ...
}
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中不存在的数据


我想我只需在useLoadData状态下内部存储selectedItems,并将其返回到我的组件以进行映射,例如selectedItemsForMapping。当selectedBuckets更改时,它仍将使用旧的selectedBuckets格式映射对过时数据进行渲染,之后它将返回新的SelectedItems格式映射以及loading==true,这样我可以显示“loading…”,其余内容已在上面讨论过。

我想您需要稍微更改一下设计。“复杂映射”通常不应该发生在渲染中-无论您如何管理状态,您都不希望在每个渲染上重新执行该映射。在这种情况下,最好在原始数据发生变化时计算派生数据,这是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>
  );
}
在这种情况下,渲染所做的一切都是从状态渲染某些内容,因此只要状态中有数据,无论数据是否过时,它都可以安全地执行。当存储区中selectedItems发生更改时,组件将再次呈现,显示旧数据,然后立即设置“加载”状态,呈现加载UI,然后它将获取新数据,处理新数据,将其设置为状态,并呈现新数据。前两次渲染速度足够快,您可能不会注意到陈旧数据的重新渲染;它将或多或少立即显示“加载”UI

但是,由于对selectedItems的任何更改都会触发新的api调用,因此将此api调用移出此组件并移入更改selectedItems的操作创建者可能是有意义的。此组件中当前正在发生的任何复杂映射也将移动到action creator,您当前在此组件中生成的派生数据将改为在redux存储中设置,并且此组件将通过选择器将其取出

这样,由于用户交互,对应用程序状态selectedItems的更改直接连接到通过api调用重新获取数据,而不是通过状态更改下游组件渲染的副作用间接连接。通过在原始状态更改发生的同一位置更改派生数据,代码可以更清楚地反映应用程序的因果性质。这取决于整个应用程序的设计,但在react/redux应用程序中,数据获取通常以这种方式发生在action creator级别,尤其是当有多个UI组件invo时
在改变和呈现相关状态时,这似乎是个好主意。实际上,我试图提供一个简化的例子来说明这个问题,可能有点过于简化了。所谓复杂映射,我指的是映射到JSX,首先映射到数据,然后嵌套映射到selectedItems。顺便说一句,selectedItems并不总是触发这个api调用,只是在应用程序的一个特定页面上,而带有selectedItems的边栏几乎可以在所有页面上看到。我会更新一下我的问题。