目录

看完这篇文章,你也可以写出Redux

最近在网上看了很多关于Redux的文章,感觉很奇怪哈,为什么这么简单的东西会被很多人讲的那么复杂。官方对Redux的描述有以下几个特点:可预测可调试集中管理灵活单一数据流等等。这都能理解,毕竟营销嘛,看着就是一乐,谁开发了一个库,都会去有的没的说一些伪优点。在经历了长期的正反思考后,我得出了以下几个结论,希望能够对你有用:

  • 如果一个博主不能将复杂的理论简单化,那他一定没懂这个理论。因为再复杂的理论也是由单个知识点串起来的,所以一定不存在永远都搞不懂的理论,这只是 精力问题 + 学习方式问题。
  • 前端框架层出不穷,像redux这种第三方库就更不用提了,更新的频率只会越来越快。越是这种时候,就越考验一个人的洞察能力。就比如 状态管理,你觉着有必要使用第三方库吗?第三方库的衍生品你觉着有必要使用吗?所以在这种大环境下,对技术leader的要求就比较高,合格的技术leader更是一票难求。
  • 多读源码,多写code。眼高不手低了,洞察问题的能力自然就上去了。

接下来,我们来看看如何写出一个状态管理。

需求

一想到状态管理,其实需求就是 有一个地方,可以保存我们的状态,在有能力改变状态的同时,我们还要感知到它的变化

这不就是所有的状态管理的库都会去做的事情嘛,剩下的就只是实现的方式不一样,扩展的能力不一样而已。

如何存储状态

2个思路,分别如下:

  • 维护全局变量。
  • 闭包。

在这里我们使用闭包的形式来实现:

javascript 复制代码
function CustomCreateStore(){
    let state = undefined;
    let getState = () => {
        return state;
    }
    return {
        getState
    }
}

export CustomCreateStore;

有了上面的方法,我们就可以使用下面的代码来访问state了。

javascript 复制代码
CustomCreateStore().getState();

规范state

个人认为这一步是很有必要,不能用户放进来啥我们就存啥,想要用我的库,就必须要遵守我的原则。我们希望用户可以用object的形式去保存它想要用到的变量。最简单的方式就是让用户把状态传进来。

所以改造如下:

javascript 复制代码
function CustomCreateStore(obj){
    if( Object.prototype.toString.call(obj) !== '[object Object]' ){
        throw new Error('CustomCreateStore的参数必须是个对象');
    }
    let state = obj;
    let getState = () => {
        return state;
    }
    return {
        getState
    }
}

我们现在可以随心所欲的定义initState了。

javascript 复制代码
CustomCreateStore({count: 1}).getState();

连接state与reducer

最开始讲需求的时候,我们就说过,一个状态管理库要包含2个能力,分别是改变状态的能力监听状态改变的能力

想要实现改变状态的能力,我们可以做一个桥梁,这个桥梁的作用是连接着用户的initState、保存用户改变state的方法。具体思路如下:

如何实现用户派发一个通知,然后状态管理库就知道了用户派发通知了呢?

其实很简单,我们只需要给用户暴露一个方法,只要用户调用了这个方法,状态管理库就认为你要改变状态了。

紧接着,我们如何根据通知的类型,去执行相应的更新呢?

我觉着用户实现这一步就好了。也就是库收到了这个通知,然后去执行这个通知对应的方法,至于通知是什么,通知对应的方法是什么,统统不管,我只负责收到和触发。如下:

jsx 复制代码
let store = CustomCreateStore({count: 1});

// 通知对应的方法
function executeClickAdd(){
    return ...
}

// 点击按钮触发
const clickAddButton = () => {
    // 派发一个通知
    store.dispatch({
        type: 'click-add'
    })
}

<button onclick={ clickAddButton }> +1 </button>

那问题来了,状态管理库如何将 通知click-addexecuteClickAdd函数联系起来?我们可以再随意一点,我哪知道你是哪个通知对应哪个方法,用户去自己想办法吧。

此时用户愤怒到了极点,但又觉得它说的有道理,所以用户维护了一个配置:

javascript 复制代码
function ActionToExecuteOfConfig(state, action){
    switch(action.type){
        case 'click-add':
            return state + 1;
        default:
            return state;
    }
}

于是用户将上述方案交给了状态管理,并且对狠狠的说了一句,你必须要将当前state返回给我,这样我才能依据当前的state去得到下一步的state。状态管理库看到这样的配置就乐了,正符合我意,于是连夜改了createStore方法。

javascript 复制代码
function CustomCreateStore(obj, fn){
    if( Object.prototype.toString.call(obj) !== '[object Object]' ){
        throw new Error('CustomCreateStore的参数必须是个对象');
    }
    let state = obj;
    let getState = () => {
        return state;
    }
    
    // 新增++++++
    let ActionToExecuteOfConfig = fn;
    let dispatch = (action) => {
        state = ActionToExecuteOfConfig(state, action);
    }
    
    return {
        getState,
        dispatch
    }
}

用户看到这样的方案后,也连夜修改了业务代码如下:

jsx 复制代码
function ActionToExecuteOfConfig(state, action){
    switch(action.type){
        case 'click-add':
            return state + 1;
        default:
            return state;
    }
}


let store = CustomCreateStore({count: 1}, ActionToExecuteOfConfig);
let curData = store.getState();

// 点击按钮触发
const clickAddButton = () => {
    // 派发一个通知
    store.dispatch({
        type: 'click-add'
    })
    curData = store.getState();
}


<button onclick={ clickAddButton }> +1 </button>
<div>当前的值是:{ curData.count }</div>

点击button按钮后,页面上的数据也跟着+1了,用户露出了慈祥的笑容。

