手写简单Redux

简介: 手写简单Redux

前言

平时使用React做开发的同学对Redux都不会陌生,这是一个基于flux架构的十分优秀的状态管理库。这是Redux官方文档对它的描述。

Redux 是 JavaScript 状态容器,提供可预测化的状态管理。可以让你构建一致化的应用,运行于不同的环境(客户端、服务器、原生应用),并且易于测试。

我们在学习的过程中,在能够使用它完成一些日常开发之后,如果要比较深入的了解一个库,可以去看看它的源码,而Redux的源码其实并不长。进而,我们可以模仿手写一个简单的Redux,提升coding能力的同时也有助于消化优秀的第三方库中蕴含的思想。

基础用法和对源码的部分解读之前也有已经发过,感兴趣的小伙伴可以移步Redux学习笔记---简单使用以及源码阅读

接下来咱们就开始手写一个简单的Redux吧!

不可变数据

Redux中有一个概念是不可变数据,指的是我们改变store里面的数据时不可以直接去对它赋值,而是需要使用一个方法去修改。使用这种方式有以下好处:

  • 易于调试,当store发生变化时,可以记录前后变化的状态,很容易借此开发出类撤销、回退等功能
  • 易于推测,由于需要触发了action变化,store才会变化,通过触发的action,我们可以判断当前的状态是什么。

而配合起一些Immutable库,还可以提升工程的性能。仅需要判断当旧的state与新的state不是同一个对象时,才去更新组件。而不需要去做一些深层次的遍历判断每个值是否相等。

创建Store实例

先来一个创建store的方法,而且对于状态管理来说,应该保证使用的时候全局单一实例。故有以下思路:

  • 以闭包的形式创建state
  • 用方法来修改state
  • state改变后可通知外部钩子,故引入发布订阅机制,调用subscribe方法订阅回调函数(listener应为函数类型),再在state被改变的时候触发所订阅的回调函数,即changeState方法中遍历listeners中存储的函数。

如下封装代码

function createStore(initState) {
    let state = initState
    let listeners = []
    //订阅
    function subscribe(listener) {
        if(typeof listener == 'function'){
            listeners.push(listener)
        }
    }
    function changeState(newState) {
        listeners.forEach(func=>func())
        state = newState
    }
    function getState() {
        return state
    }
    return {
        subscribe,
        changeState,
        getState
    }
}

依据上述代码,可以如下创建一个store

const initState = {
    user:'David',
    age:18
}
//创建store
const store = createStore(initState)
//订阅回调函数
store.subscribe(() => {
    let state = store.getState();
    console.log(`${state.user.name}:${state.user.age}`);
});
store.subscribe(() => {
    let state = store.getState();
    console.log(state.counter.count);
});

store.changeState({
    ...store.getState(),
    user: {
        name: 'Jack',
        age: 19
    }
});

store.changeState({
    ...store.getState(),
    counter: {
        count: 20
    }
});

可以看到上述版本中有一个明显的缺陷:改变数据的时候是直接把一整个对象存进去的,这样对于我们开发的时候跟踪状态十分不便。所以接下来还是要引入reduceraction

reducer和action

先来回忆一下Redux中是如果用reducer和action配合,修改state的。主要分为以下几步:

  1. 首先我们会在actionTypes.js文件中写好所有action的类型。例如
// actionTypes.js
export const ADD_COUNT = 'ADD_COUNT'

2.然后在对应的action文件中引入该type,返回对应的type类型,如果此时有额外的数据加入,一般会加多一个payload字段。

// actions/Count.js
import {ADD_COUNT} from "../actionTypes"
export function addCount() {
    return {
         type: ADD_COUNT
    }
}

3.根据action返回的type,触发reducer对应的方法

// reducers/Count.js
import {ADD_COUNT} from "../actionTypes";
const initialState = {
    count: 0
}
export function Count(state = initialState, action) {
    const count = state.count
    switch (action.type) {
        case ADD_COUNT:
            return {
                    count: count + 1
            }
        default: return state
    }
}

4.编写好reduceraction之后,创建store

//store.js
import {createStore} from 'redux'
import {Count} from './reducers/Count'
const store = createStore(Count)
export default store

5.最后,如下使用:

import store from './store'
import { addCount } from './actions/Count'
store.dispatch(addCount())

由上述看来,我们是要去实现一个dispatch方法。根据action返回的type,去触发对应的reducer重新计算更新state。所以咱们的createStore方法可以改造如下:

function createStore(initState, reducer) {
    let state = initState
    let listeners = []
    function subscribe(listener) {
        listeners.push(listener)
    }
    function dispatch(action) {
        listeners.forEach(item => item())
        //主要是这一句,将粗暴的changeState改成对应的reducer去修改。
        state = reducer(state, action())
    }
    function getState() {
        return state
    }
    return {
        subscribe,
        dispatch,
        getState
    }
}

使用如下:

let initState = {
    count: 0
}
function reducer(state, action) {
    switch (action.type) {
        case 'ADD':
            return {
                ...state,
                count: state.count + 1
            }
            break;
        default:
            return state
            break;
    }
}
let store = createStore(initState, reducer)

const ADD = 'ADD'

