Redux-CheatSheet
本文详细地阐述了 Redux 的设计理念与实践技巧,包含了其三大原则与简单的仿制、基础组件以及 React 集成使用、基于 Thunk, Promise, Sagas 三种不同的异步处理方式、Selector, Ducks 等其他常见的样式规范、中间件的实现原理与代码分析等。
Redux CheatSheet | Redux 设计理念与实践技巧清单
Redux 是受 Flux 启发的,类似于 Event Sourcing 的事件驱动型框架。
基础组件
实践工具
ducks
ducks-modular-redux 是对于 Ducks 规范的描述,其按照业务模块将 reducer, action, actionTypes 合并到单一的文件中。
// widgets.js
// Actions
const LOAD = "my-app/widgets/LOAD";
const CREATE = "my-app/widgets/CREATE";
const UPDATE = "my-app/widgets/UPDATE";
const REMOVE = "my-app/widgets/REMOVE";
// Reducer
export default function reducer(state = {}, action = {}) {
switch (action.type) {
// do reducer stuff
default:
return state;
}
}
// Action Creators
export function loadWidgets() {
return { type: LOAD };
}
export function createWidget(widget) {
return { type: CREATE, widget };
}
export function updateWidget(widget) {
return { type: UPDATE, widget };
}
export function removeWidget(widget) {
return { type: REMOVE, widget };
}
// side effects, only as applicable
// e.g. thunks, epics, etc
export function getWidget() {
return dispatch =>
get("/widget").then(widget => dispatch(updateWidget(widget)));
}
A module…
MUST export default a function called reducer() MUST export its action creators as functions MUST have action types in the form npm-module-or-app/reducer/ACTION_TYPE MAY export its action types as UPPER_SNAKE_CASE, if an external reducer needs to listen for them, or if it is a published reusable library
在外部使用时,我们可以导出默认的 reducer:
import { combineReducers } from "redux";
import * as reducers from "./ducks/index";
const rootReducer = combineReducers(reducers);
export default rootReducer;
在组件中,可以导出所有的 Action:
import * as widgetActions from "./ducks/widgets";
redux-actions
import { createActions, handleActions, combineActions } from "redux-actions";
const defaultState = { counter: 10 };
const { increment, decrement } = createActions({
INCREMENT: (amount = 1) => ({ amount }),
DECREMENT: (amount = 1) => ({ amount: -amount })
});
const reducer = handleActions(
{
INCREMENT: (state, action) => ({
counter: state.counter + action.payload
}),
DECREMENT: (state, action) => ({
counter: state.counter - action.payload
})
},
defaultState
);
const reducer = handleActions(
{
[combineActions(increment, decrement)](
state,
{
payload: { amount }
}
) {
return { ...state, counter: state.counter + amount };
}
},
defaultState
);
export default reducer;
import { createStore, applyMiddleware } from 'redux';
import promiseMiddleware from 'redux-promise-middleware';
import thunkMiddleware from 'redux-thunk';
import logger from 'redux-logger';
const reducer = (state = {}, action) => {
...
}
const store = createStore(reducer, {}, applyMiddleware(
thunkMiddleware,
promiseMiddleware(),
logger,
));
export default store;
const promiseAction = () => ({
type: "PROMISE",
payload: Promise.resolve()
});
Async Actions | 异步 Action 处理
Thunk
Promise
redux-promise 会自动处理 payload 为 Promise 对象的 Action,并且会分发该 Action 的副本,包含了 Promise 的处理结果,并且根据处理结果动态设置了 status 属性为 success
或者 error
。
// 创建简单的异步 Action
createAction("FETCH_THING", async id => {
const result = await somePromise;
return result.someValue;
});
// 与自定义的 WebAPI 协同使用
import { WebAPI } from "../utils/WebAPI";
export const getThing = createAction("GET_THING", WebAPI.getThing);
export const createThing = createAction("POST_THING", WebAPI.createThing);
export const updateThing = createAction("UPDATE_THING", WebAPI.updateThing);
export const deleteThing = createAction("DELETE_THING", WebAPI.deleteThing);
redux-promise-middleware 为我们提供了类似的异步处理功能,其能够接受某个 Promise,并且依次分发 Pending, Fulfilled, 以及 Rejected 这几个不同状态的 Action:
const promiseAction = () => ({
type: "PROMISE",
payload: Promise.resolve()
});
该工具同样可以与 Redux Thunk 协同使用,来进行多个 Action 的分发:
const secondAction = data => ({
type: "TWO",
payload: data
});
const first = () => {
return dispatch => {
const response = dispatch({
type: "ONE",
payload: Promise.resolve()
});
response.then(data => {
dispatch(secondAction(data));
});
};
};
我们也可以自己实现 Promise 中间件,可以参考 redux/promiseMiddleware 的源代码实现。在实际应用中,我们往往需要等待某个操作处理结束进行刷新等附加响应;此时我们即可以创建自定义的 Thunk,也可以直接在类方法中使用 await 来等待 dispatch 函数返回的 Promise:
const { fetchThing } = bindActionCreators({ fetchThing }, dispatch);
// 这里即会在 Redux 中分发 Action,同样也会阻塞执行直至 Promise 处理完毕
await fetchThing().payload;
Sagas
Sagas 是源于计算机科学与技术的概念,用于描述事务及其关联处理操作的约束。redux-sagas 的官方描述是用于处理副作用(Side Effects)的 Redux 中间件,允许我们以同步地方式编写异步代码,并使用 try-catch 进行异常处理。
与 redux-thunk 相比,redux-sagas 并不是直接从 UI 调用逻辑代码,而是进行纯粹地 dispatch action;所有的异步流程控制都被移入到了 sagas,从而增强组件与逻辑代码的可复用性与可测试性。redux-sagas 基于 ES6 Generator,能为我们提供高级的异步控制流以及并发管理,可以使用简单的同步方式描述异步流,并通过 fork 实现并发任务。
参考 fe-boilerplate/redux 的示例,我们首先需要引入并且创建 Sagas 中间件实例:
import createSagaMiddleware from 'redux-saga';
import rootSaga from '../sagas/sagas';
const sagaMiddleware = createSagaMiddleware();
// 引入中间件,并构建 Store 对象
...
sagaMiddleware.run(rootSaga);
这里的 sagas 文件,即包含了具体的逻辑处理代码:
// helloSaga 会在 sagaMiddleware.run 时即刻执行
export function* helloSaga() {
console.log("Hello Saga!");
}
// worker saga
export function* incrementAsync() {
yield call(delay, 1000);
// 继续分发事件
yield put({ type: "SAGA_INCREMENT" });
}
// watcher saga
export function* watchIncrementAsync() {
// 监听 Action,并执行关联操作
yield takeEvery("SAGA_INCREMENT_ASYNC", incrementAsync);
}
// root saga
export default function* rootSaga() {
yield [helloSaga(), watchIncrementAsync()];
}
Sagas 为我们定义了三种不同的 Saga,其中 Worker Saga 负责 API 调用、异步请求、结果处理等具体的任务;Watcher Saga 则监听被 dispatch 的 actions,当接收到 action 或者知道其被触发时,调用 worker saga 执行任务。而 Root Saga 则是立即启动 Sagas 的唯一入口。Saga 使用 Generator 函数来 yield Effect,其利用生成器可以暂停执行,再次执行的时候从上次暂停的地方继续执行的特性。Effect 是一个简单的对象,该对象包含了一些给 middleware 解释执行的信息。可以通过使用 effects API,如 fork,call,take,put,cancel 等来创建 Effect。
如 yield call(fetch, '/user')
即 yield
了下面的对象,call
创建了一条描述结果的信息,然后,redux-saga
middleware 将确保执行这些指令并将指令的结果返回给生成器:
{
type: CALL,
function: fetch,
args: ['/user']
}
我们也可以并发执行多个任务:
const [users, repos] = yield[(call(fetch, "/users"), call(fetch, "/repos"))];
同样以常见的接口请求,与结果处理为例:
import { take, fork, call, put } from "redux-saga/effects";
// The worker: perform the requested task
function* fetchUrl(url) {
// 指示中间件调用 fetch 异步任务
const data = yield call(fetch, url);
// 指示中间件发起一个 action 到 Store
yield put({ type: "FETCH_SUCCESS", data });
}
// The watcher: watch actions and coordinate worker tasks
function* watchFetchRequests() {
while (true) {
// 指示中间件等待 Store 上指定的 action,即监听 action
const action = yield take("FETCH_REQUEST");
// 指示中间件以无阻塞调用方式执行 fetchUrl
yield fork(fetchUrl, action.url);
}
}
样式风格
ducks
redux-actions
import { createActions, handleActions, combineActions } from "redux-actions";
const defaultState = { counter: 10 };
const { increment, decrement } = createActions({
INCREMENT: (amount = 1) => ({ amount }),
DECREMENT: (amount = 1) => ({ amount: -amount })
});
const reducer = handleActions(
{
INCREMENT: (state, action) => ({
counter: state.counter + action.payload
}),
DECREMENT: (state, action) => ({
counter: state.counter - action.payload
})
},
defaultState
);
const reducer = handleActions(
{
[combineActions(increment, decrement)](
state,
{
payload: { amount }
}
) {
return { ...state, counter: state.counter + amount };
}
},
defaultState
);
export default reducer;
import { createStore, applyMiddleware } from 'redux';
import promiseMiddleware from 'redux-promise-middleware';
import thunkMiddleware from 'redux-thunk';
import logger from 'redux-logger';
const reducer = (state = {}, action) => {
...
}
const store = createStore(reducer, {}, applyMiddleware(
thunkMiddleware,
promiseMiddleware(),
logger,
));
export default store;
const promiseAction = () => ({
type: "PROMISE",
payload: Promise.resolve()
});
Middleware | 中间件
实践的思考
Redux 适合于需要强项目健壮度与多人协调规范的大中型团队,对于很多中小型创业性质,项目需求迭代异常快的团队则往往可能起到适得其反的作用。如果你真的喜欢 Redux,那么更应该在合适的项目,合适的阶段去接入 Redux,而不是在需求尚未成型之处就花费大量精力搭建复杂的脚手架,说不准客户的需求图纸都画反了呢。Dan 推荐的适用 Redux 的情况典型的有:
仅就笔者的个人实践而言,在
对于数据的获取
对于简单可重复的全局状态,譬如通用的接口返回的错误信息,可以使用全局的错误状态:
function reducer(state, { payload }) {
if (payload.error) {
return {
...state,
errorMessage: payload.error.message
};
}
}
如果是复杂接口响应的处理,譬如创建或者更新的表单,特别是还需要包含大量的非跨组件的 UI 操作,那么不妨使用本地状态处理,当然也可以使用 Thunk 函数进行处理:
class Com extends Component {
async handleSubmit() {
try {
const result = await doMutation();
if (result.success) {
// 执行成功之后的界面操作
showSuccessMessage();
fetchThing();
closeModal();
} else {
// 执行失败之后的部分操作
doFallback();
}
} catch (e) {
showErrorMessage();
}
}
}
关键源代码
react-redux
在 Provider.js 中: