我最近使用钩子写了一个表组件,每次页面加载都有一个API调用到后端,所以同时有一个加载Spinner将被显示,直到有一个来自API的响应。我使用redux作为状态管理,所以当有来自API的响应时,就会发出一个动作并更新状态。这里的问题是,通常在类组件中我们可以使用
来比较prevProps和nextPropscomponentDidUpdate(prevProps){
if(this.props.someState !== prevProps.someState){
// do something
}
}
,但我不知道如何实现相同的使用钩子。我也提到了这个stackoverflow问题如何比较oldValues和newValues对React钩子使用效果?
但是这个解决方案似乎在我的情况下不起作用。我尝试创建自定义钩子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状态响应后将更新,新数据将显示在表中。
更新1:下面是分派、动作和减速器的代码
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;
}
}
您可以实现简单的验证来达到您想要的效果。我假设您的tableData
有一个id
,那么您可以这样验证:
useEffect(() => {
// don't fetch the same table
if(tableData?.id !== params.someID {
dispatch(fetchDetails(params.someID));
}
}, [dispatch, params.someID, tableData?.id]);
更新1
要比较以前的表数据和新的表数据,可以在action creator中这样做:
- 移动
loading
状态到redux store - 使用
getState
函数,这是动作创建者收到的第二个参数(假设您正在使用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));
};
正确的方法是使用reduxreselect包https://github.com/reduxjs/reselect。Redux团队推荐。
reelect是专门为这种情况设计的。它还在底层使用了记忆。你可以自定义选择器,并在这样的场景中使用它们。
你可以为所有的重选函数创建一个单独的文件,像这样
//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])
//...
}