function add(){
    return {
        type:ADD
    }
}
//使用它
store.dispatch(add())

中间件

由于业务的多样性,单纯的修改 dispatch 和 reducer 显然不能满足大家的需要,因此redux提供了自由组合的、可插拔的中间件机制。在日常开发中,我们常常串联不同的中间件来满足我们的开发需求。在redux进行数据流改变时,中间件可以截获action,并对它进行修改。

记录日志中间件

中间件的编写,主要是重写了dispatch方法,先用next缓存之前的dispatch方法,再处理完中间件逻辑之后,调用的next方法其实就是调用一开始的action

let store = createStore(initState, reducer)
let next = store.dispatch
store.dispatch = action => {
    //打印log
    console.log(`action:${action}`)
    console.log(`state:${store.getState()}`)
    //调用真正触发的action
    next(action)
    console.log(`next state : ${store.getState()}`)
}

记录异常中间件

了解了中间件的编码逻辑之后,我们很容易再开发出一个记录异常的中间件,如下:

let store = createStore(initState, reducer)
let next = store.dispatch
store.dispatch = action => {
    try{
        next(action)
    }catch(e){
        throw new Error(e)
    }
}

多中间件组合

使用Redux中,我们常常使用多个中间件串联,有了上述的经验之后,我们很轻松的可以如下组合起来。

let store = createStore(initState, reducer)
let next = store.dispatch
const loggerMiddleware = function (next) {
    return function (action) {
        console.log('this state', store.getState());
        console.log('action', action);
        next(action);
        console.log('next state', store.getState());
    }
}
const exceptionMiddleware = function (next) {
    return function (action) {
        try {
            next(action);
        } catch (err) {
            console.error('错误报告: ', err)
        }
    }
}
//重写dispatch方法,依次调用完中间件逻辑后,最后再触发真正的action
store.dispatch = exceptionMiddleware(loggerMiddleware(next))

const ADD = 'ADD'

function add(){
    return {
        type:ADD
    }
}

store.dispatch(add())
console.log(store.getState())

最后贴一张图,方便大伙儿理解~

最后

行文至此,感谢阅读,如果您喜欢的话,可以帮忙点个like哟~

相关文章
|
机器学习/深度学习 算法 安全
URL过滤算法:上网行为管理软件中的重要利器
URL过滤算法在上网行为管理软件中可谓是大显身手的"网络管家",它的作用绝对不容小觑。就好像是一个智能导航员,可以帮助我们精准掌控网络大海中的航向,保障我们的上网安全和舒适。接下来就让我们一起来看看,这位"网络管家"都有哪些神奇的功能吧!
236 0
|
存储 弹性计算 固态存储
*PolarDB-PG开源版本 基于ECS+ESSD云盘共享存储的部署测评**
PolarDB-PG在阿里云ECS与ESSD云盘的组合下展现优秀性能,简化部署流程,提供高并发写入时低延迟稳定性。ESSD的高性能IOPS和读取速度优化了数据库表现,只读节点实现近乎实时数据访问。分布式部署保证高可用性,即使面对故障也能快速切换。尽管ESSD初期成本较高,但长期看能降低总体拥有成本,尤其适合高并发、大数据量场景。此解决方案平衡了性能、可用性和成本,是企业级应用的理想选择。
|
存储 安全 Java
JAVA8实战 - 日期API
JAVA8实战 - 日期API
187 0
|
设计模式 缓存 算法
阿里逆天级调优方案,内部这套Java性能调优实战宝典,堪称教科书
随着互联网的发展,高可靠、高并发以及降本增效,已成为各大公司面临的现实挑战,性能优化需求愈发迫切,大到分布式系统,小到代码块的算法优化,都已经成为你日常工作中必须要面对的事情。对于开发者而言,性能优化也从加分项变为一个热门技能,缺乏相关知识将很难在面试或工作中脱颖而出
阿里逆天级调优方案,内部这套Java性能调优实战宝典,堪称教科书
|
算法 BI 数据库
[软件工程导论(第六版)]第5章 总体设计(课后习题详解)
[软件工程导论(第六版)]第5章 总体设计(课后习题详解)
循环结构流程控制(if,else,switch,for,do...while等等)(二)
循环结构流程控制(if,else,switch,for,do...while等等)(二)
155 0
|
C# 数据库
C# Button/SimpleButton (按钮) 动态赋值
C# Button/SimpleButton (按钮) 动态赋值
301 0
C# Button/SimpleButton (按钮) 动态赋值
|
数据采集 自然语言处理 算法
①数据预处理之数据清理,数据集成,数据规约,数据变化和离散化
数据预处理之数据清理,数据集成,数据规约,数据变化和离散化
618 0
①数据预处理之数据清理,数据集成,数据规约,数据变化和离散化
|
存储 数据可视化 算法
MFC模拟 Windows 文件可视化系统(上)
MFC模拟 Windows 文件可视化系统
290 0
MFC模拟 Windows 文件可视化系统(上)
逆天的Excel快速填充,简直就是处理报表的神器!
逆天的Excel快速填充,简直就是处理报表的神器!
逆天的Excel快速填充,简直就是处理报表的神器!