在我们学会了如何使用Redux之后,就会想着我们可不可以根据其原理自己实现一个简单的redux,redux的实现是基于发布订阅模式的设计思路,如果对发布订阅模式不熟悉的,可以查阅我的另一篇文章详解"观察者"模式和"发布订阅"模式。下面我们就逐步实现一个简单的Redux
Redux核心方法实现
我们知道redux的核心方法有三个createStore,combineReducers,bindActionCreators
,下面我们依次实现
实现createStore方法
javascript
export const createStore = (reducer) => {
let state = undefined
let listeners = []
const getState = () => state
const dispatch = (action) => {
state = reducer(state, action)
listeners.forEach(listener => listener())
}
const subscribe = (listener) => {
listeners.push(listener)
return () => {
listeners = listeners.filter(item => item !== listener)
}
}
dispatch({ type: 'init' })
return { getState, dispatch, subscribe }
}
方法解析:
- 首先我们得
createStore
方法接收一个函数reducer,将来触发dispatch时需要调用来重新计算并返回新的state - 定义
state
用于存放所有得状态 - 定义
listeners
用于存放所有得订阅者回调函数,当触发dispatch
时,我们需要执行所有订阅者得回调函数来通知订阅者获取最新得state
- 定义
dispatch
函数,dispatch
函数接收一个参数action,这里我们就不做类型判断了,这里的action应该是一个包含type得对象。执行dispatch时我们需要执行reducer函数,并传入当前得state和action,通过reducer函数得计算获得最新得state并赋值给state。获得最新得state后需要循环调用订阅者数组listeners
中得回调函数通知所有订阅者。 - 定义
subscribe
订阅函数,订阅函数接收一个参数为订阅者的回调函数。当订阅者调用订阅函数并传入回调函数后加入listeners
数组,并返回一个取消订阅的函数。 - 执行一次dispatch,来获取初始化的state值
- 将
getState, dispatch, subscribe
三个store中的核心函数返回出去
实现combineReducers方法
javascript
export const combineReducers = (reducers) => {
return (state = {}, action) => {
const newState = {}
for (let key in reducers) {
newState[key] = reducers[key](state[key], action)
}
return newState
}
}
方法解析:
combineReducers
函数接收一个reducers
对象,reducers
对象中包含所有的reducer函数的键值对,其中对象的key作为返回的总的state对象的key,state的value是reducer函数计算后返回的值。combineReducers
返回一个总的reducer函数,该函数接收两个参数,第一个参数是当前状态,第二个参数是执行dispatch时传入的action。我们通过循环调用reducers中的reducer来获取每个reducer的最新状态,并将所有状态组合返回出去。
实现bindActionCreators
方法
javascript
export const bindActionCreators = (actionCreators, dispatch) => {
const boundActionCreators = {}
for (let key in actionCreators) {
boundActionCreators[key] = (...args) => dispatch(actionCreators[key](...args))
}
return boundActionCreators
}
方法解析:
bindActionCreators
方法接收两个参数,第一个参数是actionCreator集合对象,其中key是actionCreator的方法名,value是actionCreator函数- 我们循环
actionCreators
对象,并生成一个新的对象,新对象的key就是actionCreators中的key,而value变成一个dispatch函数,并且dispatch函数中获取到的action是对应actionCreator返回的action对象
测试自己实现的redux
我们创建一个简单的文件夹,里面包含三个文件demo.js,index.html,myRedux.js
,文件夹目录如下
各个文件下的代码如下
index.html
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Redux Study Page</title>
<script type="module" src="./demo.js"></script>
</head>
<body>
<P>This is Redux Study Page</P>
<span class="show-count"></span>
<br>
<span class="show-user-info"></span>
<br>
<button class="add-count">AddCount</button>
<button class="minus-count">minusCount</button>
<button class="change-user-name">ChangeUserName</button>
<button class="change-user-age">ChangeUserAge</button>
</body>
</html>
myRedux.js
javascript
export const combineReducers = (reducers) => {
return (state = {}, action) => {
const newState = {}
for (let key in reducers) {
newState[key] = reducers[key](state[key], action)
}
return newState
}
}
export const createStore = (reducer) => {
let state = undefined
let listeners = []
const getState = () => state
const dispatch = (action) => {
state = reducer(state, action)
listeners.forEach(listener => listener())
}
const subscribe = (listener) => {
listeners.push(listener)
return () => {
listeners = listeners.filter(item => item !== listener)
}
}
dispatch({ type: 'init' })
return { getState, dispatch, subscribe }
}
export const bindActionCreators = (actionCreators, dispatch) => {
const boundActionCreators = {}
for (let key in actionCreators) {
boundActionCreators[key] = (...args) => dispatch(actionCreators[key](...args))
}
return boundActionCreators
}
demo.js
javascript
import { combineReducers, createStore, bindActionCreators } from './myRedux.js'
const showCount = document.getElementsByClassName('show-count')
const addCount = document.getElementsByClassName('add-count')
const minusCount = document.getElementsByClassName('minus-count')
const showUserInfo = document.getElementsByClassName('show-user-info')
const changeUserName = document.getElementsByClassName('change-user-name')
const changeUserAge = document.getElementsByClassName('change-user-age')
const incrementCount = (payload) => {
return { type: 'incrementCount', payload }
}
const decrementCount = (payload) => {
return { type: 'decrementCount', payload }
}
const countReducer = (state = 0, action) => {
const { type, payload } = action
switch (type) {
case 'incrementCount':
return state + payload
case 'decrementCount':
return state - payload
default:
return state
}
}
const changeUserNameAction = (payload) => {
return { type: 'changeUserName', payload }
}
const changeUserAgeAction = (payload) => {
return { type: 'changeUserAge', payload }
}
const userInfoReducer = (state = {name: '张三', age: 18}, action) => {
const { type, payload } = action
switch (type) {
case 'changeUserName':
return {...state, name: payload}
case 'changeUserAge':
return {...state, age: payload}
default:
return state
}
}
const rootReducer = combineReducers({
count: countReducer,
userInfo: userInfoReducer,
})
const store = createStore(rootReducer)
const { dispatch, getState, subscribe} = store
const boundActionsCreators = bindActionCreators({
incrementCount,
decrementCount,
changeUserNameAction,
changeUserAgeAction
}, dispatch)
const assignCount = () => {
showCount[0].innerHTML = getState().count
}
const assignUserInfo = () => {
const userInfo = getState().userInfo
showUserInfo[0].innerHTML = `姓名:${userInfo.name},年龄:${userInfo.age}`
}
assignCount()
assignUserInfo()
subscribe(assignCount)
subscribe(assignUserInfo)
addCount[0].addEventListener('click', () => {
boundActionsCreators.incrementCount(3)
})
minusCount[0].addEventListener('click', () => {
boundActionsCreators.decrementCount(2)
})
changeUserName[0].addEventListener('click', () => {
boundActionsCreators.changeUserNameAction('李四')
})
changeUserAge[0].addEventListener('click', () => {
boundActionsCreators.changeUserAgeAction(20)
})
我们打开html页面后得到如下页面
注意:打开html页面需要在服务中打开,在vscode中可以使用Live Server插件快速打开。
当点击各个按钮时,就会响应式的修改页面,至此我们就实现了一个简单的自己的redux