前言
Redux 正是函数式编程的一个经典实现,本文将依据原版的 redux ,简单讲述一下 redux 的函数式编程并完成自己的一个 redux ,让大家知其然知其所以然
在这之前,你需要了解的东西:
- 纯函数,固定的输入就有固定的输出
- react 的函数组件体现就是纯函数,接受 props,输出的 view 就是纯的
- redux 本身就是一个容器的概念(container)
- react 内部有 state,就是容器(container)的值
- action 接受变形关系
- reducer 可以遍历(map)当前容器的值并执行相应修改更新操作
- middleware(中间件)也就是IO函子,它解决了异步的操作
redux 的目录结构
-
createStore.js
创建一个 redux 的 store ,用来存放 state -
bindActionCreators.js
把 action Creators 转化成拥有同名 keys 的对象,在组件内使用的时候直接的调用 -
applyMiddleware.js
使用自定义的中间件来扩展 redux -
combineReducers.js
对reducer 进行拆分,拆分后每一块独立负责管理 state 的部分 -
compose.js
从右向左组合函数,避免柯里化函数而写出洋葱式的代码h(g(f(x)))
-
index.js
向外输出的
开始我们的 redux
- 首先创建一个 index.js 文件,用于向外输出我们的核心(createStore)和其他方法
- 就是一个对象从同级目录下导入,并导出
import createStore from "./createStore.js";
export { createStore, combineReducers };
第一版 (createStore)
-
创建核心文件(createStore.js)
- 先在在内部实现 subscribe、changeState、getState 三个方法
- 订阅(subscribe),常用的开发模式就是往里面放一个数据并把东西都塞到数组里
let listeners = []; function subscribe(listener) { listeners.push(listener); }
- 通知(changeState),对 listeners 里面的每一项执行派发执行
function changeState(newState) { state = newState; for (let item of listeners) { item(); } }
- getState 就返回 state 的值
function getState() { return state; }
- 如此我们就完成了 createStore 的创建
export default function createStore(initState) {
let state = initState;
let listeners = [];
function subscribe(listener) {
listeners.push(listener);
}
function changeState(newState) {
state = newState;
for (let item of listeners) {
item();
}
}
function getState() {
return state;
}
return {
subscribe,getState,changeState
};
}
第一版总结
这个版本对状态的管理没有约束,changeState是我们改变状态的唯一方法,此时我们需要使用到 reducer,从而有了我们的第二版
第二版 (dispatch)
-
第一版中的 changeState 方法是我们自己编出来的
-
在 redux 中执行更新的是 reducer(业务逻辑),派发任务的方法是 dispatch
-
如此我们把刚才的 changeState 改成 dispatch
-
此时的 dispatch 不是随意执行了,而是按照 reducer 的规则来执行
-
createStore.js
export default function createStore(reducer, initState) {
let state = initState;
let listeners = [];
function subscribe(listener) {
listeners.push(listener);
}
function dispatch(action) {
state = reducer(state, action);
for (let item of listeners) {
item();
}
}
function getState() {
return state;
}
return {
subscribe,
getState,
dispatch
};
}
- 业务逻辑(对状态的修改操作)其实是在reducer里面的,reducer 最终会返回一个新的 state ,在使用的时候,我们需要引入 reducer, 执行 createStore 并传入 reducer 和 initState,然后使用 dispatch 方法来派发当前的任务,表明任务的类型,然后使用 subscribe 来订阅可以获取更新后的值
- 注意
如果在使用 createStore 过程中只传入了 reducer 并没有传入 initState ,如此就会得到一个undefined,那么我们需要进行一步操作:在 createStore.js 文件里,自身调用一下 dispatch,传入的参数为 {type:Symbol()},有用户就要说了,那我传入一个 {type:'xxxxx'}不也行吗,那假如有个开发者在 reducer 里面写了一个 type 正好就是 'xxxxx' 不就熄火了吗,所以使用的 type 的值为 Symbol 是最可靠的,可以保证不会出现重复!
第二版总结
这个版本我们加入了 reducer (业务逻辑)来修改状态使用的值,但是有个确定,非计划外的任务不能修改,也就是当你传递的 action 不在 reducer 的类型中的时候,是不会进行更改的
第三版 (combineReducer)
-
当项目过于复杂时,就会出现多个 reducer,用来分别执行各自的修改
-
此时就需要合并多个 reducer 成一个reducer 并使用
-
此时我们创建一个 combineReducer.js 文件来完成多个 reducer 的合并
- combineReducer 方法接受一个 reducers 的对象作为参数
- 将 reducers 对象的键名(key)存为一个数组
const reducerKeys = Object.keys(reducers);
- 然后返回合并后的 reducer,让它成为一个大的 reducer,所以它和那些小的 reducer 长得都是一样的(一个function接受 state 和 action)
return function combination(state = {}, action) {}
- 遍历这个 reducerKeys 得到每个 reducer
- 通过 key 来取到之前的状态(previousStateOfKey)
- 则执行 reducer 传入之前的状态和 action 来得到下一个状态
- 然后返回一个 nextState
- 如此我们就完成了 combineReducer.js
export default function combineReducers(reducers) { const reducerKeys = Object.keys(reducers); return function combination(state = {}, action) { const nextState = {}; for (let key of reducerKeys) { const reducer = reducers[key]; const previousStateOfKey = state[key]; const nextStateOfKey = reducer(previousStateOfKey, action); nextState[key] = nextStateOfKey; } return nextState; }; }
- 使用 combineReducer
import { combineReducer } from "./axtlive-redux/index.js"; import counterReducer from "./reducers/counter.js"; import infoReducer from "./reducers/info.js"; const reducer = combineReducer({ counter: counterReducer, info: infoReducer });
第三版总结
这个版本我们创建了 combineReducer 来合并多个小的 reducer 成为一个大的 reducer,使得多种逻辑处理相分离
第四版 (middleware && applyMiddleware)
- 众所周知,在 redux 里面 dispatch 一个 action ,它就会到达 reducer 进行处理,而middleware 就可以让我们在 dispatch action 之后,到达reducer之前,这个时间段内搞点事情,例如(打印、报错、跟异步API通信等等)
- 有个需求,在每次 dispatch 了 action 之后,我们想要获取到下一个 state 的 值 (nextState)
let action = doSomthing('123');
console.log('dispatching',action)
store.dispatch(action);
console.log('nextState',store.getState())
- 如上我们就可以实现这个需求了,接下来我们把它放到一个函数里面
function getDispatchLog(store,action){
let action = doSomthing('123');
console.log('dispatching',action)
store.dispatch(action);
console.log('nextState',store.getState())
}
- 然而我们并不希望每次都来 import 这个函数,所以我们直接替代
function logger(){
let next = store.dispatch
return function dispatchLog(action){
let action = doSomthing('123');
console.log('dispatching',action)
next(action);
console.log('nextState',store.getState())
}
}
- 这样在下次使用 dispatch 的时候就可以获取到 nextState 了,好似一个 log 的功能,这样的功能我们就称之为 middleware ,用来增强 dispatch 的功能
- 我们可以直接 return 这个函数,就可以在后面实现一个链式调用,赋值这件事就在 applyMiddleware里做
- 接下来我们实现一个 applyMiddleware 来连接两个 middleware
function myApplyMiddleware(store,middlewares){
middlewares = middlewares.splice()
middlewares.reverse()
middlewares.forEach(middleware => {
store.dispatch = middleware(store)
})
}
- 在 applyMiddle 里必须要给 store.dispatch 赋值,否则下一个 middleware 就拿不到最新的dispatch,但是可以在 middleware 里不直接从 store.dipatch 里读取 next 函数,而是将 next 作为一个参数传入,在 applyMiddleware 里用的时候把这个参数传下去
function logger(store) {
return function wrapDispatchLog(next) {
return function dispatchLog(action) {
console.log('dispatching', action)
let result = next(action)
console.log('next state', store.getState())
return result
}
}
}
- 对上述代码用 ES6 的柯里化写法,写成如下形式
const logger = store => next => action => {
console.log('dispatching', action)
let result = next(action)
console.log('next state', store.getState())
return result
}
-
对比一下 thunk 这个中间件,它是用于异步的,由上可以看出 middleware 需要三个参数,store,next,action,而 thunk 就看第三个参数是否为一个 function,如果是则执行它,如果不是,则就按照原来的方式执行 next(action)
-
然后我们再了解一下函数的合成,一个数据要经过多个函数的处理,才可以成为另外一个数据,那我们把所有中间的处理合并成一个函数的过程叫做函数的合成(compose)
-
compose 做的事情就是将上一个函数的返回结果作为下一个函数的参数传入
export const compose = (...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
- 在redux源码里 createStore 的时候,如果发现第二个参数为一个 function 且第三个参数为 undefined,那么它会认为你使用了中间件则创建 store 完全交给了中间件
- 下面我们就可以看到最终的 applyMiddleWare 了
export const applyMiddleWare = (...middlewares) => (createStore) => (reducer,initState) => {
const store = createStore(reducer,initState)
let dispatch = store.dispatch
const middleWareAPIs = middlewares.map(item => item({
getState: store.getState,
dispatch: action => dispatch(action)
}))
dispatch = compose(...middleWareAPIs)(dispatch)
return {
...store,
dispatch
}
}
第四版总结
这个版本我们实现了 applyMiddleWare 来增强 dispatch,其实在使用了 middleware 以后,创建 store 的任务就交给了 中间件,它最终也是会返回一个新的 store,subscribe 和 getState 方法并没有改变,只是拥有了一个新的 dispatch 方法
总结
- 本文就 createStore、combinReducers、compose、applyMiddleWare 的思想对创建步骤进行了讲述
- 纯函数,有固定的输入,就有固定的输出
- createStore 作为核心部分,内部拥有subscribe、dispatch、getState 三个方法,用于订阅、通知、获取
- combinReducers 会对多个小的 reducer 进行合并,然后返回一个大的 reducer 来进行业务逻辑的处理
- compose 就是将上一个函数的执行结果作为下一个函数的参数传入
- applyMiddleWare 则是使用一些中间件(在 dispatch action 之后,到达 reducer 之前执行的一些操作的方式)来增强 dispatch(比如执行一下异步的操作等)
网友评论