You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
315 lines
10 KiB
315 lines
10 KiB
8 months ago
|
如果你需要创建一个Redux应用,但不想使用react-redux和`createStore`(这通常是Redux的标准API用于创建store的方法),你可以使用Redux Toolkit中的`configureStore`作为`createStore`的替代方案来创建store。
|
||
|
|
||
|
Redux Toolkit 是官方推荐的方式来设置store,它提供了更简化的API和一些有用的工具,比如默认集成了redux-thunk中间件以及Redux DevTools扩展支持。下面是一个不使用react-redux的示例,但是如果你想完全不使用任何辅助工具或库,仅使用最基本的Redux API(这是不推荐的,因为你会失去许多便利和性能优化),那你将需要自己管理store的状态订阅和更新组件的渲染。
|
||
|
|
||
|
下面是一个不使用`createStore`和`react-redux`的Redux应用例子:
|
||
|
|
||
|
目录结构:
|
||
|
```
|
||
|
my-app/
|
||
|
src/
|
||
|
store/
|
||
|
index.js // 创建和配置应用程序的Redux store
|
||
|
counterReducer.js // 管理计数状态的reducer
|
||
|
components/
|
||
|
Counter.js // 一个使用Redux store的React组件
|
||
|
App.js // 应用程序的根组件
|
||
|
index.js // 应用程序的主入口文件
|
||
|
```
|
||
|
|
||
|
`store/index.js`:
|
||
|
```javascript
|
||
|
import { configureStore } from '@reduxjs/toolkit';
|
||
|
import counterReducer from './counterReducer';
|
||
|
|
||
|
const store = configureStore({
|
||
|
reducer: {
|
||
|
counter: counterReducer,
|
||
|
},
|
||
|
});
|
||
|
|
||
|
export default store;
|
||
|
```
|
||
|
|
||
|
`store/counterReducer.js`:
|
||
|
```javascript
|
||
|
const INCREMENT = 'INCREMENT';
|
||
|
const DECREMENT = 'DECREMENT';
|
||
|
|
||
|
const initialState = {
|
||
|
value: 0,
|
||
|
};
|
||
|
|
||
|
function counterReducer(state = initialState, action) {
|
||
|
switch (action.type) {
|
||
|
case INCREMENT:
|
||
|
return { ...state, value: state.value + 1 };
|
||
|
case DECREMENT:
|
||
|
return { ...state, value: state.value - 1 };
|
||
|
default:
|
||
|
return state;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
export const increment = () => ({ type: INCREMENT });
|
||
|
export const decrement = () => ({ type: DECREMENT });
|
||
|
|
||
|
export default counterReducer;
|
||
|
```
|
||
|
|
||
|
`components/Counter.js`:
|
||
|
```jsx
|
||
|
import React, { useState, useEffect } from 'react';
|
||
|
import store from '../store';
|
||
|
import { increment, decrement } from '../store/counterReducer';
|
||
|
|
||
|
export const Counter = () => {
|
||
|
const [count, setCount] = useState(store.getState().counter.value);
|
||
|
|
||
|
useEffect(() => {
|
||
|
const unsubscribe = store.subscribe(() => {
|
||
|
setCount(store.getState().counter.value);
|
||
|
});
|
||
|
return unsubscribe; // 在组件卸载时取消订阅
|
||
|
}, []);
|
||
|
|
||
|
return (
|
||
|
<div>
|
||
|
<h1>{count}</h1>
|
||
|
<button onClick={() => store.dispatch(increment())}>Increment</button>
|
||
|
<button onClick={() => store.dispatch(decrement())}>Decrement</button>
|
||
|
</div>
|
||
|
);
|
||
|
};
|
||
|
```
|
||
|
|
||
|
`App.js`:
|
||
|
```jsx
|
||
|
import React from 'react';
|
||
|
import { Counter } from './components/Counter';
|
||
|
|
||
|
const App = () => {
|
||
|
return (
|
||
|
<div>
|
||
|
<Counter />
|
||
|
</div>
|
||
|
);
|
||
|
}
|
||
|
|
||
|
export default App;
|
||
|
```
|
||
|
|
||
|
`index.js`:
|
||
|
```jsx
|
||
|
import React from 'react';
|
||
|
import ReactDOM from 'react-dom';
|
||
|
import App from './App';
|
||
|
import store from './store';
|
||
|
import { increment } from './store/counterReducer';
|
||
|
|
||
|
// 可以在这里测试我们的Redux store是否正常工作
|
||
|
console.log(store.getState()); // 应该看到初始状态
|
||
|
store.dispatch(increment());
|
||
|
console.log(store.getState()); // 状态应该更新,并增加了1
|
||
|
|
||
|
ReactDOM.render(
|
||
|
<React.StrictMode>
|
||
|
<App />
|
||
|
</React.StrictMode>,
|
||
|
document.getElementById('root')
|
||
|
);
|
||
|
```
|
||
|
|
||
|
在上述的示例中,我们使用`useEffect`来订阅Redux store和清理订阅,通过store的`getState`方法获取当前的状态,并通过`dispatch`方法来派发actions。这种方式下,我们没有使用react-redux,而是手动连接到Redux store并监听状态变化。
|
||
|
|
||
|
请注意,这样手动处理Redux可能会使你的应用程序难以管理和扩展,特别是在大型应用程序中,react-redux提供的`Provider`和`connect`方法或者hooks`useSelector`和`useDispatch`可以更好地帮助你管理连接逻辑和组件更新,提高应用的性能。
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
在 Redux Toolkit 中,`createSlice` 通常用于同步更新 state ,但当它与 `createAsyncThunk` 一起使用时,可以非常容易地用于异步更新。`createAsyncThunk` 会生成三种 action 类型(pending,fulfilled,rejected),你可以在 `createSlice` 的 `extraReducers` 属性中监听这些 action:
|
||
|
|
||
|
```javascript
|
||
|
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
|
||
|
import { userAPI } from './userAPI';
|
||
|
|
||
|
// 首先创建一个异步 thunk action
|
||
|
export const fetchUserData = createAsyncThunk(
|
||
|
'user/fetchData',
|
||
|
async (userId, thunkAPI) => {
|
||
|
// 调用API
|
||
|
const response = await userAPI.fetchById(userId);
|
||
|
return response.data; // 或根据你的API返回结构进行调整
|
||
|
}
|
||
|
);
|
||
|
|
||
|
// 然后创建 slice,这里我们可以处理我们上面定义的三种状态的actions
|
||
|
const userSlice = createSlice({
|
||
|
name: 'user',
|
||
|
initialState: {
|
||
|
data: null,
|
||
|
isLoading: false,
|
||
|
error: null,
|
||
|
},
|
||
|
reducers: {
|
||
|
// 你的其他同步 reducers 可以在这里定义
|
||
|
},
|
||
|
extraReducers: {
|
||
|
// 当 fetchUserData action 被派发时,根据 action 的不同状态对 state 进行更新
|
||
|
[fetchUserData.pending]: (state, action) => {
|
||
|
state.isLoading = true;
|
||
|
state.error = null;
|
||
|
},
|
||
|
[fetchUserData.fulfilled]: (state, action) => {
|
||
|
state.isLoading = false;
|
||
|
state.data = action.payload; // 假设我们的API返回了用户的数据结构
|
||
|
},
|
||
|
[fetchUserData.rejected]: (state, action) => {
|
||
|
state.isLoading = false;
|
||
|
state.error = action.error.message;
|
||
|
},
|
||
|
},
|
||
|
});
|
||
|
|
||
|
export const { actions, reducer } = userSlice;
|
||
|
```
|
||
|
|
||
|
在这个例子中:
|
||
|
|
||
|
- `fetchUserData.pending` 相关的 reducer 将在请求开始时将 `isLoading` 状态设置为 true。
|
||
|
- `fetchUserData.fulfilled` 相关的 reducer 将在请求成功并收到数据时更新 `data` ,并将 `isLoading` 设置为 false。
|
||
|
- `fetchUserData.rejected` 相关的 reducer 将在请求失败时设置错误信息,并将 `isLoading` 设置为 false。
|
||
|
|
||
|
通过使用 `createAsyncThunk` 和 `createSlice` 的 `extraReducers` ,我们也很方便的可以将异步行为整合到我们的 Redux state 管理中。
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
在视图(常常指的是 React 组件)中使用 `@reduxjs/toolkit` 中 `createAsyncThunk` 创建的异步 action 通常涉及以下几个步骤:
|
||
|
|
||
|
1. 将 Redux store 和 React 组件连接起来,可以使用 `react-redux` 提供的 `useSelector` 来选择 state,以及 `useDispatch` 来派发 actions。
|
||
|
|
||
|
2. 创建视图(UI组件),并在适当的时候(如组件挂载或者用户互动事件)派发异步 action。
|
||
|
|
||
|
3. 显示加载状态、错误信息或者成功得到的数据。
|
||
|
|
||
|
下面是一个例子,展示了如何在一个 React 组件中使用异步 action:
|
||
|
|
||
|
```jsx
|
||
|
import React, { useEffect } from 'react';
|
||
|
import { useSelector, useDispatch } from 'react-redux';
|
||
|
import { fetchUserData } from './userSlice';
|
||
|
// 以上的 userSlice 是我们之前定义的包含 fetchUserData 异步 action 的文件
|
||
|
|
||
|
function UserComponent({ userId }) {
|
||
|
const dispatch = useDispatch();
|
||
|
|
||
|
// 从 Redux state 中选择数据
|
||
|
const user = useSelector(state => state.user.data);
|
||
|
const isLoading = useSelector(state => state.user.isLoading);
|
||
|
const error = useSelector(state => state.user.error);
|
||
|
|
||
|
// 当组件挂载时,派发 fetchUserData action
|
||
|
useEffect(() => {
|
||
|
dispatch(fetchUserData(userId));
|
||
|
}, [userId, dispatch]);
|
||
|
|
||
|
// 根据 state 渲染 UI
|
||
|
if (isLoading) {
|
||
|
return <div>Loading...</div>;
|
||
|
}
|
||
|
|
||
|
if (error) {
|
||
|
return <div>Error: {error}</div>;
|
||
|
}
|
||
|
|
||
|
return (
|
||
|
<div>
|
||
|
<h1>User Data</h1>
|
||
|
{/* 假设 user 对象有 name 属性 */}
|
||
|
{user ? <p>Name: {user.name}</p> : <p>No user details</p>}
|
||
|
</div>
|
||
|
);
|
||
|
}
|
||
|
|
||
|
export default UserComponent;
|
||
|
```
|
||
|
|
||
|
在这个 React 组件中:
|
||
|
|
||
|
- 使用 `useDispatch` hook 获取 `dispatch` 方法,以便于我们可以派发 actions。
|
||
|
- 使用 `useSelector` hook 从 Redux store 中选择 `user.state` 中的各部分数据。
|
||
|
- 使用 `useEffect` React hook 来触发 `fetchUserData` 异步 action。当 `userId` 改变或者组件首次渲染时会重新触发。
|
||
|
- 在 UI 中根据 `isLoading`, `error`, 和 `user` 状态显示不同的内容。如果 `isLoading` 为 true,显示加载状态;如果 `error` 存在,则显示错误;否则显示用户信息。
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
在 `createAsyncThunk` 中使用的 `thunkAPI` 对象是由 Redux Thunk 提供的一个参数,包含了几个有用的属性和方法,使得在 thunk 中可以执行更为复杂的逻辑。`thunkAPI` 在每次调用异步 thunk 函数时都会被提供,并且具有以下字段和方法:
|
||
|
|
||
|
1. **dispatch**: 允许你在 thunk 中派发 action。
|
||
|
2. **getState**: 允许你访问当前的 Redux store state。
|
||
|
3. **extra**: 如果在创建 Redux store 的 `configureStore` 方法中定义了 `extraArgument`,则这里可以取得。
|
||
|
4. **requestId**: 是对每次异步 thunk action 调用的唯一标识。
|
||
|
5. **signal**: 是一个 `AbortSignal` 对象,与本次异步操作相关联,可以用来响应取消操作。
|
||
|
6. **rejectWithValue**: 一个函数,允许你在发生错误时手动地派发一个拒绝 (rejected) action,并携带自定义的 payload 值。
|
||
|
7. **fulfillWithValue**: 当你需要在 resolve (解决) action 中提供一个不同于异步操作返回结果的 payload 时,可以用这个函数。
|
||
|
8. **rejectWithReason**: 类似 `rejectWithValue`,允许在 rejected action 中提供自定义拒绝原因,更明确地说明拒绝的原因。
|
||
|
|
||
|
这些功能给予开发者很大的灵活性去处理异步逻辑和实现复杂的异步操作流程。例如,使用 `dispatch` 来派发其他 actions,利用 `getState` 获取最新的 state 来指导后续逻辑,或使用 `rejectWithValue` 在出错时捕捉错误并优化错误处理。
|
||
|
|
||
|
以下是一个使用 `thunkAPI` 的例子:
|
||
|
|
||
|
```javascript
|
||
|
export const fetchUserData = createAsyncThunk(
|
||
|
'user/fetchData',
|
||
|
async (userId, thunkAPI) => {
|
||
|
try {
|
||
|
const response = await userAPI.fetchById(userId);
|
||
|
return response.data;
|
||
|
} catch (error) {
|
||
|
// 如果 API 抛出一个错误,我们可以选择发送一个拒绝action,并附带一个自定义的payload
|
||
|
return thunkAPI.rejectWithValue({errorMessage: 'Cannot load user data'});
|
||
|
}
|
||
|
}
|
||
|
);
|
||
|
```
|
||
|
|
||
|
在这个例子中,如果 `userAPI.fetchById` 方法抛出一个错误,`thunkAPI.rejectWithValue` 方法则被用来派发一个拒绝的 action,并附上一个含错误信息的 payload。这使得 reducer 可以捕捉这个拒绝的 action,并根据附带的 payload 更新 state。
|