手写简单Redux

    科技2022-07-10  111

    前言

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

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

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

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

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

    不可变数据

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

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

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

    创建Store实例

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

    以闭包的形式创建state用方法来修改statestate改变后可通知外部钩子,故引入发布订阅机制,调用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 } });

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

    reducer和action

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

    首先我们会在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.编写好reducer和action之后,创建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哟~

    欢迎转载,但要注明出处哟~​

    该文章首发自【全栈web之路】,web开发之路,诚邀您携手同行。

    关注公众号回复关键字【前端】,即可领取笔者备战秋招笔记

    Processed: 0.011, SQL: 8