当我使用 useSelector 时,变量总是保持其初始状态。我感觉它存储在某个平行星系中并且从未更新过。但是当我用 const store = useStore(); 检索值时store.getState()... 它给出了正确的值(但缺少订阅)。当我在 redux devtools 中检查商店时,我可以看到所有值都正确记录在商店中。只是不使用 useSelector 从商店中检索值。
我想要实现的是为用户配置文件提供一些缓存,即不在同一页面上多次获取 /api/profile/25 。我不想将其视为“缓存”并发出多个请求,只是记住请求被缓存并且很便宜,而是将其视为从商店获取配置文件并记住在需要时获取配置文件,我意味着一些懒惰的更新。
实现应该看起来像一个钩子,即
// use pattern
const client = useProfile(userId);
// I can also put console.log here to see if the component is getting updated
let outputProfileName;
if( client.state==='pending' ) {
outputProfileName = 'loading...';
} else if( client.state==='succeeded' ) {
outputProfileName = <span>{client.data.name}</span>
} // ... etc
所以我把我的代码放在 use-profile.js 中,在 profile-slice.js 中有 redux-toolkit 切片
profile-slice.js
import {
createSlice,
//createAsyncThunk,
} from '@reduxjs/toolkit';
const entityInitialValue = {
data: undefined,
state: 'idle',
error: null
};
export const slice = createSlice({
name: 'profile',
initialState: {entities:{}},
reducers: {
updateData: (state,action) => {
// we received data, update the data and the status to 'succeeded'
state.entities[action.payload.id] = {
...entityInitialValue,
//...state.entities[action.payload.id],
data: action.payload.data,
state: 'succeeded',
error: null
};
return; // I tried the other approach - return {...state,entities:{...state.entities,[action.payload.id]:{...}}} - both are updating the store, didn't notice any difference
},
dispatchPendStart: (state,action) => {
// no data - indicates we started fetching
state.entities[action.payload.id] = {
...entityInitialValue,
//...state.entities[action.payload.id],
data: null,
state: 'pending',
error: null
};
return; // I tried the other approach - return {...state,entities:{...state.entities,[action.payload.id]:{...}}} - both are updating the store, didn't notice any difference
},
dispatchError: (state,action) => {
state.entities[action.payload.id] = {
//...entityInitialValue,
...state.entities[action.payload.id],
data: null,
state: 'failed',
error: action.payload.error
};
return; // I tried the other approach - return {...state,entities:{...state.entities,[action.payload.id]:{...}}} - both are updating the store, didn't notice any difference
},
},
extraReducers: {
}
});
export const {updateData,dispatchPendStart,dispatchError} = slice.actions;
// export const selectProfile... not used
export default slice.reducer;
使用-profile.js
import React, { useState, useEffect } from 'react';
import { useDispatch, useSelector, useStore } from 'react-redux';
import {
updateData as actionUpdateData,
dispatchPendStart as actionDispatchPendStart,
dispatchError as actionDispatchError,
} from './profile-slice';
//import api...
function useProfile(userId) {
const dispatch = useDispatch();
const actionFunction = async () => {
const response = await client.get(`... api endpoint`);
return response;
};
const store = useStore();
// versionControl is a dummy variable added for testing to make sure the component is updated;
// it is updated: I tried adding console.log to my component function (where I have const client = useProfile(clientId)...)
const [versionControl,setVersionControl] = useState(0);
const updateVersion = () => setVersionControl(versionControl+1);
// TODO: useSelector not working
const updateData = newVal => { dispatch(actionUpdateData({id:userId,data:newVal})); updateVersion(); };
const dispatchPendStart = newVal => { dispatch(actionDispatchPendStart({id:userId})); updateVersion(); };
const dispatchError = newVal => { dispatch(actionDispatchError({id:userId,error:newVal})); updateVersion(); };
const [
getDataFromStoreGetter,
getLoadingStateFromStoreGetter,
getLoadingErrorFromStoreGetter,
] = [
() => (store.getState().profile.entities[userId]||{}).data,
() => (store.getState().profile.entities[userId]||{}).state,
() => (store.getState().profile.entities[userId]||{}).error,
];
const [
dataFromUseSelector,
loadingStateFromUseSelector,
loadingErrorFromUseSelector,
] = [
useSelector( state => !!state.profile.entities[userId] ? state.profile.entities[userId].data : undefined ),
useSelector( state => !!state.profile.entities[userId] ? state.profile.entities[userId].loadingState : 'idle' ),
useSelector( state => !!state.profile.entities[userId] ? state.profile.entities[userId].loadingError : undefined ),
];
useEffect( async () => {
if( !(['pending','succeeded','failed'].includes(getLoadingStateFromStoreGetter())) ) {
// if(requestOverflowCounter>100) { // TODO: protect against infinite loop of calls
dispatchPendStart();
try {
const result = await actionFunction();
updateData(result);
} catch(e) {
dispatchError(e);
throw e;
}
}
})
return {
versionControl, // "versionControl" is an approach to force component to update;
// it is updating, I added console.log to the component function and it runs, but the values
// from useSelector are the same all the time, never updated; the problem is somewhere else; useSelector is just not working
// get data() { return getDataFromStoreGetter(); }, // TODO: useSelector not working; but I need subscribtions
// get loadingState() { return getLoadingStateFromStoreGetter(); },
// get loadingError() { return getLoadingErrorFromStoreGetter(); },
data: dataFromUseSelector,
loadingState: loadingStateFromUseSelector,
loadingError: loadingErrorFromUseSelector,
};
}
export default useProfile;
store.js
import { configureStore,combineReducers } from '@reduxjs/toolkit';
import profileReducer from '../features/profile/profile-slice';
// import other reducers
export default configureStore({
reducer: {
profile: profileReducer,
// ... other reducers
},
});
component.js - 实际上看上面的使用模式,除了贴出来的几行之外没有什么有趣的。
所以
当我导出加载状态时(我的意思是 use-profile.js 中的最后一行;我可以取消最后三行并取消对其他三行的注释)。因此,如果我使用 getLoadingStateFromStoreGetter(通过 store.getState() 检索的值...),那么一些配置文件名称会显示已提取的名称,而另一些则持有“正在加载...”并永远卡住。这说得通。从 redux store 中检索到正确的数据,我们没有订阅。
当我导出用 useSelector 创建的另一个版本时,我总是得到它的初始状态。我从未收到任何用户名或表示“正在加载”的值。
我在 StackOverflow 上阅读了很多答案。一些常见的错误包括:
有人说您的组件没有更新。事实并非如此,我测试了它,将 console.log 放在代码中并添加 versionControl 变量(参见代码)以确保它更新。
有些答案是说您没有正确使用 reducer 更新存储,并且它仍然持有相同的对象。事实并非如此,我尝试了两种方法,返回一个新的对象 {...state,entities:{...state.entities...etc...}} 并改变现有的代理对象 - 两种方式我的reducers 应该提供一个新对象,redux 应该通知更改。
有时会创建多个商店实例并且事情会变得一团糟。绝对不是这样,我只有一次调用 configureStore() 和一个组件。
此外,我在我的代码中没有看到违反钩子规则的情况。我在 useSelector fn 中有一个 if 语句,但是 useSelector 钩子本身是无条件调用的。
我不知道还有什么其他原因导致 useSelect 无法正常工作。谁能帮我理解一下?
答案 0 :(得分:1)
操作,像往常一样,非常简单的错字是原因。花了这么多小时。非常抱歉那些花时间试图查看此内容的人,并感谢您的时间。
useSelector( state => !!state.profile.entities[userId] ? state.profile.entities[userId].loadingState : 'idle' )
应该不是.loadingState,而是.state。就是这样。