首先咱们要弄清楚 reduxjs
的思想、做用是什么,这样咱们才能开始下一步的构思。在我看来 reduxjs
核心就是一种单一数据源的概念,数据存储在一个函数的 state
变量中,只能经过固定的方法去修改和获取 dispatch()、getState()
。react
在 SPA 应用中,reduxjs
被普遍使用。对数据进行统一管理、实现数据共享,一般组件和组件之间、页面和页面之间能够数据共享。在 react
开发中,我常常将共用的数据和异步请求数据存放在 state
中。经过 props
的形式存在,只要在一个组件中对数据源进行了修改,其余共享的组件都会及时获得更新和渲染UI界面。redux
如今咱们知道了关于 redux
的关键思想和用途,接下来咱们一步一步实现它。我会按照下面这个列表的顺序给你们详细说明:数组
function createStore(reducer, initState) {
// 声明一个初始化用的 action
const INIT_ACTION = undefined;
// 绑定监听事件的集合
const listeners = [];
// 这就是咱们一直说的那个【数据源】
// 参数 initState 能够有,也能够没有。通常状况下不须要传递
let state = initState ? initState : {};
function dispatch(action) {
// action 必须是一个纯对象,不能是其余的类型
if (Object.prototype.toString.call(action) === '[object Object]') {
throw new Error('Actions must be plain objects');
}
// 注意:这里是最终仍是经过调用 reducer 方法
state = reducer(state, action);
// 遍历 listeners
for (let i = 0; i < listeners.length; i++) {
listeners[i]();
}
}
// 获取 state 数据
function getState() {
return state;
}
// 绑定监听事件
function subscription(listener) {
listeners.push(listener);
// 取消监听,将事件从 listeners 中移除
return function() {
const idx = listeners.indexOf(listener);
if (idx >= 0) {
listeners.splice(idx, 1);
}
}
}
// 这是啥意思了,其实这是在调用 createStore() 时,就初始化了一个 state
dispatch(INIT_ACTION);
// 经过对象,将这些内部函数传递到外部。不要怀疑,这就是一个典型的闭包
return {
dispatch,
getState,
subscription,
};
}
复制代码
从 createStore
方法中咱们能够看出来,其实他就是 js模块
。利用了局部变量和闭包的特性,将 state
隐藏起来,只能经过闭包的形式进行访问和修改。闭包
首先 reduce 它是一个函数,咱们能够本身定义。咱们能够把咱们的项目想像成以下的一个场景,修改用户的信息:app
function userName(state = {}, action = {}) {
switch (action.type) {
case 'name':
return { ...state, name: action.data };
case 'age':
return { ...state, age: action.data };
case 'sex':
return { ...state, sex: action.data };
// 必须设置 default,直接返回 state
default:
return state;
}
}
复制代码
若是咱们的项目中只须要这一种交互场景,那么定义 userName() 就够了。这个时候 咱们把 userName 传递给 createStore异步
const { getState } = createStore(userName);
// 返回的是一个 {}
console.log(getState());
复制代码
上面的代码在执行 createStore(userName)
时,内部执行一次 dispatch(INIT_ACTION)
,从而在 dispatch 方法内部调用了 userName({}, undefined)
。因此打印的结果是一个空对象。函数
若是交互场景比较多的时候呢,一个 reducer
确定不够用啊,那么这个时候咱们可能会定义多个相似 userName
这个的 reducer
函数,因此咱们还须要定义一个工具函数 combineReducers
,将多个 reducer
函数组合成一个 reducer
函数。工具
function combineReducers(reducers) {
const keys = Object.keys(reducers);
const finallyKeys = [];
for (let i = 0; i < keys.length; i++) {
if (typeof reducers[keys[i]] !== 'function') throw Error('reducer must be a function');
finallyKeys.push(keys[i]);
}
// 看,最后返回的仍是一个 function
return function(state = {}, action) {
let hasChange = false;
const newState = {};
// 遍历全部的 reducer 函数
finallyKeys.forEach(key => {
// 获取这个 reducer 函数对应的 state。注意它多是一个 undefined
// 没错,在 createStore() 中执行 dispatch(INIT_ACTION),这个时候 prevState_key 可能就是一个 unudefined
const prevState_key = state[key];
const reducer = reducers[key];
// 调用该 reducer,返回一个新的 state
const nextState_key = reducer(prevState_key, action);
// 注意这里,若是 reducer 函数返回的是一个 undefined。那么这里就会报错了
// 因此咱们在定义 reducer 函数时,应该有一个限制:若是没有匹配到 action 的 type 。应该默认返回 previous state。
if (typeOf nextState_key === 'undefined') {
throw Error('to ignore an action, you must explicitly return the previous state');
}
// 当 reducer 执行完成时,会在 newState 上添加一个新属性,属性值就是 nextState_key
// 其实,从这个地方咱们就应该能够猜想到,最终获得的 state【数据源】,它的结果应该和咱们传入的 reducers 结构是同样的
newState[key] = nextState_key;
hasChange = hasChange || nextState_key !== prevState_key;
});
return hasChange ? newState : state;
}
}
复制代码
结合以前的 createStore
,咱们看看下面的 demo:ui
function menu(state = {}, action = {}) {
switch (action.type) {
case 'home':
return { ...state, home: action.data };
case 'list':
return { ...state, list: action.data };
case 'detail':
return { ...state, detail: action.data };
default:
return state;
}
}
const reducer = combineReducers({ userName, menu });
const { getState } = createStore(userName);
// 返回的是一个 { userName: {}, menu: {} }
// 这里和咱们传递给 combineReducers() 中的参数的结构是一致的。
console.log(getState());
复制代码
上面的 reducer
是 userName, menu
的一个组合体,因此每次调用 dispatch(action)
时,都会遍历全部的 reducers
。还有一个很重要的地方就是,每一个 reducer
函数在没有匹配到 action.type
时,必须把 reducer()
的参数 state
做为返回值,不然就报错。spa
reduxjs
还有一个很是厉害的功能,就是能够利用中间件,作不少事情。好比说,咱们比较经常使用的 redux-thunk、redux-logger
等。
// 这里先不考虑参数为空的状况
function compose() {
const middleware = [...arguments];
// 这里利用了redux 高阶函数
// 第一次执行时,将 middleware 中的第一个和第二个元素赋值给 a、b。而后将返回的结果函数 fn 赋值给 a。
// 第二次执行时,a 就是上一次的执行结果,这个时候将 middleware 中的第三个元素赋值给 b。而后将返回的结果函数 fn 赋值给 a。
// 第三次,第四次。依次类推。。。
return middleware.reduce(function(a, b) {
return function fn () {
return a(b.apply(null, arguments));
}
});
}
function applyMiddlyWare(createStore) {
return function(reducer) {
// 接收中间件做为参数
return function(...middlewares) {
const { dispatch, getState, subscription } = createStore(reducer);
// 将 dispatch 赋值给变量 _dispatch
let _dispatch = dispatch;
const disp = (...args) => {
_dispatch(...args);
}
// 将上面定义 disp 内部函数,传递给每个中间件函数
// 因此上面的 disp 就构成了一个闭包
const chain = middlewares.map(middleware => middleware({ dispatch: disp, getState }));
// 这里又对变量 _dispatch 进行了赋值。这里理解可能有点绕,后面再详细介绍
// 注意这里是一个科里化函数的调用, 参数 dispatch 是原始,没有进过改造的
_dispatch = compose(...chain)(dispatch);
return {
dispatch: _dispatch,
getState,
subscription,
}
}
}
}
复制代码
到这里为止,reduxjs 就基本实现了。可是咱们的探讨尚未结束,继续往下看
从上面的代码咱们能够看出来,applyMiddlyWare
函数其实就是对 createStore
的一层封装,最终输出的 dispatch
是通过中间件改造过的。如今咱们来看看这个 dispatch
究竟是什么,它和咱们传入的中间件有什么关系???
const chain = middlewares.map(middleware => middleware({ dispatch: disp, getState }));
_dispatch = compose(...chain)(dispatch);
复制代码
上面的两行代码,先遍历执行中间件,再将变量 chain
传递给 compose
函数。因此咱们应该能够猜想到,表达式 middleware({ dispatch: disp, getState })
应该返回一个函数,否则 compose
中的 reduce
就没有办法执行了。
这里还要考虑到中间件执行的策略,全部的中间件必须串联起来,挨个往下执行。因此中间件应该还应该接收另外一个中间件做为参数。因此如今咱们能够大体的猜想到一个中间件应该是这样的:
function middleware({ dispatch, getState }) {
return function (nextMiddleware) {
return function () {
// 这里应该先执行一些任务,而后再去执行下一个中间件
...
nextMiddleware();
}
}
}
复制代码
这个时候其实中间件的模型还不够完整,少了一些东西。少了什么了,就是 action
呀!applyMiddlyWare
函数经过中间件对 dispatch
进行改造。因此仍是要接收 action
才能对 state
进行修改。因此这下咱们清楚了
function middleware({ dispatch, getState }) {
return function (nextMiddleware) {
return function (action) {
// 在调用 nextMiddleware 以前能够进行一些操做
console.log(1111);
// 必须将 action 传递给下一个中间件
const result = nextMiddleware(action);
// 在调用 nextMiddleware 以后能够进行一些操做
console.log(222);
return result;
}
}
}
复制代码
如今咱们清楚了中间件的模型了,能够来专门研究一下 applyMiddlyWare
函数返回的 dispatch
是啥玩意了
function compose() {
const middleware = [...arguments];
return middleware.reduce(function(a, b) {
return function fn () {
return a(b.apply(null, arguments));
}
});
}
function one(next) {
console.log('one');
return function one_(action) {
console.log('这是中间件one,你能够在这里作不少事情', action);
return next(action)
}
}
function two(next) {
console.log('two');
return function two_(action) {
console.log('这是中间件two,你能够在next调用以前作一些事情', action);
const result = next(action);
console.log('这是中间件two,也能够在next调用以后作一些事情', action);
return result;
}
}
function three(next) {
console.log('three');
return function three_(action) {
console.log('这是中间件three,你能够在这里作不少事情', action);
return next(action)
}
}
// 能够把它看成 createStore 函数返回的 dispatch 方法
function dispatch(action) {
console.log(action);
}
// 我这么写,你们应该能够理解哈。由于 compose 函数接收到的实际上是 middleware({ dispatch, getState }) 返回的结果
// 因此这里的 one, two, three 能够理解为是 middleware({ dispatch, getState }) 返回的结果
// 这里只是作一个简单的 demo,用不到 dispatch, getState。
var disp = compose(one, two, three)(dispatch);
复制代码
咱们把 compose(one, two, three)(dispatch)
这段代码用咱们本身的代码实现一下,大体就是下面这样的效果:
var fn = (function(one, two, three) {
var first = function() {
return one(two.apply(null, arguments));
};
var next = function() {
return first(three.apply(null, arguments));
};
return next
})(one, two, three);
var disp = fn(dispatch);
复制代码
当调用 fn(dispatch)
时,three.apply(null, dispatch)
开始执行,返回一个 three_
函数。继续往下执行。
first(three_)
开始执行,而后执行 two.apply(null, three_)
,two
执行完成,返回一个 two_
函数。继续往下执行。
one(two_)
开始执行,并返回一个 one_
函数,这个函数最终做为 fn(dispatch)
执行的最终结果,并赋值给变量 disp
。
disp(action)
执行时,先调用 one_(action)
而后是 two_(action)
最后是 three_(action)
。注意最后一个中间件接收的参数不是中间件参数了,而是原始的 dispatch
方法。因此会在最后一个中间件中执行 dispatch(action)
,从而调用 rducer 函数修改数据源【state】。
执行 disp({data: 1200, type: 'username'})
这段代码,看下打印的结果是啥
这下咱们就很是清楚了,原来通过 applyMiddlyWare
改造后输出的 dispatch
方法,在调用时,会挨个执行每个传入 applyMiddlyWare
函数的中间件,并在最后一个中间件中调用原始的 dispatch()
方法。
// 中间件1
function thunk ({dispatch, getState}) {
return function (next) {
return function(action) {
if (typeof action === 'function') {
action({dispatch, getState});
} else {
return next(action);
}
}
}
}
// 中间件2
function dialog ({dispatch, getState}) {
return function (next) {
return function(action) {
console.log('prevstate:', getState());
const result = next(action);
console.log('nextstate:', getState());
return result;
}
}
}
复制代码
// 模拟用户http请求
function getUserName(name) {
return ({dispatch}) => {
setTimeout(() => {
dispatch({type: 'name', data: name})
}, 0);
}
}
function getUserAge(age) {
return ({dispatch}) => {
setTimeout(() => {
dispatch({type: 'age', data: age})
}, 0);
}
}
function getUserSex(sex) {
return ({dispatch}) => {
setTimeout(() => {
dispatch({type: 'sex', data: sex})
}, 0);
}
}
function getHome(value) {
return ({dispatch}) => {
setTimeout(() => {
dispatch({type: 'home', data: value})
}, 0);
}
}
function getList(value) {
return ({dispatch}) => {
setTimeout(() => {
dispatch({type: 'list', data: value})
}, 0);
}
}
function getDetail(value) {
return ({dispatch}) => {
setTimeout(() => {
dispatch({type: 'detail', data: value})
}, 0);
}
}
复制代码
// userName, menu 直接复制前面的代码
var reducer = combineReducers({ userName, menu });
var { dispatch, getState, subscription } = applyMiddlyWare(store)(reducer)(thunk, dialog);
console.log(getState(), 'initState');
const name_button = document.querySelector('.name');
const age_button = document.querySelector('.age');
const sex_button = document.querySelector('.sex');
const home_button = document.querySelector('.home');
const list_button = document.querySelector('.list');
const detail_button = document.querySelector('.detail');
const addListener = document.querySelector('.addListener');
const removeListener = document.querySelector('.removeListener');
name_button.onclick = function() {
dispatch(getUserName('shenxuxiang'))
};
age_button.onclick = function() {
dispatch(getUserAge('29'))
};
sex_button.onclick = function() {
dispatch(getUserSex('man'))
};
home_button.onclick = function() {
dispatch(getHome('home_page'))
};
list_button.onclick = function() {
dispatch(getList('list_page'))
};
detail_button.onclick = function() {
dispatch(getDetail('detail_page'))
};
let removeListen;
addListener.onclick = function() {
removeListen = subscription(function() {
console.log('咱们添加了一个事件监听器', getState())
})
};
removeListener.onclick = function() {
removeListen && removeListen();
};
复制代码