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

相关推荐
前端青山5 小时前
Node.js-增强 API 安全性和性能优化
开发语言·前端·javascript·性能优化·前端框架·node.js
从兄6 小时前
vue 使用docx-preview 预览替换文档内的特定变量
javascript·vue.js·ecmascript
清灵xmf7 小时前
在 Vue 中实现与优化轮询技术
前端·javascript·vue·轮询
薛一半8 小时前
PC端查看历史消息,鼠标向上滚动加载数据时页面停留在上次查看的位置
前端·javascript·vue.js
过期的H2O28 小时前
【H2O2|全栈】JS进阶知识(四)Ajax
开发语言·javascript·ajax
MarcoPage8 小时前
第十九课 Vue组件中的方法
前端·javascript·vue.js
你好龙卷风!!!9 小时前
vue3 怎么判断数据列是否包某一列名
前端·javascript·vue.js
shenweihong11 小时前
javascript实现md5算法(支持微信小程序),可分多次计算
javascript·算法·微信小程序
巧克力小猫猿11 小时前
基于ant组件库挑选框组件-封装滚动刷新的分页挑选框
前端·javascript·vue.js
嚣张农民11 小时前
一文简单看懂Promise实现原理
前端·javascript·面试