本文从零实现一个简单的 redux
,主要内容在于redux 的设计思路及实现原理
redux 是一个状态管理器,里面存放着数据,比如我们创建 store.js,在里面我们存放着这些数据,只需要在任何地方引用这个文件就可以拿到对应的状态值:
let state = {
count: 1
}
console.log(state.count)
state.count = 2
复制代码现在我们实现了状态(计数)的修改和使用了!当然上面的有一个很明显的问题:这个状态管理器只能管理 count,修改 count 之后,使用 count 的地方不能收到通知。
实现 subscribe
我们可以使用发布-订阅模式来解决这个问题。我们用个函数封装一下 redux
function createStore(initState) {
let state = initState
let listeners = []
/* 订阅函数 */
function subscribe(listener) {
listeners.push(listener)
}
function changeState(newState) {
state = newState
/* 执行通知 */
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
}
function getState() {
return state
}
return { subscribe, changeState, getState }
}
这里我们完成了一个简单的状态管理器。state 的数据可以自由的定, 我们修改状态,在订阅的地方监听变化,可以实现监听。
let initState = {
count: 1,
detail: {
age: 24
}
}
let store = createStore(initState)
store.subscribe(() => {
let state = store.getState()
console.log('t1: ', state)
})
store.changeState({ ...store.getState(), count: store.getState().count + 1 })
// t1: { count: 2, detail: { age: 24 } }
这里需要理解的是 createStore
,提供了 changeState
,getState
,subscribe
三个能力。
在上面的函数中,我们调用 store.changeState
可以改变 state
的值,这样就存在很大的弊端了。比如 store.changeState({})
我们一不小心就会把 store 的数据清空,或者误修改了其他组件的数据,那显然不太安全,出错了也很难排查,因此
我们需要有条件地操作 store,防止使用者直接修改 store 的数据。
因此,我们需要一个约束来修改 state
的值,而不允许意外的情况来将 state
的值清空或者误操作。分两步来解决这个问题:
-
1. dispatch: 制定一个 state 修改计划,告诉 store,我的修改计划是什么。
-
2. reducer: 修改 store.changeState 方法,告诉它修改 state 的时候,按照我们的计划修改。
我们将 store.changeState
改写为 store.dispatch
, 在函数中传递多一个 reducer
函数来约束状态值的修改。
实现 reducer
reducer
是一个纯函数,接受一个 state
, 返回新的 state
。
function createStore(reducer, initState) {
let state = initState
let listeners = []
/* 订阅函数 */
function subscribe(listener) {
listeners.push(listener)
}
/* state 值的修改 */
function dispatch(action) {
state = reducer(state, action)
/* 执行通知 */
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
}
function getState() {
return state
}
return { subscribe, dispatch, getState }
}
我们来尝试使用 dispatch
和 reducer
来实现自增和自减
let initState = {
count: 1,
detail: {
age: 24
}
}
function reducer(state, action) {
switch (action.type) {
case 'INCREMENT':
return { ...state, count: state.count + 1 }
case 'DECREMENT':
return { ...state, count: state.count - 1 }
default:
return state
}
}
let store = createStore(reducer, initState)
store.subscribe(() => {
let state = store.getState()
console.log('t1: ', state)
})
store.dispatch({ type: 'INCREMENT' }) // 自增
store.dispatch({ type: 'DECREMENT' }) // 自减
store.dispatch({ count: 2 }) // 计划外:不生效
我们知道 reducer
是一个约束函数,接收老的 state
,按计划返回新的 state
。那我们项目中,有大量的 state
,每个 state
都需要约束函数,如果所有的计划写在一个 reducer
函数里面,会导致 reducer
函数及其庞大复杂。所以我们需要将封装 combineReducers
来优化 reducer
函数。
实现 combineReducers
粒子化 reducer
-
传入对象参数,
key
值即为state
状态树的key
值,value
为对应的reducer
函数。 -
遍历对象参数,执行每一个
reducer
函数,传入state[key]
, 函数获得每个reducer
最新的state
值。 -
耦合
state
的值, 并返回。返回合并后的新的reducer
函数。
function combineReducers(reducers) {
const reducerKeys = Object.keys(reducers)
/*返回合并后的新的reducer函数*/
return function combination(state = {}, action) {
/*生成的新的state*/
const nextState = {}
/*遍历执行所有的reducers,整合成为一个新的state*/
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i]
const reducer = reducers[key]
/*之前的 key 的 state*/
const previousStateForKey = state[key]
/*执行 分 reducer,获得新的state*/
const nextStateForKey = reducer(previousStateForKey, action)
nextState[key] = nextStateForKey
}
return nextState
}
}
//使用 combineReducers:
let state = {
counter: { count: 0 },
detail: { age: 24 }
}
function counterReducer(state, action) {
switch (action.type) {
case 'INCREMENT':
return { count: state.count + 1 }
default:
return state
}
}
function detailReducer(state, action) {
switch (action.type) {
case 'INCREMENT-AGE':
return { age: state.age + 1 }
default:
return state
}
}
const reducers = combineReducers({
counter: counterReducer,
info: detailReducer
})
let store = createStore(reducers, initState)
store.subscribe(() => {
let state = store.getState()
console.log('t1: ', state)
})
store.dispatch({ type: 'INCREMENT' })
store.dispatch({ type: 'INCREMENT-AGE' })
我们把 reducer
按组件维度拆分了,通过 combineReducers
合并了起来。 但是还有个问题, state
我们还是写在一起的,这样会造成 state
树很庞大,不直观,很难维护。我们需要拆分,一个 state
,一个 reducer
写一块。
粒子化 state
改写 combineReducers
函数,在 createStore
函数中执行 dispatch({ type: Symbol() })
function createStore(reducer, initState) {
let state = initState
let listeners = []
/* 订阅函数 */
function subscribe(listener) {
listeners.push(listener)
}
function dispatch(action) {
state = reducer(state, action)
/* 执行通知 */
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
}
/* 注意!!!只修改了这里,用一个不匹配任何计划的 type,来获取初始值 */
dispatch({ type: Symbol() })
function getState() {
return state
}
return { subscribe, dispatch, getState }
}
//将 state 分别传入各自的 reducer:
function counterReducer(state = { count: 1 }, action) {
//...
}
function detailReducer(state = { age: 24 }, action) {
//...
}
// 合并 reducer
const reducers = combineReducers({
counter: counterReducer,
info: infoReducer
})
// 移除 initState
let store = createStore(reducers)
console.log(store.getState()) // { counter: { count: 1 }, detail: { age: 24 } }
createStore
的时候,用一个不匹配任何type
的action
,来触发state = reducer(state, action)
- 因为
action.type
不匹配,每个子reducer
都会进到default
项,返回自己初始化的state
,这样就获得了初始化的state
树了。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!