我在尝试使用redux的新方法时遇到了一个令人沮丧的问题。我一直在遵循https://redux-toolkit.js.org/上的指南/文档/教程,但无论我尝试什么,我总是遇到同样的问题。
错误消息
Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'isBatchingLegacy')
at batchedUpdates$1 (react-dom.development.js:4441:25)
at Object.notify (Subscription.js:20:7)
at Object.notifyNestedSubs (Subscription.js:78:15)
at handleChangeWrapper (Subscription.js:82:20)
at dispatch (redux.js:276:7)
at eval (redux-toolkit.esm.js:547:22)
at eval (index.js:23:16)
at eval (redux-toolkit.esm.js:461:32)
at dispatch (redux.js:623:28)
at eval (redux-toolkit.esm.js:1488:21)
背景:
- 我没有使用CRA来生成我的项目,所以我怀疑问题可能是与webpack/typescript配置相关的
- 以上是因为我使用模块联合:https://github.com/module-federation/module-federation-examples
- 我在项目中使用typescript,这增加了实现redux的复杂性。
- 相关依赖性:
"@reduxjs/toolkit": "^1.9.0",
"axios": "^1.1.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-redux": "^8.0.5",
"react-router-dom": "^6.4.3"
我正试图把一个简单的API调用的结果放入我的redux存储。
src/组件/App/userSlice.ts
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import axios from 'axios';
import { User } from '@componentLib/path/to/my/types/User';
interface UserState {
status: string;
user: User | {};
}
const initialState: UserState = {
status: 'idle',
user: {},
};
// async thunk
export const getUser = createAsyncThunk<User, void>(
'user/getUser',
async () => {
console.log('fetching user...'); // this is never called
const { data: user } = await axios.get('/api/user');
console.log('user response', user); // this is never called
return user as User;
},
);
// core redux state slice for users
export const userSlice = createSlice({
name: 'user',
initialState,
reducers: {},
extraReducers: (builder) => {
builder
.addCase(getUser.pending, (state, action) => {
console.log('pending', action);
state.status = 'loading';
})
.addCase(getUser.fulfilled, (state, action) => {
console.log('fulfilled', action);
state.user = action.payload;
state.status = 'idle';
});
},
});
// basic selector to return user
export const selectUser = (state: any) => state.user.user;
export const selectStatus = (state: any) => state.user.status;
// export the reducer
export default userSlice.reducer;
src/组件/App/App.tsx
import { Suspense, lazy, useEffect, useState } from 'react';
import { useAppSelector, useAppDispatch } from '../../hooks';
import axios from 'axios';
import { MyApp } from '@componentLib/myApp';
import { getUser, selectStatus, selectUser } from './userSlice';
import ErrorBoundary from '../ErrorBoundary';
import Authenticating from './Authenticating';
const ExampleModule = lazy(() => import('exampleModule/ExampleModule'));
function App() {
const example = (
<ErrorBoundary>
<Suspense fallback={<>Loading...</>}>
<ExampleModule />
</Suspense>
</ErrorBoundary>
);
// const [user, setUser] = useState(); // old way that used to work prior to redux
const user = useAppSelector(selectUser);
const status = useAppSelector(selectStatus);
const dispatch = useAppDispatch();
useEffect(() => {
// old way that used to work prior to redux implementation
// a.k.a what I'm trying to replace so sub components have access to the user via redux
// rather than as a passed prop (like below)
const getUserOld = async () => {
const { data: user } = await axios.get('/api/user');
console.log('non-thunk load', user);
// if (user.sid) {
// console.log("user", user);
// // setUser(user);
// } else {
// // redirect them to authenticate via OIDC/SSO
// // window.location.replace('/api/auth');
// }
};
getUserOld();
console.log('dispatching get user');
dispatch(getUser());
}, []);
useEffect(() => {
// If the user is not defined and the loader is not fetching
console.log('status', status);
console.log('user', user);
}, [status, user]);
const modules = [
{
path: '/',
name: 'Example',
element: example,
},
];
return user.sid ? (
<MyApp modules={modules} user={user} />
) : (
<Authenticating />
);
}
export default App;
src/store.ts
import { configureStore } from '@reduxjs/toolkit';
import userReducer from './components/App/userSlice';
export const store = configureStore({
reducer: {
user: userReducer,
},
});
// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<typeof store.getState>;
// Inferred type: {users: UsersState} ect...
export type AppDispatch = typeof store.dispatch;
export default store;
src/hooks.ts
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import type { RootState, AppDispatch } from './store';
// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
/src/index.tsx
import { createRoot } from 'react-dom/client';
import { Provider } from 'react-redux';
import App from './components/App';
import { store } from './store';
const container = document.getElementById('app');
const root = createRoot(container);
root.render(
<Provider store={store}>
<App />
</Provider>,
);
这会产生以下控制台日志:
[HMR] Waiting for update signal from WDS...
App.tsx:94 dispatching get user
userSlice.ts:81 pending {type: 'user/getUser/pending', payload: undefined, meta: {…}}
App.tsx:99 status idle
App.tsx:100 user {}
App.tsx:99 status loading
App.tsx:100 user {}
react-dom.development.js:4441 Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'isBatchingLegacy')
non-thunk load {sid: 'A123456', firstName: 'CLEAN', lastName: 'SHOOTER', roles: Array(5)}
正如您所看到的,大部分设置都是逐字逐句地来自redux工具包网站。它似乎是失败的,在这一点上,坦克试图执行的承诺,但我不知道为什么。我也不是一个打字专家,所以看到一个变量,我不与名为"isBatchingLegacy"正在寻找是令人困惑的。这让我相信我没有正确地键入createAsyncThunk
,但我试图按照redux网站上的文档尽我所能。如果你需要更多的信息或额外的背景,请告诉我。
您的应用程序代码看起来很好。根据实际的错误消息堆栈跟踪,它来自react-dom
内部,实际上不是Redux Toolkit的问题。
也就是说,我不知道为什么在React中会发生错误。我认为你怀疑Module Federation以某种方式参与了是对的,但我不知道这里的问题究竟是什么。