从大学到现在用 React
开发差不多有1年多了,虽然还是一个小菜鸟,不过 react
虐我千百遍,我仍待她如初恋嘻嘻。
Redux
是咱使用的第一个状态管理器,在学习的时候总觉得它是依赖于 react
的,还和 react-redux
搞混了。了解了官网以及查阅很多关于 redux
才知道其实 redux
和 react
以及 react-redux
没有什么关系,因为它是原生 js 编写的,所以任何框架都可以使用它。而react-redux
为react
和redux
提供了更为便捷的关联方式,方便我们维护;
开始进入我们的真香环节,如果有不怎么熟悉 redux
建议先阅读中文文档
createStore
createStore 函数用于创建并且返回一个状态操作对象,以下是对 store 对象的部分类型定义;
interface action {
type: string;
[payload: string]: any;
}
type subscribe = (lister: Function) => Function
interface Store {
dispatch: action;
getState: Function;
subscribe: subscribe;
}
store 对象主要有有getState
,dispatch
和subscribe
这几个 API。
getState
用于获取最新的状态dispatch
用于派发action
对象进行状态更新subscribe
用于监听状态变更
知道这几个 API 的功能后我们之一来实现吧~
/**
* createStore
* 创建 store 对象,并将对象返回
* @param {(state:{[key:string]:any},action:{type: string,[key:string]:any}) => {[key:string]:any}} reducer
* @param {Function} [middleware] 中间件
* @returns {{dispatch: Function,getState: Function,subscribe: Function}} state 操作对象
*/
function createStore(reducer) {
// store 状态
let state;
//监听队列
let listers = [];
/**
* 获取最新的 state
* @returns {store} state
*/
function getState() {
const { parse, stringify } = JSON;
// 为了安全起见,返回深拷贝后的 state 对象,防止组件随意增加属性造成数据污染
return parse(stringify(state));
}
/**
* 发布函数
* 接受一个 action 对象
* @param {{type: string,[key:string]:any}} action
* @returns {{[key:string]: any}} action
*/
function dispatch(action) {
// 将 action 派发给 reducer 函数进行状态处理,并且更新最新的 state
state = reducer(state, action);
// 状态更新后还得执行以下我们的监听队列,告诉他们我们的 state 更新啦
listers.forEach(observer => typeof observer === 'function' && observer());
// 将此次分发的 action 返回出去
return action;
}
/**
* 订阅函数
* @param {Function} lister 监听函数
* @returns {Function} disconnect 注销监听
*/
function subscribe(lister) {
if (typeof lister !== 'function') {
console.warn(
'The Lister parameter of the subscribe function must be a function'
);
// 返回一个匿名函数,防止报错
return () => {
// 顺便在多提示几下  ̄ω ̄=
console.warn(
'The Lister parameter of the subscribe function must be a function'
);
};
}
return function () {
// 将监听的数组从 listers (监听队列)移除掉
listers = listers.filter(observer => observer !== lister);
};
}
// 初始化 state ,派发一个私有的 action,避免重名影响到状态误改
dispatch({ type: `CODE_ROOKIE_262@@${Date.now().toString(16)}` });
return {
dispatch,
getState,
subscribe
};
}
以上基本实现了 createStore
。
基本用法
// 定义一个 计数器 reducer
function reducer(state,action){
switch(action.type){
case 'add':
return {...state,count: state.count + 1};
case 'minus':
return {...state,count: state.count - 1};
default:
return state;
}
}
// 创建 Store
const store = createStore(reducer);
// 订阅状态监听
let observer = store.subscribe(function(){
console.log('new state',store.getState())
});
// 派发 action 更改状态
store.dispatch({
type: 'add'
})
// 注销监听
observer()
combineReducers
有的时候我们的状态是有模块区分的,就和 Vuex
一样有多个 module
进行分开管理,避免多个状态一同处于同一级造成数据会造成难以维护,所以我们也需要对不同的状态进行划分,需要将reducer
函数拆分成多个单独的函数对独自的状态状态管控。但是上文中的 createStore
只接受一个 reducer
,如何将多个 reducer
同时传进入呢?
这个时候就是我们大哥 combineReducers
的工作了,他接受一个对象,通过{reducer: reducerFun}
形式的参数传递给 combineReducers
,combineReducers
接收到参数后会返回一个reducer
函数,将这个函数传递给 createStore
即可。
/**
* 合并多个 reducer 函数
* @param reducers {reducer1: reducer1,reducer2: reducer2,...}
* @returns reducer
*/
function combineReducers(reducers) {
return function reducer(state = {}, action) {
let newState = {};
// 更新每个模块的 state,并且将其最新状态返回
for (var key in reducers) {
newState[key] = reducers[key](state[key], action);
}
return newState;
};
}
使用方法
const count = function countReducer(state,action){...};
const list = function countReducer(state,action){...};
const reducer = combineReducers({
count: count,
list: list
})
const store = createStore(reducer);
applyMiddleware
使用包含自定义功能的 middleware 来扩展 Redux 是一种推荐的方式。Middleware 可以让你包装 store 的 dispatch 方法来达到你想要的目的。同时, middleware 还拥有“可组合”这一关键特性。多个 middleware 可以被组合到一起使用,形成 middleware 链。其中,每个 middleware 都不需要关心链中它前后的 middleware 的任何信息。
以上是引用 redux 中文网
可以看出 applyMiddleware
主要是通过 一个或者多个 middleware
包装 store.dispatch
。
中间件的用法
// redux-logger 是打印状态变更的一个中间件
import logger from 'redux-logger';
import {createStore,applyMiddleware} from 'redux'
function reducer(state,action){}
let store = createStore(reducer,applyMiddleware(logger))
这个时候我们得给我的上面的 createStore
函数进行一些参数的调整。
function createStore(reducer,middleware){
//...other code
// 安装 middleware
if(typeof middleware === 'function'){
return middleware(createStore)(reducer)
}
return {
getState: getState,
dispatch: dispatch,
subscribe: subscribe
}
}
我们先从单个中间件middleware
的思路入手更容易理解吧~
function applyMiddleware(middleware){
return function(createStore){
return function(reducer){
// 创建 store
let store = createStore(reducer);
// 将 store 传入中间件
let middle = middleware(store);
// 将 dispatch 传入中间件返回的函数进行修饰,并返回
let middleDispatch = middle(store.dispatch);
return {
...store,
dispatch: middleDispatch
}
}
}
}
基本的中间件注入是解决了,但是applyMiddleware
是支持多个中间件的,也就是说最后返回的dispatch
只有一个,并且是经过了多个中间件修饰过的了。那么就有一个难点,就是如何获取最终的的dispatch
呢?
例如有 middleware1
和middleware2
,每个函数都接受原有的dispatch
后返回新的dispatch
,
可以分解为:
const dispatch1 = middleware1(dispatch);
const dispatch2 = middleware(dispatch11);
所以我们是不是可以这样写呢 => middleware2(middleware2(dispatch))
;
那我们可以封装一函数 compose
将多个 middleware
组合成一个函数,这个函数接受一个的 dispatch
并返回最终的 dispatch
;
在此之前我们可以看下 redux
源码中的处理,我们可以在 compose.js
中看到这样一句代码,非常巧妙。
funcs.reduce((a, b) => (...args) => a(b(...args)));
可以看看简化后的代码
function compose(...funs) {
return arg => {
let res = arg;
for (var i = 0, fl = funs.length; i < fl; i++) {
// 接受返回值,并且将上一个函数的返回值传递给当前函数
res = funs[i](res);
}
return res;
};
}
看完后是不是焕然大悟了哈哈哈,当然用 数组的 reduce
还有另外的写法哟´( ̄▽ ̄)~*
function compose(...funs) {
return dispatch => funs.reduce((res, fun) => fun(res), dispatch);
}
关于插件合并的问题我们基本解决了,接下来我们再次修改一下我们的 applyMiddleware
函数。
function applyMiddleware(...middleware){
return function(createStore){
return function(reducer){
// 创建 store
let store = createStore(reducer);
// 将 store 传入中间件
+ let middles = middleware.map(middle => middle(store));
// 将 dispatch 传入中间件返回的函数进行修饰,并返回
+ let middleDispatch = compose(...middles)(store.dispatch);
return {
...store,
dispatch: middleDispatch
}
}
}
}
这样就实现多个插件同时注入的问题了,也基本实现了redux
的功能,在实现的过程中虽然会遇到很多挫折,不过最终实现完成更让人充满动力哈哈哈Y(^o^)Y