自己实现一个简单的Redux

在我们学会了如何使用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 }
}

方法解析:

  1. 首先我们得createStore方法接收一个函数reducer,将来触发dispatch时需要调用来重新计算并返回新的state
  2. 定义state用于存放所有得状态
  3. 定义listeners用于存放所有得订阅者回调函数,当触发dispatch时,我们需要执行所有订阅者得回调函数来通知订阅者获取最新得state
  4. 定义dispatch函数,dispatch函数接收一个参数action,这里我们就不做类型判断了,这里的action应该是一个包含type得对象。执行dispatch时我们需要执行reducer函数,并传入当前得state和action,通过reducer函数得计算获得最新得state并赋值给state。获得最新得state后需要循环调用订阅者数组listeners中得回调函数通知所有订阅者。
  5. 定义subscribe订阅函数,订阅函数接收一个参数为订阅者的回调函数。当订阅者调用订阅函数并传入回调函数后加入listeners数组,并返回一个取消订阅的函数。
  6. 执行一次dispatch,来获取初始化的state值
  7. 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
    }
}

方法解析:

  1. combineReducers函数接收一个reducers对象,reducers对象中包含所有的reducer函数的键值对,其中对象的key作为返回的总的state对象的key,state的value是reducer函数计算后返回的值。
  2. 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
}

方法解析:

  1. bindActionCreators方法接收两个参数,第一个参数是actionCreator集合对象,其中key是actionCreator的方法名,value是actionCreator函数
  2. 我们循环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

相关推荐
来自星星的坤1 小时前
【Vue 3 + Vue Router 4】如何正确重置路由实例(resetRouter)——避免“VueRouter is not defined”错误
前端·javascript·vue.js
香蕉可乐荷包蛋5 小时前
浅入ES5、ES6(ES2015)、ES2023(ES14)版本对比,及使用建议---ES6就够用(个人觉得)
前端·javascript·es6
未来之窗软件服务6 小时前
资源管理器必要性———仙盟创梦IDE
前端·javascript·ide·仙盟创梦ide
西哥写代码7 小时前
基于cornerstone3D的dicom影像浏览器 第十八章 自定义序列自动播放条
前端·javascript·vue
清风细雨_林木木7 小时前
Vue 中生成源码映射文件,配置 map
前端·javascript·vue.js
雪芽蓝域zzs8 小时前
JavaScript splice() 方法
开发语言·javascript·ecmascript
森叶9 小时前
Electron 主进程中使用Worker来创建不同间隔的定时器实现过程
前端·javascript·electron
霸王蟹9 小时前
React 19 中的useRef得到了进一步加强。
前端·javascript·笔记·学习·react.js·ts
霸王蟹9 小时前
React 19版本refs也支持清理函数了。
前端·javascript·笔记·react.js·前端框架·ts
codelxy9 小时前
vue引用cesium,解决“Not allowed to load local resource”报错
javascript·vue.js