如何在挂钩中比较来自 react redux 状态的值

Posted

技术标签:

【中文标题】如何在挂钩中比较来自 react redux 状态的值【英文标题】:How to compare values from react redux state in hooks 【发布时间】:2021-10-24 00:13:05 【问题描述】:

我最近使用 hooks 编写了一个表格组件,每次页面加载时都会对后端进行 API 调用,因此同时会显示一个正在加载的 Spinner,直到有来自 API 的响应。我使用 redux 作为状态管理,所以当有来自 API 的响应时,会调度一个动作并更新状态。 所以这里的问题是,通常在类组件中我们可以比较 prevProps 和 nextProps 使用

componentDidUpdate(prevProps)
  if(this.props.someState !== prevProps.someState)
      // do something
    

但我不确定如何使用 Hooks 实现相同的目标。我还提到了这个***问题 How to compare oldValues and newValues on React Hooks useEffect?

但这个解决方案似乎不适用于我的情况。我确实尝试创建自定义钩子 usePrevious 并创建一个 ref 来比较当前值,但这并没有解决我的问题。

这是我的部分代码。

let loading = true;

let tableData = useSelector((state) => 
   
    if (
      state.common.tableDetails.data &&
      state.common.tableDetails.status === true
    ) 
      loading = false;
      return state.common.tableDetails.data;
    
   
    if (
      state.common.tableDetails.data &&
      state.common.tableDetails.status === false
      
    ) 
      loading = true;
    
    return [];
  );

// table component

<Fragment>
   
   loading === true ? <Spinner />  :  <TableComponent tableData=tableData />
   
</Fragment>

因此,每当组件加载时,如果 redux state 中存在任何数据,则会显示该数据,并且不会对 prevProps 和 nextProps 进行比较,因为加载微调器不会显示,并且在新调用的 api 响应之后状态将被更新,新数据将显示在表格中。

更新 1: 这是 dispatch 和 action 以及 reducer 的代码

 useEffect(() => 
    
    dispatch(fetchDetails(params.someID));

  , [dispatch, params.someID]);

动作文件

export const fetchDetails = (data) => (dispatch) => 
  axios
    .post(
      `$SomeURL/fetchAll`,
      data
    )
    .then((res) => 
      if (res.data.status) 
        dispatch(
          type: FETCH_DETAILS,
          payload: res.data,
        );
      
    )
    .catch((err) => console.log(err));
;

减速器文件

const initialState = 
  tableDetails: ,
;

export default function (state = initialState, action) 
  switch (action.type) 
    case FETCH_DETAILS:
      return 
        ...state,
        tableDetails: action.payload,
      ;

    default:
      return state;
  



【问题讨论】:

您是否尝试过使用React.memo? 【参考方案1】:

您可以实施简单的验证来实现您想要的。 我会假设你的tableData 有一个id,然后你可以这样验证:

useEffect(() => 
    // don't fetch the same table
    if(tableData?.id !== params.someID 
      dispatch(fetchDetails(params.someID));
    
, [dispatch, params.someID, tableData?.id]);

更新 1

要将以前的表格数据与新提取的数据进行比较,您可以在操作创建器中执行此操作:

    loading 状态移动到 redux 存储区。 使用 getState 函数,这是 action creator 接收的第二个参数(假设您使用的是 redux-thunk) 在调度操作之前,将新的tableData 与存储中的数据(上一个tableData)进行比较。 检查并更新loading 状态。

export const fetchDetails = (data) => (dispatch, getState) => 
  axios
    .post(
      `$SomeURL/fetchAll`,
      data
    )
    .then((res) => 
      if (res.data.status) 
        const prevTableData = getState().tableData;
        
        // compare prev and new table data
        if(isDiff(prevTableData, res.data) 
            // Do something...
        
        dispatch(
          type: FETCH_DETAILS,
          payload: res.data,
        );
      
    )
    .catch((err) => console.log(err));
;

【讨论】:

问题不在于获取/调度操作。它关于在表格中显示加载图标,每次页面加载时,一旦从 API 获取响应,响应就可以显示在表格中。如果 redux 状态有一些旧数据,则显示旧数据而不是加载图标。我想根据 api 响应比较 prevprops 和 nextprops 来设置加载状态真/假。 你使用 redux-thunk 吗?【参考方案2】:

正确的做法是使用redux reselect包https://github.com/reduxjs/reselect。 Redux 团队推荐它。

reselect 是专门为这种场景设计的。它还在引擎盖下使用了记忆。您可以制作自定义选择器并在这样的场景中使用它们。

您可以像这样为所有重新选择功能制作一个单独的文件

//select file (eg: selectors.js)
import  createSelector  from "reselect";

const tableDetails = state => state.common.tableDetails;

function checkDataFunction(details)
    if (
      details.data &&
      details.status === true
    ) 
      return 
        loading: false,
        data: details.data
      
    
   
    else if (
      details.data &&
      details.status === false
      
    ) 
      return 
        loading: true,
        data: []
      
    

    return 
      loading: true,
      data: []
    ;


//redux selector
//enables you to make your own custom selector
export const checkData = createSelector(
  tableDetails,
  checkDataFunction
);

现在在您想要使用它的主组件文件中,只需导入它并将其用作任何其他选择器

//main component file (eg: App.js)
import checkData from './selectors';


export default function App()
  const tableData = useSelector(checkData);

  useEffect(() => 
    //continue with your logic here
  ,[tableData.loading])

  //...

【讨论】:

【参考方案3】:

尝试使用 redux 工具包中的 redux-reselect 包来为此类任务构建您自己的选择器。如果您想以 redux 方式管理状态,这是一个方便的工具。 另外我建议按照YouTube tutorial 来设置和使用 redux-reselect。

【讨论】:

【参考方案4】:

您如何将数据从存储区分派回该组件? 将其包装到 useEffect 调用中。

这是一个使用 useDispatch 挂钩的示例。

Need clarification for react + react-redux hooks + middleware thunk+ fetching API

【讨论】:

嘿,谢谢你的回答,我已经添加了 dispatch 和 action 以及 reducer 代码。你能看一下吗?

以上是关于如何在挂钩中比较来自 react redux 状态的值的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 thunk 在 react-redux 挂钩中进行异步调用?

如何使用 thunk 和 useDispatch(react-redux 挂钩)从操作中返回承诺?

如何在数组依赖中正确使用 useEffect 挂钩。我从 redux 商店传递了状态,但我的组件仍然无限渲染

如何防止在单击事件上触发两个相同的 useState 挂钩

React Redux Axios:POST 请求未收到来自 redux 状态的凭据

React-redux挂钩和功能组件逻辑