前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >从 0 实现一个 mini redux

从 0 实现一个 mini redux

原创
作者头像
小鑫
发布2022-05-11 16:30:51
4460
发布2022-05-11 16:30:51
举报
文章被收录于专栏:小鑫の随笔小鑫の随笔

前言

本文从 redux 原理出发,一步步实现一个自己的 mini-redux,主要目的是了解其内部之间的各种关系,所以本篇不会讲解太多关于 redux 的用法

redux 是什么

redux 是一种可预测的状态管理库,在 react 中,它解决的是多个组件之间的通信问题

在没有使用 redux 的情况下,如果两个组件(非父子关系)之间需要通信的话,可能需要多个中间组件来为他们进行消息传递,这样既浪费了资源,代码也会变得更复杂

redux 提出了单一数据源 store 来存储状态数据,所有的组件都可以通过 action 来修改 store,也可以从 store 中获取最新状态。使用了 redux 就可以完美解决组件之间的通信问题

redux 的设计原则

redux 的三大设计原则:

  • 单一数据源
  • 状态是只读的
  • 使用纯函数编写 reducer

单一数据源

意思是整个 react 项目里的 state 都存放在一起,单一数据源主要是为了解决状态一致性的问题

在传统的 MVC 架构中,需要创建无数个 Model,而 Model 之间可以互相监听、触发事件甚至循环或嵌套触发事件,这些在 redux 中都是不允许的

在 redux 的思想里,一个应用永远只有唯一的数据源,这个设计也是有一些好处的,对于开发者来说,它可以更容易调试和观察状态的变化

也不用担心数据源对象过于庞大的问题,redux 提供的 combineReducers 函数可以解决这个问题

状态是只读的

这里说的状态,指的是上面说的存放在 store 中的状态数据,你不能直接对其中的状态数据进行改动只能间接的通过发送 action 来改动状态。间接的改动状态,这是一个很关键的设计,也是单向数据流的重点之一,对于每个动作的发生,最终会影响到什么状态上的改动,一个接一个的执行顺序等等,都是可预测的

使用纯函数编写 reducer

纯函数的概念:函数的返回结果只依赖其参数,并且执行过程中不会产生副作用

在 redux 中,我们通过定义 reducer 来更改状态,每个 reducer 都是纯函数,这意味着它没有副作用,相同的输入必定有相同的输出

ps:修改外部的变量、调用 DOM API 修改页面,发送 Ajax 请求,调用 window.reload 刷新浏览器甚至是console.log 打印数据,都是副作用

就问你纯不纯

redux 的几个基本概念

store

store 是存储数据的地方,它是一个对象,有这么几个方法

  • getState() 获取当前状态
  • dispatch(action) 派发 action
  • subscribe(handler) 监听数据的变化

action

action 可以理解为操作数据的行为

action 一般的写法如下:

代码语言:javascript
复制
const add = (val) => {
    return {
        type: 'ADD',
        value: val
    }
}

通过 type 去定义这个 action 是干嘛的,在 reducer 中要进行什么操作

dispatch

dispatch 的作用就是派发一个 action,让 reducer 进行数据的处理

一般写法:

代码语言:javascript
复制
dispatch({
    type: 'ADD',
    value: 1
})

reducer

reducer 里是真正更改数据的地方,dispatch 派发的 action 最终由 reducer 来进行数据的处理,并且每次的更改都是返回新的 state,这样做的目的是为了让 state 变的可预测

middleware

在创建 store 的时候 createStore 可以传入三个参数,第三个参数就是中间件,使用 redux 提供的 applyMiddleware 方法来调用,applyMiddleware 等于是对 dispatch 进行了增强,这样的话,在 dispatch 的过程中可以做一些其他的事情,比如记录 state 的变化、异步请求等等

从 0 实现一个 mini-redux

redux 的核心,就是 createStore 这个函数,store、getState、dispatch 都是这个函数返回的

redux 的大致原理就是发布订阅模式:通过 dispatch 派发 action 更改 store,通过 subscribe 订阅 store 的变化,去更新对应的 view

createStore

