如何比较钩子中反应 redux 状态的值

How to compare values from react redux state in hooks

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

比较 prevProps 和 nextProps
componentDidUpdate(prevProps){
  if(this.props.someState !== prevProps.someState){
      // do something
    }
}

但我不确定如何使用 Hooks 实现同样的效果。我还提到了这个 Whosebug 问题 如何比较 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 状态中存在任何数据,则会显示该数据,并且不会对 prevProps 和 nextProps 进行比较,因为加载微调器不会显示,并且在新的响应之后调用 api 状态将被更新,新数据将显示在 Table.

更新 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;
  }
}


您如何将数据从商店发送回此组件? 将其包装到 useEffect 调用中。

这是一个使用 useDispatch 钩子的例子。

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

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

更新 1

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

  1. loading 状态移动到 redux 存储。
  2. 使用 getState 函数,它是动作创建者接收到的第二个参数(假设您使用的是 redux-thunk)
  3. 在调度操作之前,将新的 tableData 与存储中的数据(上一个 tableData)进行比较。
  4. 检查并更新 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));
};

正确的方法是使用redux reselect package 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])

  //...
}

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