Redux 中的动作创建与应用
1. 动作调度基础
在 Redux 中调度动作,只需将
exampleUse
文件导入主应用文件,应用启动时它就会运行。以下是
src/index.js
需要做的小修改:
import React from 'react';
import { render } from 'react-dom';
import { App } from './containers/App';
import { Home, SinglePost, Login, NotFound, Profile } from './containers';
import { Router, Route } from './components/router';
import { history } from './history';
import { firebase } from './backend';
import configureStore from './store/configureStore';
import initialReduxState from './constants/initialState';
import './store/exampleUse';
//...
当以开发模式(使用
npm run dev
)加载应用时,Redux 开发工具图标会启用。应用运行时,导入的文件会多次调用存储调度器,将动作发送到存储。不过目前还没有为动作设置处理(通过 reducers),也未与 React 连接,所以不会有有意义的变化。但打开开发者工具查看动作历史记录,会看到每个加载动作都被调度并记录。
2. 异步动作与中间件
目前在 Redux 中只能调度同步动作,但实际应用中很多情况需要基于异步动作进行更改,如网络请求、读取浏览器本地存储值等。Redux 本身不支持异步动作,因为它期望动作只是普通对象。不过可以通过集成
redux-thunk
库来启用异步动作。
redux-thunk
是一个 Redux 中间件库,它就像 Redux 的一种“过路”或传递机制。Redux 中间件是“在调度动作和该动作到达 reducer 之间的第三方扩展点”,意味着在动作被 reducer 处理之前,有机会对其进行操作。
以下是将
redux-thunk
中间件集成到应用的代码:
import thunk from 'redux-thunk';
import { createStore, compose, applyMiddleware } from 'redux';
import rootReducer from '../reducers/root';
let store;
export default (initialState) => {
if (store) {
return store;
}
const createdStore = createStore(rootReducer, initialState, compose(
applyMiddleware(
thunk,
),
window.devToolsExtension()
)
);
store = createdStore;
return store;
};
安装
redux-thunk
后就可以创建异步动作创建器。虽然在进行网络请求等异步操作,但创建的动作本身不是异步任务。
redux-thunk
让 Redux 存储在接收到 Promise 时进行评估,通过 Promise 的执行过程来调度动作。动作仍然是同步的,但 Redux 现在知道在将 Promise 传入调度函数时等待其解析。
3. 创建动作创建器
3.1 错误动作
首先创建一个错误动作,用于在出现问题时向用户显示错误信息:
import * as types from '../constants/types';
export function createError(error, info) {
return {
type: types.app.ERROR,
error,
info
};
}
3.2 评论动作
接下来创建与评论相关的动作,包括显示、切换、更新可用评论和创建新评论:
import * as types from '../constants/types';
import * as API from '../shared/http';
import { createError } from './error';
export function showComments(postId) {
return {
type: types.comments.SHOW,
postId
};
}
export function toggleComments(postId) {
return {
type: types.comments.TOGGLE,
postId
};
}
export function updateAvailableComments(comments) {
return {
type: types.comments.GET,
comments
};
}
export function createComment(payload) {
return dispatch => {
return API.createComment(payload)
.then(res => res.json())
.then(comment => {
dispatch({
type: types.comments.CREATE,
comment
});
})
.catch(err => dispatch(createError(err)));
};
}
export function getCommentsForPost(postId) {
return dispatch => {
return API.fetchCommentsForPost(postId)
.then(res => res.json())
.then(comments => dispatch(updateAvailableComments(comments)))
.catch(err => dispatch(createError(err)));
};
}
3.3 文章动作
文章动作与评论动作类似,还包括点赞、取消点赞、创建文章、获取文章列表和加载单篇文章等功能:
import parseLinkHeader from 'parse-link-header';
import * as types from '../constants/types';
import * as API from '../shared/http';
import { createError } from './error';
import { getCommentsForPost } from './comments';
export function updateAvailablePosts(posts) {
return {
type: types.posts.GET,
posts
};
}
export function updatePaginationLinks(links) {
return {
type: types.posts.UPDATE_LINKS,
links
};
}
export function like(postId) {
return (dispatch, getState) => {
const { user } = getState();
return API.likePost(postId, user.id)
.then(res => res.json())
.then(post => {
dispatch({
type: types.posts.LIKE,
post
});
})
.catch(err => dispatch(createError(err)));
};
}
export function unlike(postId) {
return (dispatch, getState) => {
const { user } = getState();
return API.unlikePost(postId, user.id)
.then(res => res.json())
.then(post => {
dispatch({
type: types.posts.UNLIKE,
post
});
})
.catch(err => dispatch(createError(err)));
};
}
export function createNewPost(post) {
return (dispatch, getState) => {
const { user } = getState();
post.userId = user.id;
return API.createPost(post)
.then(res => res.json())
.then(newPost => {
dispatch({
type: types.posts.CREATE,
post: newPost
});
})
.catch(err => dispatch(createError(err)));
};
}
export function getPostsForPage(page = 'first') {
return (dispatch, getState) => {
const { pagination } = getState();
const endpoint = pagination[page];
return API.fetchPosts(endpoint)
.then(res => {
const links = parseLinkHeader(res.headers.get('Link'));
return res.json().then(posts => {
dispatch(updatePaginationLinks(links));
dispatch(updateAvailablePosts(posts));
});
})
.catch(err => dispatch(createError(err)));
};
}
export function loadPost(postId) {
return dispatch => {
return API.fetchPost(postId)
.then(res => res.json())
.then(post => {
dispatch(updateAvailablePosts([post]));
dispatch(getCommentsForPost(postId));
})
.catch(err => dispatch(createError(err)));
};
}
4. 是否使用 Redux
完成上述动作创建器后,已经创建了创建文章和评论的初始功能,但还缺少用户认证部分。在决定是否将某些状态或功能放入 Redux 存储时,可以考虑以下两个问题:
-
是否有很多其他部分需要了解该状态或功能
:如果应用的其他部分需要知道某个状态,那么该状态可能应该放入 Redux 存储。例如,如果应用需要控制下拉菜单的打开或关闭并对其做出响应,这些状态变化应该通过存储处理;如果不需要,将状态保留在组件本地即可。
-
该状态在 Redux 中是否会更简单或更好表达
:如果只是为了使用 Redux 而将组件的状态和动作转换到 Redux 中,可能会引入额外的复杂性。但如果状态复杂,Redux 能使其更易于处理,那么可以将其包含在存储中。
考虑到用户认证逻辑,应用的其他部分需要知道用户信息,并且将其集中在存储中可以避免在不同页面重复逻辑,所以目前将用户和认证逻辑集成到 Redux 是有意义的。
以下是创建与用户相关的动作的代码:
import * as types from '../constants/types';
import { history } from '../history';
import { createError } from './error';
import { loading, loaded } from './loading';
import { getFirebaseUser, loginWithGithub, logUserOut, getFirebaseToken } from '../backend/auth';
export function loginSuccess(user, token) {
return {
type: types.auth.LOGIN_SUCCESS,
user,
token
};
}
export function logoutSuccess() {
return {
type: types.auth.LOGOUT_SUCCESS
};
}
export function logout() {
return dispatch => {
return logUserOut()
.then(() => {
history.push('/login');
dispatch(logoutSuccess());
window.Raven.setUserContext();
})
.catch(err => dispatch(createError(err)));
};
}
export function login() {
return dispatch => {
return loginWithGithub().then(async () => {
try {
dispatch(loading());
const user = await getFirebaseUser();
const token = await getFirebaseToken();
const res = await API.loadUser(user.uid);
if (res.status === 404) {
const userPayload = {
name: user.displayName,
profilePicture: user.photoURL,
id: user.uid
};
const newUser = await API.createUser(userPayload).then(res => res.json());
dispatch(loginSuccess(newUser, token));
dispatch(loaded());
history.push('/');
return newUser;
}
const existingUser = await res.json();
dispatch(loginSuccess(existingUser, token));
dispatch(loaded());
history.push('/');
return existingUser;
} catch (err) {
createError(err);
}
});
};
}
通过以上步骤,我们可以在 Redux 中创建和调度各种同步和异步动作,同时合理地决定哪些状态和功能应该放入 Redux 存储,从而构建一个健壮的应用架构。
Redux 中的动作创建与应用(续)
5. 动作调度流程总结
为了更清晰地理解 Redux 中动作的调度流程,我们可以通过以下 mermaid 流程图来展示:
graph LR
classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px;
A([事件触发]):::startend --> B(动作创建器):::process
B --> C(动作):::process
C --> D(调度器):::process
D --> E{是否为异步动作}:::decision
E -->|是| F(中间件处理):::process
E -->|否| G(直接传递):::process
F --> G
G --> H(存储):::process
H --> I(Reducer):::process
I --> J(新状态):::process
J --> K(视图更新):::process
从流程图中可以看出,事件触发动作创建器生成动作,动作通过调度器传递。如果是异步动作,会先经过中间件处理,如
redux-thunk
中间件会处理 Promise 并在合适的时机调度动作。最终动作到达存储,由 Reducer 处理生成新状态,进而更新视图。
6. 不同类型动作的使用场景分析
| 动作类型 | 使用场景 | 示例代码 |
|---|---|---|
| 同步动作 | 简单的状态更新,如显示或隐藏组件、切换开关等 |
export function showComments(postId) {
return {
type: types.comments.SHOW,
postId
};
}
| 异步动作 | 涉及网络请求、文件读取等异步操作的场景,如获取文章列表、创建新文章等 |
export function getPostsForPage(page = 'first') {
return (dispatch, getState) => {
const { pagination } = getState();
const endpoint = pagination[page];
return API.fetchPosts(endpoint)
.then(res => {
const links = parseLinkHeader(res.headers.get('Link'));
return res.json().then(posts => {
dispatch(updatePaginationLinks(links));
dispatch(updateAvailablePosts(posts));
});
})
.catch(err => dispatch(createError(err)));
};
}
| 错误处理动作 | 处理应用中出现的错误,如网络请求失败、数据解析错误等 |
export function createError(error, info) {
return {
type: types.app.ERROR,
error,
info
};
}
7. 中间件的作用及原理深入理解
中间件在 Redux 中起着至关重要的作用。以
redux-thunk
为例,它允许我们在动作创建器中返回一个函数而不是普通对象,从而实现异步操作。其工作原理如下:
1.
拦截动作调度
:当动作被调度时,
redux-thunk
中间件会拦截该动作。
2.
判断动作类型
:如果动作是一个函数,中间件会执行该函数,并将
dispatch
和
getState
方法作为参数传递给它。
3.
处理异步操作
:在函数内部,我们可以进行异步操作,如网络请求。当异步操作完成后,通过
dispatch
方法调度其他动作。
以下是一个简单的示例,展示
redux-thunk
如何处理异步动作:
import thunk from 'redux-thunk';
import { createStore, applyMiddleware } from 'redux';
// 模拟 reducer
const reducer = (state = {}, action) => {
switch (action.type) {
case 'FETCH_DATA_SUCCESS':
return { ...state, data: action.payload };
default:
return state;
}
};
// 创建存储并应用中间件
const store = createStore(reducer, applyMiddleware(thunk));
// 异步动作创建器
const fetchData = () => {
return dispatch => {
// 模拟异步请求
setTimeout(() => {
const data = { message: 'Data fetched successfully' };
dispatch({ type: 'FETCH_DATA_SUCCESS', payload: data });
}, 1000);
};
};
// 调度异步动作
store.dispatch(fetchData());
在这个示例中,
fetchData
是一个异步动作创建器,它返回一个函数。
redux-thunk
中间件会执行这个函数,并在异步操作完成后调度
FETCH_DATA_SUCCESS
动作。
8. 合理使用 Redux 的最佳实践
- 状态划分 :根据前面提到的两个问题,合理划分哪些状态应该放入 Redux 存储,哪些应该保留在组件本地。例如,表单输入的临时值可以保留在组件本地,而用户的认证信息、文章列表等需要在多个组件中共享的状态应该放入 Redux 存储。
-
动作命名规范
:为动作创建器和动作类型使用清晰、有意义的命名,便于代码的维护和理解。例如,使用
showComments而不是toggleShowHideComments这样模糊的命名。 -
中间件管理
:在使用中间件时,要注意中间件的顺序和功能。不同的中间件可能会有不同的执行顺序要求,确保中间件之间不会相互冲突。例如,
redux-thunk通常是第一个应用的中间件。 -
错误处理
:在每个可能出现错误的地方都要进行错误处理,如网络请求失败、数据解析错误等。使用统一的错误动作创建器,如
createError,将错误信息传递到存储,便于统一管理和显示错误信息。
9. 总结与展望
通过本文,我们详细介绍了 Redux 中动作的创建、调度以及异步动作的处理方法。我们学习了如何使用
redux-thunk
中间件实现异步动作,创建了各种类型的动作创建器,包括错误处理动作、评论动作、文章动作和用户相关动作。同时,我们还探讨了如何合理地决定哪些状态和功能应该放入 Redux 存储。
在未来的开发中,随着应用的不断扩展,我们可能会遇到更复杂的异步操作和状态管理需求。可以考虑使用其他 Redux 中间件库,如
redux-saga
,它可以更优雅地处理异步操作和副作用。此外,还可以结合 React Router 等库,实现更复杂的路由和状态管理。总之,合理使用 Redux 可以帮助我们构建更健壮、可维护的应用架构。
超级会员免费看
37

被折叠的 条评论
为什么被折叠?



