redux适用于很多场景,需要用到全局存储状态的应用都可以用到它,不管是换肤的应用还是购物车的场景,需要将不同的组件通过相同的状态关联起来,或者相同状态的变化触发不同视图的更新,都很适合用到redux。
这篇文章通过实现一个简单的redux,理解redux是怎样把状态和视图关联起来的,接下来会实现redux这几个接口:
export {
createStore, // 创建store,接受reducer函数和初始化的状态state
combineReducer, // 合并多个reducer
bindActionCreator, // 转换action对象
}
redux会有一个存放和管理状态的函数reducer
,用户更改状态需通过dispatch
才能修改里面的状态,这样能避免用户直接修改state:
import * as TYPES from '../typings'
function reducer(state,action){
switch(action.type){
case TYPES.ADD:
return {...state,action.payload}
default:
return state;
}
}
这里可以看出,action
是一个对象,而且必须有type
属性,reducer
通过判断type
属性对state进行不同的合并操作并返回更新后的state
。另外,这里还通过typings
文件维护不同的type
变量。
接下来是创建createStore
函数,store会向外抛出getState
、dispatch
、subscribe
这三个接口,方便用户调用:
export default function createStore(reducer,initState){
return {
getState, // 获取最新state状态
dispatch, // 触发状态更新,接受参数对象{type}
subscribe, // 订阅视图更新函数
}
}
针对这个目标,我们就开始编写内部函数,首先是创建作用域内的state
,将初始值initState
赋值给它,接着getState
函数当然就是返回它了:
let state = initState;
const getState = ()=> state;
dispatch
方法需要接收一个带有type
属性的对象action
,方便reducer
函数调用:
const dispatch = (action)=>{
if(!isPlainObject(action)){
throw new Error('action 必须是纯对象')
}
if(typeof action.type == "undefined"){
throw new Error('必须定义action.type属性');
}
state = reducer(state, action);
return action
}
这里看源码的时候还会判断action
是否为纯对象,这里附上isPlainObject
的实现:
function isPlainObject(obj) {
if(typeof obj !=="object" || obj == null) return false;
let objPro = obj;
// 这里拿到obj最初始的__proto__
while (Object.getPrototypeOf(objPro)) {
objPro = Object.getPrototypeOf(objPro);
}
if (objPro === Object.getPrototypeOf(obj)) return true
}
接着是实现subscribe
函数,方便用户更新视图时调用:
let listeners=[];
const subscribe = (listener)=>{
listeners.push(listener);
let subscribed = true;
return ()=>{
if(!subscribed) return;
let index = listeners.indexOf(listener);
listeners.splice(index,1);
subscribed = false;
}
}
然后dispatch
函数里面,每次更新完state
再执行下listeners
里面存放的订阅方法:
const dispatch = (action)=>{
...
state = reducer(state, action);
// 更新完state,紧接着触发订阅方法
listeners.forEach(fn=>fn());
...
}
三个函数实现后,基本就完成了,不过我们还需要在函数内部执行下dispatch
方法,初始化用户传过来的state
值:
dispatch({ type: "@@@Redux/INIT"})
ok,这样这个简单版的createStore
函数就算完成了。
接下来,实现combineReducer
函数,这个函数是为了合并多个reducer
时使用的:
function combineReducer(reducers) {
let reducersKeys = Object.keys(reducers);
return function(state={},actions){
let combineState = {};
for(let i=0;i
combineReducer
的实现方式有很多,查阅资料的时候也看到有其它的版本,不过我们只有理解最终返回的值,是一个合并后的大reducer
,照着reducer
的参数和返回值来写就好理解了。
还有最后一个bindActionCreator
方法,目的是将actions
对象转换,把属性函数替换成能够执行的dispatch
方法:
function bindActionCreator(actions,dispatch) {
if(typeof actions === "function"){
return function(){
dispatch(actions.apply(this,arguments))
}
}
let actionCreator = {};
for(let name in actions){
actionCreator[name] = function(){
dispatch(actions[name].apply(this, arguments))
}
}
return actionCreator;
}
这样,一个简单的redux
就实现了,redux
的思想在很多场景中都可以使用到,不过我觉得关键还在于理解什么时候需要使用它,适用的场景中使用可以优化代码结构,提高可读性,不适用的场景反而会让应用变得复杂,不易理解。