Redux 存储在分派后不更新(异步数据加载)
Redux store not updating after dispatch (async data load)
我遇到了一个奇怪的问题。我调用 dispatch
方法从服务器加载一些数据。效果很好,我设法获取了数据并更新了 reducer 状态。但是 redux store 状态似乎没有更新。 useSelector
常量永远不会更新。任何想法?感谢您的帮助!
这是代码...
App.tsx
function App(): ReactElement {
//* Init *//
const alertPopupRef = useRef(null);
const [dataLoaded, setDataLoaded] = useState<boolean>(false);
const applicationPreferences = useSelector<RootState, ApplicationPreferences>(state => state.applicationPreferences);
const dispatch = useDispatch();
useEffect(() => {
// This code is only triggered once, when the component is mounted.
console.log(applicationPreferences);
}, [applicationPreferences]);
dispatch(loadApplicationPreferences(showError));
// Just for test.
setTimeout(() => {
// The 'ApplicationPreferences' variable is still empty.
console.log(store.getState());
}, 3000);
//* Component Functions *//
function showError(err: ApiBusinessLayerError): void {
alertPopupRef.current.show(AlertsTypes.Error, err.error, err.description, true);
console.log("showAlert is triggered.");
}
return (
dataLoaded ?
<AlertContext.Provider value={{ showError }}>
<Alert ref={alertPopupRef} />
<Router>
<Switch>
<Route exact path="/" component={public_welcome_page} />
<Route path="/signup" component={public_signup_page} />
</Switch>
</Router>
</AlertContext.Provider>
:
<div style={styles.loading_page}>
<Alert ref={alertPopupRef} />
<div style={styles.loading_image_container}>
<img src={loadingImage} alt="" />
</div>
<div>
<img src={logo} alt="" style={styles.logo} />
</div>
<div>
Language: {applicationPreferences.language}
</div>
</div>
);
}
application_preferences_reducer.ts
import produce from "immer";
import { ApiBusinessLayerError, ApiDataTypes, get } from "../api_business_layer";
import ApplicationPreferences from "../models/application_preferences";
import { RootState } from "../store";
//* ┌──────────────────┐
//* │ ETI DECLARATIONS │
//* └──────────────────┘
export interface ApplicationPreferencesState {
language: string;
server: string;
isInDevelopement: boolean;
}
export enum ApplicationPreferencesActionsTypes {
SET_PREFERENCES = "SET_PREFERENCES"
}
export type ApplicationPreferencesAction = {
type: ApplicationPreferencesActionsTypes,
payload: ApplicationPreferences[]
}
//* ┌─────────┐
//* │ ACTIONS │
//* └─────────┘
export function loadApplicationPreferences(errorCallback: (err: ApiBusinessLayerError) => void): Function {
return async (dispatch: (action: ApplicationPreferencesAction) => RootState, getState: () => RootState): Promise<void> => {
get<ApplicationPreferences>(ApiDataTypes.ApplicationPreferences, null, null)
.then(data => { dispatch({ type: ApplicationPreferencesActionsTypes.SET_PREFERENCES, payload: data }); })
.catch((err: ApiBusinessLayerError) => { errorCallback(err); });
}
}
//* ┌─────────┐
//* │ REDUCER │
//* └─────────┘
const initialState: ApplicationPreferencesState = {
language: "",
server: "",
isInDevelopement: false
}
const routes: { [actionType: string]: (state: ApplicationPreferencesState, action: ApplicationPreferencesAction) => ApplicationPreferencesState } = {
[ApplicationPreferencesActionsTypes.SET_PREFERENCES]: _setApplicationPreferences,
}
function applicationPreferencesReducer(state: ApplicationPreferencesState = initialState, action: ApplicationPreferencesAction): ApplicationPreferencesState {
if (!routes[action.type]) { return state; }
return routes[action.type](state, action);
}
function _setApplicationPreferences(state: ApplicationPreferencesState, action: ApplicationPreferencesAction): ApplicationPreferencesState {
return produce(state, state => {
// The state is correctly set here.
state = JSON.parse(JSON.stringify(action.payload[0]));
});
}
export default applicationPreferencesReducer;
store.ts
import { applyMiddleware, CombinedState, combineReducers, createStore } from "redux";
import thunk from "redux-thunk";
import applicationPreferencesReducer, { ApplicationPreferencesState } from "./reducers/application_preferences_reducer";
import languagesReducer, { LanguagesState } from "./reducers/languages_reducer";
import signInReducer, { SignInState } from "./reducers/signin_reducer";
import userPrivilegesReducer, { UserPrivilegesState } from "./reducers/user_privileges_reducer";
import userTypesReducer, { UserTypesState } from "./reducers/user_types_reducer";
export type RootState = CombinedState<{
applicationPreferences: ApplicationPreferencesState;
language: LanguagesState;
signIn: SignInState;
userTypes: UserTypesState;
userPrivileges: UserPrivilegesState;
}>
export const rootReducer = combineReducers({
applicationPreferences: applicationPreferencesReducer,
language: languagesReducer,
signIn: signInReducer,
userTypes: userTypesReducer,
userPrivileges: userPrivilegesReducer
});
const store = createStore(rootReducer, applyMiddleware(thunk));
export default store;
index.tsx
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import "bootstrap/dist/css/bootstrap.min.css";
import "bootstrap/dist/js/bootstrap.min.js";
import App from "./App";
import { Provider } from 'react-redux';
import store from "./data/store";
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>
, document.getElementById('root')
);
不确定为什么要在 reducer 中使用 produce,因为您实际上并没有更改状态的任何子集,而是 return 一个全新创建的状态。我认为您可以执行以下操作:
//don't you hate code that wanders off the right side of the screen so you can't see what's going on, when someone sets their editor/formatter to a line length over 80
function _setApplicationPreferences(state: ApplicationPreferencesState, action: ApplicationPreferencesAction): ApplicationPreferencesState {
return JSON.parse(JSON.stringify(action.payload[0]));
}
我遇到了一个奇怪的问题。我调用 dispatch
方法从服务器加载一些数据。效果很好,我设法获取了数据并更新了 reducer 状态。但是 redux store 状态似乎没有更新。 useSelector
常量永远不会更新。任何想法?感谢您的帮助!
这是代码...
App.tsx
function App(): ReactElement {
//* Init *//
const alertPopupRef = useRef(null);
const [dataLoaded, setDataLoaded] = useState<boolean>(false);
const applicationPreferences = useSelector<RootState, ApplicationPreferences>(state => state.applicationPreferences);
const dispatch = useDispatch();
useEffect(() => {
// This code is only triggered once, when the component is mounted.
console.log(applicationPreferences);
}, [applicationPreferences]);
dispatch(loadApplicationPreferences(showError));
// Just for test.
setTimeout(() => {
// The 'ApplicationPreferences' variable is still empty.
console.log(store.getState());
}, 3000);
//* Component Functions *//
function showError(err: ApiBusinessLayerError): void {
alertPopupRef.current.show(AlertsTypes.Error, err.error, err.description, true);
console.log("showAlert is triggered.");
}
return (
dataLoaded ?
<AlertContext.Provider value={{ showError }}>
<Alert ref={alertPopupRef} />
<Router>
<Switch>
<Route exact path="/" component={public_welcome_page} />
<Route path="/signup" component={public_signup_page} />
</Switch>
</Router>
</AlertContext.Provider>
:
<div style={styles.loading_page}>
<Alert ref={alertPopupRef} />
<div style={styles.loading_image_container}>
<img src={loadingImage} alt="" />
</div>
<div>
<img src={logo} alt="" style={styles.logo} />
</div>
<div>
Language: {applicationPreferences.language}
</div>
</div>
);
}
application_preferences_reducer.ts
import produce from "immer";
import { ApiBusinessLayerError, ApiDataTypes, get } from "../api_business_layer";
import ApplicationPreferences from "../models/application_preferences";
import { RootState } from "../store";
//* ┌──────────────────┐
//* │ ETI DECLARATIONS │
//* └──────────────────┘
export interface ApplicationPreferencesState {
language: string;
server: string;
isInDevelopement: boolean;
}
export enum ApplicationPreferencesActionsTypes {
SET_PREFERENCES = "SET_PREFERENCES"
}
export type ApplicationPreferencesAction = {
type: ApplicationPreferencesActionsTypes,
payload: ApplicationPreferences[]
}
//* ┌─────────┐
//* │ ACTIONS │
//* └─────────┘
export function loadApplicationPreferences(errorCallback: (err: ApiBusinessLayerError) => void): Function {
return async (dispatch: (action: ApplicationPreferencesAction) => RootState, getState: () => RootState): Promise<void> => {
get<ApplicationPreferences>(ApiDataTypes.ApplicationPreferences, null, null)
.then(data => { dispatch({ type: ApplicationPreferencesActionsTypes.SET_PREFERENCES, payload: data }); })
.catch((err: ApiBusinessLayerError) => { errorCallback(err); });
}
}
//* ┌─────────┐
//* │ REDUCER │
//* └─────────┘
const initialState: ApplicationPreferencesState = {
language: "",
server: "",
isInDevelopement: false
}
const routes: { [actionType: string]: (state: ApplicationPreferencesState, action: ApplicationPreferencesAction) => ApplicationPreferencesState } = {
[ApplicationPreferencesActionsTypes.SET_PREFERENCES]: _setApplicationPreferences,
}
function applicationPreferencesReducer(state: ApplicationPreferencesState = initialState, action: ApplicationPreferencesAction): ApplicationPreferencesState {
if (!routes[action.type]) { return state; }
return routes[action.type](state, action);
}
function _setApplicationPreferences(state: ApplicationPreferencesState, action: ApplicationPreferencesAction): ApplicationPreferencesState {
return produce(state, state => {
// The state is correctly set here.
state = JSON.parse(JSON.stringify(action.payload[0]));
});
}
export default applicationPreferencesReducer;
store.ts
import { applyMiddleware, CombinedState, combineReducers, createStore } from "redux";
import thunk from "redux-thunk";
import applicationPreferencesReducer, { ApplicationPreferencesState } from "./reducers/application_preferences_reducer";
import languagesReducer, { LanguagesState } from "./reducers/languages_reducer";
import signInReducer, { SignInState } from "./reducers/signin_reducer";
import userPrivilegesReducer, { UserPrivilegesState } from "./reducers/user_privileges_reducer";
import userTypesReducer, { UserTypesState } from "./reducers/user_types_reducer";
export type RootState = CombinedState<{
applicationPreferences: ApplicationPreferencesState;
language: LanguagesState;
signIn: SignInState;
userTypes: UserTypesState;
userPrivileges: UserPrivilegesState;
}>
export const rootReducer = combineReducers({
applicationPreferences: applicationPreferencesReducer,
language: languagesReducer,
signIn: signInReducer,
userTypes: userTypesReducer,
userPrivileges: userPrivilegesReducer
});
const store = createStore(rootReducer, applyMiddleware(thunk));
export default store;
index.tsx
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import "bootstrap/dist/css/bootstrap.min.css";
import "bootstrap/dist/js/bootstrap.min.js";
import App from "./App";
import { Provider } from 'react-redux';
import store from "./data/store";
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>
, document.getElementById('root')
);
不确定为什么要在 reducer 中使用 produce,因为您实际上并没有更改状态的任何子集,而是 return 一个全新创建的状态。我认为您可以执行以下操作:
//don't you hate code that wanders off the right side of the screen so you can't see what's going on, when someone sets their editor/formatter to a line length over 80
function _setApplicationPreferences(state: ApplicationPreferencesState, action: ApplicationPreferencesAction): ApplicationPreferencesState {
return JSON.parse(JSON.stringify(action.payload[0]));
}