用过 createStore 方法的都知道,创建一个 store 需要三个参数

代码语言:javascript
复制
/**
 * 创建 store 
 * @param {*} reducer 
 * @param {*} initState 初始 state
 * @param {*} enhancer 中间件
 */
const createStore = (reducer, initState, enhancer) => {

}

这个函数会返回几个功能函数

代码语言:javascript
复制
/**
 * 创建 store 
 * @param {*} reducer 
 * @param {*} initialState 初始 state
 * @param {*} enhancer 中间件
 */
const createStore = (reducer, initialState, enhancer) => {
  return {
    getState,
    dispatch,
    subscribe,
    replaceReducer
  }
}

下面来实现这几个方法

getState 的实现

getState 方法的作用就是返回当前的 state

代码语言:javascript
复制
let currentState; // 当前 state
/**
 * 返回最新的 state
 */
const getState = () => {
  return currentState;
};
subscribe 的实现

subscribe 的作用是订阅 state 的变化,使用者通过这个方法订阅,当 state 变化后,执行监听函数subscribe 是一个高阶函数,它的返回值一个函数,执行该函数可以移除当前的监听函数

代码语言:javascript
复制
let subQueue = []; // 创建一个监听队列
/**
 * 监听 state 的变动
 * @param {*} listener 数据变化时要执行的函数 
 */
const subscribe = (listener) => {
  // 把监听函数放入监听队列里
  subQueue.push(listener);
  // 移除监听事件
  return () => {
    subQueue = subQueue.filter((l) => l !== listener);
  };
};
dispatch 的实现

dispatch 方法接收一个 action 参数,来执行 reducer,执行完成后会执行所有的监听函数

代码语言:javascript
复制
let currentReducer = reducer;
let isDispatch = false;
/**
  * 派发 action 并执行所有 监听函数
  * @param {*} action 
  */
const dispatch = (action) => {
  // 这里使用 isDispatch 做标识,上一个处理完成后才能处理下一个
  if(isDispatch) {
    throw new Error('dispatching')
  }

  try {
    currentState = currentReducer(currentState, action)
    isDispatch = true;
  } finally {
    isDispatch = false;
  }

  // 执行所有监听函数
  subQueue.forEach((listener) => listener())
  return action
}
replaceReducer 的实现

replaceReducer 的作用就是替换当前 reducer,执行 createStore 的时候,会接收一个默认的 reducer,如果后面想要重新换一个,就需要用到 replaceReducer 了

代码语言:javascript
复制
/**
 * 替换 reducer
 * @param {*} reducer
 */
const replaceReducer = (reducer) => {
 // 直接把新的 reducer 覆盖掉旧的就行了
 currentReducer = reducer;
 // 替换之后派发一次 dispatch
 dispatch({ type: 'MINI_REDUX_REPLACE' });
};

替换之后派发一次 dispatch 的目的是初始化一下新的 reducer

完整版 createStore

要想理解并实现中间件,内容还是蛮多的,所以本篇先不写中间件相关的内容

代码语言:javascript
复制
/**
 * 创建 store
 * @param {*} reducer
 * @param {*} initialState 初始 state
 * @param {*} enhancer 中间件
 */
const createStore = (reducer, initialState, enhancer) => {
  let currentState; // 当前 state
  let subQueue = []; // 创建一个监听队列
  let currentReducer = reducer;
  let isDispatch = false;

  if (initialState) {
    currentState = initialState;
  }

  /**
   * 返回最新的 state
   */
  const getState = () => {
    return currentState;
  };

  /**
   * 监听 state 的变动
   * @param {*} listener 数据变化时要执行的函数
   */
  const subscribe = (listener) => {
    // 把监听函数放入监听队列里
    subQueue.push(listener);
    // 移除监听事件
    return () => {
      subQueue = subQueue.filter((l) => l !== listener);
    };
  };

  /**
   * 派发 action 并执行所有 监听函数
   * @param {*} action
   */
  const dispatch = (action) => {
    // 这里使用 isDispatch 做标识,上一个处理完成后才能处理下一个
    if (isDispatch) {
      throw new Error('dispatching');
    }

    try {
      currentState = currentReducer(currentState, action);
      isDispatch = true;
    } finally {
      isDispatch = false;
    }

    // 执行所有监听函数
    subQueue.forEach((listener) => listener());
    return action;
  };

  /**
   * 替换 reducer
   * @param {*} reducer
   */
  const replaceReducer = (reducer) => {
    if (reducer) {
      // 直接把新的 reducer 覆盖掉旧的就行了
      currentReducer = reducer;
    }
    // 替换之后派发一次 dispatch
    dispatch({ type: 'MINI_REDUX_REPLACE' });
  };

  return {
    getState,
    dispatch,
    subscribe,
    replaceReducer,
  };
};

