自己实现一个简单的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

相关推荐
free354 小时前
作用域链与 Environment:变量查找、遮蔽和赋值怎么实现
javascript
YHL4 小时前
🤖 Agent 智能体开发实战 · 第一课:Tool Use —— 让大模型自动干活
前端·javascript·人工智能
HUMHSX6 小时前
Vue 组件通信核心知识点梳理
javascript·vue.js·ecmascript
风止何安啊6 小时前
🚦 前端并发请求 “交通管制”:别让你的接口堵成早高峰
前端·javascript·面试
熊猫钓鱼>_>6 小时前
2026 前端的三场底层革命:当 Vue、React Native 和 Three.js 同时去掉中间层
前端·javascript·vue.js·react native·架构·webgl·three.js
名字还没想好☜6 小时前
React useEffect 依赖数组踩坑:闭包陷阱与清理函数
前端·javascript·react.js·react·useeffect
柒和远方7 小时前
LeetCode 4. 寻找两个正序数组的中位数 —— 二分划分的艺术
javascript·python·算法
默_笙7 小时前
😢 我被 JS 的 this 整不会了:同一个函数,换个“地区”调用,结果就不一样
javascript
Tom@敲代码8 小时前
js学习笔记-01
javascript·笔记·学习
木西8 小时前
告别机械重复:使用 Node.js + Playwright 构建 24 小时全自动测试网领水脚本
前端·javascript·node.js