react-redux 是 React 生态中比较(如果不是最的话)流行的一种状态管理方式,而它所依托的 redux,继承了 flux 的衣钵,并且引入了单一数据源、state 为只读、只能通过纯函数修改三大原则。一个最简单的 redux 应用示例:
// https://cn.redux.js.org/
import { createStore } from 'redux'
/**
* 这是一个 reducer,形式为 (state, action) => state 的纯函数。
* 描述了 action 如何把 state 转变成下一个 state。
*
* state 的形式取决于你,可以是基本类型、数组、对象、
* 甚至是 Immutable.js 生成的数据结构。惟一的要点是
* 当 state 变化时需要返回全新的对象,而不是修改传入的参数。
*
* 下面例子使用 `switch` 语句和字符串来做判断,但你可以写帮助类(helper)
* 根据不同的约定(如方法映射)来判断,只要适用你的项目即可。
*/
function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}
// 创建 Redux store 来存放应用的状态。
// API 是 { subscribe, dispatch, getState }。
let store = createStore(counter)
// 可以手动订阅更新,也可以事件绑定到视图层。
store.subscribe(() => console.log(store.getState()))
// 改变内部 state 惟一方法是 dispatch 一个 action。
// action 可以被序列化,用日记记录和储存下来,后期还可以以回放的方式执行
store.dispatch({ type: 'INCREMENT' })
// 1
store.dispatch({ type: 'INCREMENT' })
// 2
store.dispatch({ type: 'DECREMENT' })
// 1
复制代码
createStore
是 redux 中最基础的 API,它的作用就是返回一个 store
;而这个 store
有三个方法 —— dispatch
, subscribe
和 getState
。其实从方法名上,我们大致可以推测出 redux 应用了订阅发布模式;该模式可以直接套用下面的模板代码:
// 开始
// createStore 是一个闭包,维护了 currentState, listeners 私有性;
// 外层想要获取 state,只能通过 getState;
// listeners 无法从外层修改
function createStore(reducer, preloadedState) {
// 每次调用 subscribe,就会向 listeners 数组 push 一个 listener;
// 每次调用 dispatch,listeners 数组中的函数会依次执行;
const listeners = [];
let currentReducer = reducer;
let currentState = preloadedState;
function dispatch(action) {
// 正如示例代码中所展现的,reducer(即 counter) 函数接受 state,action 两个参数
// action 是一个简单对象,有一个 type 属性;
// reducer 中根据 type 执行对应操作,返回新的 state;
currentState = currentReducer(currentState, action);
// 执行 listeners 数组中的所有函数
listeners.forEach(listener => listener());
}
function subscribe(listener) {
listeners.push(listener);
// 返回一个取消订阅的函数
return function unsubscribe() {
let index = listeners.indexOf(listener);
listeners.splice(index, 1);
};
}
// 这个函数的作用就是返回 state
function getState() {
return currentState;
}
return {
dispatch,
subscribe,
getState
};
}
function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state;
}
}
// 测试一下
let store = createStore(counter);
store.subscribe(() => console.log(store.getState()));
store.dispatch({ type: 'INCREMENT' });
// 1
store.dispatch({ type: 'INCREMENT' });
// 2
store.dispatch({ type: 'DECREMENT' });
// 1
复制代码
上面的代码只是为了大致解释一下 redux 的运行机制,其实是有不少问题的,比如 action 只能是一个简单对象,但我们的代码中并没有进行检测;我们暂且不去关注这些问题,而是先去实现功能。接下来,修改 dispatch,使它支持函数参数:
function dispatch(action) {
// 如果传入一个函数,则为这个函数注入 dispatch 和 getState 方法,
// 并且执行这个 action
if (typeof action === 'function') {
return action(dispatch, getState);
}
// 正如示例代码中所展现的,reducer(即 counter) 函数接受 state,action 两个参数
// action 是一个简单对象,有一个 type 属性;
// reducer 中根据 type 执行对应操作,返回新的 state;
currentState = currentReducer(currentState, action);
// 执行 listeners 数组中的所有函数
listeners.forEach(listener => listener());
return action;
}
复制代码
用异步的方式,测试一下:
function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state;
}
}
function asyncAdd(dispatch) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(
dispatch({
type: 'INCREMENT'
})
);
}, 1000);
});
}
// 测试一下
let store = createStore(counter);
store.subscribe(() => console.log(store.getState()));
store.dispatch({ type: 'INCREMENT' });
// 1
store.dispatch({ type: 'INCREMENT' });
// 2
store.dispatch(asyncAdd).then(() => store.dispatch(asyncAdd)); // 1s 后 3, 2s 后 4
复制代码
现在,我们的代码已经具备了 redux + redux-thunk 的功能,而且没有增加新的 API。但我们是用入侵的方式修改了 dispatch,如果要增加其他新功能怎么办?继续修改 dispatch 么?显然不能这么干。redux 使用中间件的机制,来修改 dispatch:
function compose(...funcs) {
if (funcs.length === 0) {
return args => args;
}
if (funcs.length === 1) {
return funcs[0];
}
return funcs.reduce((a, b) => (...args) => a(b(...args)));
}
function applyMiddleware(...middlewares) {
return createStore => (...args) => {
const store = createStore(...args);
let dispatch = () => {
throw new Error('not valid');
};
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
};
const chain = middlewares.map(middleware => middleware(middlewareAPI));
// applyMiddleware 本质上,还是需要去覆盖原来的 dispatch 方法
dispatch = compose(...chain)(store.dispatch);
return {
...store,
dispatch
};
};
}
// 我们的 thunk
const thunk = ({ dispatch, getState }) => next => action => {
// 这段代码,和我们上面暴力修改 dispath 的方法,其实是一样的
if (typeof action === 'function') {
return action(dispatch, getState);
}
return next(action);
};
复制代码
以上就是 redux 以及 redux-thunk 的简单实现,有不少 bug,希望对大家理解 redux 有点帮助;也建议大家去看看 redux 的源码,其实源码也很短,并且注释很完整。