export default createStore;

要想在项目中跑起来,光实现一个 createStore 是不够的

createStore 建造了一个仓库,还需要配送点(Provider)和送货员(connect)才能到用户(组件)手里

Provider、connect

首先,我们需要清楚他们三者之间的职责:

createStore:生成 store,返回一系列功能函数

Provider:把 createStore 返回的一系列函数传递到每个子组件里

connect:把 store 里的数据关联到组件上

Provider 的实现

Provider 的主要作用就是把 store 里的数据传递下去

代码语言:text
复制
// Provider.jsx
import React, { createContext } from 'react';

export const StoreContext = createContext(null);

const Provider = (props = {}) => {
  const { store, children } = props;
  return <StoreContext.Provider value={store}>{children}</StoreContext.Provider>;
};

export default Provider;

connect 的实现

connect 是一个高阶组件,第二个参数为需要关联数据的组件,返回一个新组件

connect 的作用就是把 store 的数据关联到对应组件里,并监听 store 的变化,数据变化后更新对应组件

代码语言:text
复制
// connect.jsx
import React, { useContext, useEffect, useState } from 'react';
import { StoreContext } from './Provider';

const connect = (mapStateToProps, mapDispatchToProps) => (WrapComponent) => {
  const ConnectComponent = () => {
    const { getState, dispatch, subscribe } = useContext(StoreContext);
    const [props, setProps] = useState({
      getState,
      dispatch,
    });

    let stateToProps;
    let dispatchToProps;

    const update = () => {
      if (mapStateToProps) {
        stateToProps = mapStateToProps(getState());
      }

      if (mapDispatchToProps) {
        dispatchToProps = mapDispatchToProps(dispatch);
      }

      setProps({
        ...props,
        ...stateToProps,
        ...dispatchToProps,
      });
    };

    useEffect(() => {
      update();
      subscribe(() => update());
    }, []);

    return <WrapComponent {...props} />;
  };

  return ConnectComponent;
};

export default connect;

总结

一个基础版的 mini redux 就实现完了,有空了把中间件相关的东西输出一下

这是我学习完相关内容之后输出的一个笔记,有写的不对的地方,还请各位铁汁指正 抱拳了老铁

体验在线 demo:点我点我点我

github:https://github.com/isxiaoxin/mini_redux

首发自:从 0 实现一个 mini redux - 小鑫の随笔

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • redux 是什么
  • redux 的设计原则
    • redux 的三大设计原则:
      • 单一数据源
        • 状态是只读的
          • 使用纯函数编写 reducer
          • redux 的几个基本概念
            • store
              • action
                • dispatch
                  • reducer
                    • middleware
                    • 从 0 实现一个 mini-redux
                      • createStore
                        • getState 的实现
                        • subscribe 的实现
                        • dispatch 的实现
                        • replaceReducer 的实现
                        • 完整版 createStore
                    • Provider、connect
                      • Provider 的实现
                        • connect 的实现
                        • 总结
                        相关产品与服务
                        消息队列 TDMQ
                        消息队列 TDMQ (Tencent Distributed Message Queue)是腾讯基于 Apache Pulsar 自研的一个云原生消息中间件系列,其中包含兼容Pulsar、RabbitMQ、RocketMQ 等协议的消息队列子产品,得益于其底层计算与存储分离的架构,TDMQ 具备良好的弹性伸缩以及故障恢复能力。
                        领券
                        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档