共计 2840 个字符,预计需要花费 8 分钟才能阅读完成。
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<reducersKeys.length;i++){let key = reducersKeys[i]; // 拿到每个 reducer 的 key 值 | |
let reducer = reducers[key]; // 拿到每个 reducer 办法 | |
combineState[key] = reducer(state[key], actions); | |
} | |
return combineState; | |
} | |
} |
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
的思维在很多场景中都能够应用到,不过我感觉要害还在于了解什么时候须要应用它,实用的场景中应用能够优化代码构造,进步可读性,不实用的场景反而会让利用变得复杂,不易了解。