alioth/before/cha/12=react-template/test/redux.md
2025-05-30 09:18:01 +08:00

315 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

如果你需要创建一个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 类型pendingfulfilledrejected你可以在 `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。