监听状态的改变

有了上面的基础,相信这个肯定难不住你们,subscribe的时候将callback push到listener队列里,最后在dispatch的末尾将listener里的callback全部依次执行即可。

jsx 复制代码
function CustomCreateStore(obj, fn){
    if( Object.prototype.toString.call(obj) !== '[object Object]' ){
        throw new Error('CustomCreateStore的参数必须是个对象');
    }
    let state = obj;
    let listener = [];
    
    let getState = () => {
        return state;
    }
    
    // 新增++++++
    let ActionToExecuteOfConfig = fn;
    let dispatch = (action) => {
        state = ActionToExecuteOfConfig(state, action);
        // 执行监听函数
        for (let index = 0; index < listener.length; index++){
            listener[index](state);
        }
    }
    
    // 新增++++++
    let subscribe = (callback) => {
        listener.push(callback);
        return function unsubscribe(){
              const index = nextListeners.indexOf(listener)
              nextListeners.splice(index, 1)
        }
    }
    
    return {
        getState,
        dispatch,
        subscribe
    }
}

用户的代码修改如下:

jsx 复制代码
// 其余代码不变

let store = CustomCreateStore({count: 1}, ActionToExecuteOfConfig);

store.subscribe(
    () => {
        console.log('store发生变化了');
    }
);

// 其余代码不变

此时用户再点击button +1时,发现我们注册的listener生效了。

改造state

随着应用的越来越大,做这个项目的人越来越多,因为所有的case都写在了一个ActionToExecuteOfConfig函数里,这就避免不里冲突了。你影响我,我影响你的。所以用户又给状态管理提了一个需求,你要给我想办法,我们不想所有的case都写在一个函数里,限你下班之前给我这个需求写出来,要不然我就不用你了。

于是状态管理就想了一个招,我给每个initState都动态分配一个名称name吧,在createStore的时候,我支持你传入一个对象,但是这个对象我有一个条件,value必须是用户处理action的函数。如果你想要访问自己注册的initState,就必须通过store.getState().name来访问,而这个name的值就是你的处理action得函数名。具体做法如下:

javascript 复制代码
// 新增combineReducers函数 ++++++++++++++++++++++
function combineReducers(reducers) {
  const reducerKeys = Object.keys(reducers)
  const finalReducers = {}
  for (let i = 0; i < reducerKeys.length; i++) {
    const key = reducerKeys[i];
    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }
  const finalReducerKeys = Object.keys(finalReducers);

  return function combination(state = {}, action) {
    let hasChanged = false;
    const nextState = {};
    for (let i = 0; i < finalReducerKeys.length; i++) {
      const key = finalReducerKeys[i];
      const reducer = finalReducers[key];
      const previousStateForKey = state[key];
      const nextStateForKey = reducer(previousStateForKey, action);
      nextState[key] = nextStateForKey;
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
    }
    hasChanged = hasChanged || finalReducerKeys.length !== Object.keys(state).length
    return hasChanged ? nextState : state
  }
}

// CustomCreateStore函数修改如下:
function CustomCreateStore(fn){
    if( Object.prototype.toString.call(obj) !== '[object Object]' ){
        throw new Error('CustomCreateStore的参数必须是个对象');
    }
    let state = obj;
    let listener = [];
    let ActionToExecuteOfConfig = fn;
    
    // 获取initState
    let getState = () => {
        return state;
    }

    // 派发通知
    let dispatch = (action) => {
        state = ActionToExecuteOfConfig(state, action);
        // 执行监听函数
        for (let index = 0; index < listener.length; index++){
            listener[index](state);
        }
    }
    
    // store的监听函数
    let subscribe = (callback) => {
        listener.push(callback);
        return function unsubscribe(){
              const index = nextListeners.indexOf(listener)
              nextListeners.splice(index, 1)
        }
    }
    
    // 初始化状态
    // 此时会执行combination函数,这个函数主要就是将所有的key对应的函数都执行一遍,最后再将key,key对应的value全部都拼到一个新的对象里
    dispatch({ type: 'initState-996' })
    
    return {
        getState,
        dispatch,
        subscribe
    }
}

用户修改业务代码如下:

javascript 复制代码
let store  = CustomCreateStore(
    combineReducers({
        count: () => {},
        number: () => {}
    })
);

// 访问count下的initState
let count = store.getState().count.xxx;

// 访问number下的initState
let number = store.getState().number.xxx;

最后,经过这么一顿折腾,用户露出了满意的笑容。

最后

好啦,本篇文章到这里也就结束啦,希望我的文章对您有帮助,2023年12月16日,23点39分,我们下期再见啦,拜拜~~

本文是转载文章,点击查看原文
如有侵权,请联系 xyy@jishuzhan.net 删除
相关推荐
敲代码的彭于晏21 分钟前
前端上传与下载基础:Blob、File与ArrayBuffer详解
前端
緑水長流26 分钟前
什么是Promise?什么是async和await?
前端·javascript·vue.js
Mintopia27 分钟前
Three.js 相机(Camera)的使用详解
前端·javascript·three.js
wordbaby30 分钟前
PC 屏幕自适应的流行方案解析
前端
Mintopia30 分钟前
Node.js 中path模块的深度解析与实战应用
前端·javascript·node.js
程序员小续31 分钟前
git rebase 和git merge使用及区别
前端·git·后端
静待一世花开32 分钟前
React基础学习
前端
华科云商xiao徐33 分钟前
使用Scrapy编写图像下载程序示例
前端
WillaWang33 分钟前
html结构中图片下方的间隙
前端
阿笑带你学前端34 分钟前
从0到1:我用Flutter造了个全平台IPTV神器,从此看直播不再"精神分裂"!
前端·flutter