【Redux】从程序设计的角度基本了解 & 实现 Redux

前言

  • 本文会从变量赋值 & 修改到 Flux 思想来阐述 Redux 究竟想要做什么
  • 本文会做一个 Redux 的基本使用 & 基本实现
  • 本文会从函数式编程的角度来进行阐述

从字面量赋值到 Flux 流思想

假设有这样一个 TodoList 的场景:我们需要有变量 list 表示当前的列表和 todoInput 来表示当前需要添加的数据。

直接使用变量进行修改:

在通常情况下,我们会考虑使用直接字面量赋值的方式来创建和修改这些变量的值:

js 复制代码
let list = [];
let todoInput = '';

// ...
list = [...list, { /* something */ }];
todoInput = '打篮球';

上述的代码当然是不好的,因为它如下的弊端:

  1. 数据的离散型太强(内聚性差):没有办法在 TodoList 模块中进行统一的处理
  2. 数据修改不明确 :每次数据的修改都需要查看对应行的代码,链路模糊不清
  3. 数据的修改难以做其他的操作处理 :(e.g 中间操作(日志、劫持、连接 devtool)、后续操作(更新视图、配合其他模块操作))

下面将针对上述这些问题,进行逐一修改:

降低数据的离散程度

我们需要使用一个状态变量 state 来存储 TodoList 模块需要用到的数据:

js 复制代码
const state = {
  list: [],
  todoInput: ''
};

state.list = [...state.list, { /* something */ }];
state.todoInput = '打篮球';

让数据修改变得更加明确

在每一次修改数据的时候,我们都需要使用对应的纯函数来进行修改(而不是直接字面量赋值)。

js 复制代码
const state = {
  list: [],
  todoInput: ''
};

function addTodo(state, newTodo)
  const newList = [
    ...state.list,
    newTodo
  ];

  state.todoList = newList;
}

function changeTodoInput(state, todoInput) {
  state.todoInput = todoInput;
}

扩展操作处理

我们需要:

  • 使用函数将 TodoList 这个模块包裹起来
  • 将需要外界访问的内容抛出去,不需要外界访问的内容使用 _ 前置命名声明在模块中
  • 使用回调函数的方式来扩展 state 的操作
js 复制代码
import _ from 'lodash';

function TodoList(initialState, middleware, callback) {
  const _state = _.cloneDeep(initialState);
  const _middleware = typeof middleware === 'function' ? middleware : (() => {});
  const _callback = typeof callback === 'function' ? callback : (() => {});

  function _addTodo(state, newTodo) {
    const newList = [
      ...state.list,
      newTodo
    ];

    _middleware(state, newTodo);

    state.todoList = newList;

    _callback(state, newTodo);
  }

  function _changeTodoInput(state, todoInput) {
    _middleware(state, todoInput);
    state.todoInput = todoInput;
    _callback(state, todoInput);
  }

  return {
    getState() {
      return _state;
    },

    // 返回 _addTodo 的偏函数
    addTodo(newTodo) {
      _addTodo(_state, newTodo);
    },

    // 返回 _changeTodoInput 的偏函数
    changeTodoInput(todoInput) {
      _changeTodoInput(_state, todoInput);
    }
  };
}

这样写,几乎把变量的操作问题解决了,但是方法操作上面还有问题需要修复:

  1. 方法操作需要每次都调用 _middleware_callback, 可读性变差。这里我们封装一个面相切面操作的高阶函数来解决可读性差的问题
js 复制代码
import _ from 'lodash';

function TodoList(initialState, middleware, callback) {
  const _state = _.cloneDeep(initialState);
  const _middleware = typeof middleware === 'function' ? middleware : (() => {});
  const _callback = typeof callback === 'function' ? callback : (() => {});

  function _addTodo(state, newTodo) {
    // _middleware(state, newTodo);
    // const newList = [
    //   ...state.list,
    //   newTodo
    // ];

    // state.todoList = newList;

    // _callback(state, newTodo);
    useExtendCallbacks(() => {
      const newList = [
        ...state.list,
        newTodo
      ];

      state.todoList = newList;
    }, {
      middleware: _middleware,
      callback: _callback
    }, [state, newTodo]);
  }

  function _changeTodoInput(state, todoInput) {
    // _middleware(state, todoInput);
    // state.todoInput = todoInput;
    // _callback(state, todoInput);
    useExtendCallacks(() => {
      state.todoInput = todoInput;
    }, [state, todoInput]);
  }

  function useExtendCallacks(main, {
    middleware,
    callback
  }, args) {
    if (arguments[2] === undefined) {
      args = arguments[1];
      middleware = _middleware;
      callback = _callback;
    }

    typeof middleware === 'function' && middleware.apply(null, args);
    main();
    typeof callback === 'function' && callback.apply(null, args);
  }

  return {
    getState() {
      return _state;
    },

    // 返回 _addTodo 的偏函数
    addTodo(newTodo) {
      _addTodo(_state, newTodo);
    },

    // 返回 _changeTodoInput 的偏函数
    changeTodoInput(todoInput) {
      _changeTodoInput(_state, todoInput);
    }
  };
}
  1. 每声明一个修改变量的方法,都需要从 TodoList 模块里面返回出去。当方法越来越多的时候,返回的方法也会越来越多,不利于统一管理操作。

    解决方法:这里我们需要使用一个【抽象函数】来统一管理所有的方法,通常这个【抽象函数】我们把它命名为 dispatch, 意思是派发给对应的方法进行操作:

js 复制代码
import _ from 'lodash';

function TodoList(initialState, middleware, callback) {
  const _state = _.cloneDeep(initialState);
  const _middleware = typeof middleware === 'function' ? middleware : (() => {});
  const _callback = typeof callback === 'function' ? callback : (() => {});

  function _addTodo(state, newTodo) {
    // _middleware(state, newTodo);
    // const newList = [
    //   ...state.list,
    //   newTodo
    // ];

    // state.todoList = newList;

    // _callback(state, newTodo);
    useExtendCallbacks(() => {
      const newList = [
        ...state.list,
        newTodo
      ];

      state.todoList = newList;
    }, {
      middleware: _middleware,
      callback: _callback
    }, [state, newTodo]);
  }

  function _changeTodoInput(state, todoInput) {
    // _middleware(state, todoInput);
    // state.todoInput = todoInput;
    // _callback(state, todoInput);
    useExtendCallacks(() => {
      state.todoInput = todoInput;
    }, [state, todoInput]);
  }

  function useExtendCallacks(main, {
    middleware,
    callback
  }, args) {
    if (arguments[2] === undefined) {
      args = arguments[1];
      middleware = _middleware;
      callback = _callback;
    }

    typeof middleware === 'function' && middleware.apply(null, args);
    main();
    typeof callback === 'function' && callback.apply(null, args);
  }

  return {
    getState() {
      return _state;
    },

    dispatch(type, ...payload) {
      switch (type) {
        case 'addTodo':
          _addTodo(_state, ...payload);
          break;
        case 'changeTodoInput':
          _changeTodoInput(_state, ...payload);
          break;
        default:
          break;
      }
    }
  };
}

执行到这里时,flux 流的思想就呼之欲出了!

Flux 是什么?

概述

Flux 是一种架构思想,专门用于解决软件的结构问题。它跟 MVC 架构是一类东西,但是更加简单和清晰。

Flux 的核心就是一个简单的约定:视图层组件不允许直接修改应用状态,只能通过 dispatch 方法来触发 action。应用的状态必须独立 出来放到 store 里面统一管理,通过侦听 action 来执行具体的状态操作。

大致结构:

以 React 为例,Flux 架构的组件结构如下:

核心概念:

这里会有几个 Flux 思想对应的一些核心概念:

  • store: 数据仓库,存放应用状态,并且通过 dispatch 方法来触发 action

  • state: 数据仓库中真实存储的数据,可以通过 getState 方法来获取。

  • action: 动作,触发 store 状态变更的操作,可以是用户操作、异步操作等,可以是一个对象,也可以是一个异步函数。

  • dispatch: 触发 action 的方法,可以大致理解为 storesetState,但是它不会直接修改 store 仓库里面的数据。

  • view: store 仓库中的数据对应需要渲染的视图

Flux 数据流是一个经典的单向的数据流动设计思想,而 Redux 就是参考 Flux 数据流设计的解决方案之一。

Redux 是什么?

Redux 概述

Redux 是基于 Flux 思想设计的 JavaScript 应用程序的状态容器,它为应用提供了可预测的状态管理。

Redux 官网

参考:www.reduxjs.cn/introductio...

Redux 做了什么?

注意:

  • Redux 是一个全局状态管理的应用仓库,它的使用并不仅仅局限于 React 的应用,您可以在任何的 JavaScript 程序中使用 Redux
  • 不是所有的状态都应该存储在 redux 中,虽然 redux 提供了全局状态管理,但是它并不是万能的,有些状态不应该存储在 redux 中,比如:组件的配置 props,组件自身应有的状态 state / data
  • 不是所有的全局状态都应该存储在 redux 中,Redux 中大量的数据存储会使 store 的行为越来越难以预测!(以 React 项目举例,我们可以使用 context API 和 自定义 hook 来存储我们应用中的全局数据。)

Redux 核心

下面我们将从零到一,创建一个 Redux 仓库并且使用它:

创建 store

在 Redux 中,我们可以使用 createStore 来创建一个 store:

js 复制代码
const store = createStore(/* ... */);

直接使用 createStore 是无法创建 Redux 仓库的。因为 createStore 是一个高阶函数,它必须要接收一个修改 state 的函数作为参数,这个函数就是 Redux 中的 reducer

js 复制代码
const store = createStore(reducer);

注意

  • Redux 认为:使用 createStore 方法创建 store 需要遵循单一 store 原则 ------ 一个应用只能有一个 store !

创建 reducer

在 Redux 中,reducer 是一个纯操作的函数,它接收当前状态和 action 作为参数,返回一个新的状态。

js 复制代码
// 这个 initialState 就是 redux 仓库一开始的值,它是由用户自己去定义的
import initialState from './state';

/**
 * @function reducer
 * @description reducer -> 通知 redux 仓库修改状态值的唯一方法
 * @param {any} state redux 仓库的状态
 * @param {{ type: string; [payload: string]: any }} action 一个带有 type 属性的对象,它表示要执行的操作
 * @returns {} 返回一个新的 state(最好和旧的 state 的类型保持一致)
 */
function reducer(state = initialState, action) {
  switch (action.type) {
    case 'xxx':
      return {
        ...state
      };
    default:
      return state;
  }
}

export default reducer;

作者观点

  • 和很多人的理解不同,笔者认为 reducer 并不是一个纯函数,因为它一开始 state 的值取决于用户定义的 initialState,而 initialState 是一个对象,它可能包含一些初始值,也可能不包含。
  • 虽然 reducer 不是一个纯函数,但是它里面的操作需要保证纯度(reducer 内部不应该有副作用)

使用 store

概述:

在 Redux 中,store 是一个对象,它抛出了一些 API 方法给到我们使用:

  • getState() : 获取当前 Redux 仓库中的状态
  • dispatch() : 接收一个 action,并通知 Redux 仓库修改状态值
  • subscribe() : 接收一个函数,当 Redux 仓库状态改变时,会执行该函数;subscribe 函数执行完成后返回一个 unsubscribe 函数 (unsubscribe 函数执行后取消 subscribe 函数注册的 listener 回调)。

基本实现:

js 复制代码
/* index.js */

import store from './store';

// 获取当前 Redux 仓库中的状态
const state = store.getState();
console.log('=== Redux 仓库中的状态 ===', state);

// 修改 Redux 仓库中的状态
store.dispatch({
  type: 'ADD_TODO',
  payload: '待办事项'
});

// 重新获取当前 Redux 仓库中的状态
const state = store.getState();
console.log('=== Redux 仓库中的状态 ===', state);

使用 store.subscribe 修改上面的例子

js 复制代码
import store from './store';

const unsubcribe = store.subscribe(() => {
  const state = store.getState();
  console.log('=== Redux 仓库中的状态 ===', state);
});

store.dispatch({
  type: 'ADD_TODO',
  payload: '待办事项'
});

unsubcribe();

Redux 的基本实现

下面,笔者将使用自己的思路来实现一下 redux 的基本功能

代码结构:

perl 复制代码
my-redux
  |- creators
    |- createStore.js # 创建 Redux 仓库的核心方法
  |- shared
    |- utils.js # 一些公共的工具库
    |- configs.js # redux 中的一些默认配置
  |- index.js # my-redux 库的出口

实现步骤

my-redux 从外往里面设计:

  1. createStore 方法和 utils 中需要用到的方法统一导出
js 复制代码
export { default as createStore } from './creators/createStore';
export {
  // compose, combineReducers
} from './shared/utils';
  1. 实现一下将 shared 包里面的 utils.jsconfigs.js
js 复制代码
/* shared/utils.js */
export function isFunction(target) {
  return typeof target === 'function';
}

export function isObject(target) {
  return typeof target === 'object' && target !== null;
}

export function deepClone(origin, target) {
  var toString = Object.prototype.toString;
  var arrType = '[object Array]';

  var tar = target || (toString(origin) === arrType ? [] : {});

  for (var key in origin) {
    if (origin.hasOwnProperty(key)) {
      if (isObject(origin[key])) {
        // do somthing
        tar[key] = toString(origin[key]) === arrType ? [] : {};
        deepClone(origin[key], tar[key]);
      } else {
        tar[key] = origin[key];
      }
    }
  }

  return tar;
}

export function compose() {
  var fns = [];

  for (var i = 0; i < arguments.length; i++) {
    fns.push(arguments[i]);
  }

  return function (x) {
    return fns.reduceRight(function (y, f) {
      return f(y);
    }, x);
  }
}
js 复制代码
/* shared/configs.js */
export const INIT_ACTION_TYPE = '@@INIT@@';
  1. 声明核心方法 createStore()
js 复制代码
/* creators/createStore.js */

export function createStore(reducer) {
  return {};
}
  1. createStore 中,我们需要返回三个 API 方法:getState()dispatch()subscribe()
js 复制代码
/* creators/createStore.js */

export function createStore(reducer) {
  return {
    getState,
    dispatch,
    subscribe
  };

  function getState() {}

  function dispatch() {}

  function subscribe() {}
}
  1. 初始化 createStore 中需要用到的变量
js 复制代码
/* creators/createStore.js */
import { INIT_ACTION_TYPE } from '../shared/configs';
import { isFunction, isObject } from '../shared/utils';

export function createStore(reducer) {
  if (!isFunction(reducer))
    throw new Error('reducer must be a function');

  let currentReducer = reducer;
  let currentState = currentReducer(undefined, { type: INIT_ACTION_TYPE });
  const subscribeListeners = [];

  return {
    getState,
    dispatch,
    subscribe
  };

  function getState() {}

  function dispatch() {}

  function subscribe() {}
}
  1. 实现一下 getState 方法
js 复制代码
/* creators/createStore.js */
import { INIT_ACTION_TYPE } from '../shared/configs';
import { isFunction, isObject, deepClone } from '../shared/utils';

export function createStore(reducer) {
  if (!isFunction(reducer))
    throw new Error('reducer must be a function');

  let currentReducer = reducer;
  let currentState = currentReducer(undefined, { type: INIT_ACTION_TYPE });
  const subscribeListeners = [];

  return {
    getState,
    dispatch,
    subscribe
  };

  function getState() {
    return deepClone(currentState);
  }

  function dispatch() {}

  function subscribe() {}
}
  1. 实现一下 dispatch 方法 (暂时不考虑中间件的情况)
js 复制代码
/* creators/createStore.js */
import { INIT_ACTION_TYPE } from '../shared/configs';
import { isFunction, isObject, deepClone } from '../shared/utils';

export function createStore(reducer) {
  if (!isFunction(reducer))
    throw new Error('reducer must be a function');

  let currentReducer = reducer;
  let currentState = currentReducer(undefined, { type: INIT_ACTION_TYPE });
  const subscribeListeners = [];

  return {
    getState,
    dispatch,
    subscribe
  };

  function getState() {
    return deepClone(currentState);
  }

  function dispatch(action) {
    const currentAction = action;

    if (!isObject(currentAction))
      throw new Error('action must be an object');
    if (action.type !== 'string')
      throw new Error('action.type must be a string');

    currentState = currentReducer(currentState, currentAction);
    subscribeListeners.forEach(listener => listener());

    return currentAction;
  }

  function subscribe() {}
}
  1. 实现一下 subscribe 方法
js 复制代码
/* creators/createStore.js */
import { INIT_ACTION_TYPE } from '../shared/configs';
import { isFunction, isObject, deepClone } from '../shared/utils';

export function createStore(reducer) {
  if (!isFunction(reducer))
    throw new Error('reducer must be a function');

  let currentReducer = reducer;
  let currentState = currentReducer(undefined, { type: INIT_ACTION_TYPE });
  const subscribeListeners = [];

  return {
    getState,
    dispatch,
    subscribe
  };

  function getState() {
    return deepClone(currentState);
  }

  function dispatch(action) {
    const currentAction = action;

    if (!isObject(currentAction))
      throw new Error('action must be an object');
    if (action.type !== 'string')
      throw new Error('action.type must be a string');

    currentState = currentReducer(currentState, currentAction);
    subscribeListeners.forEach(listener => listener());

    return currentAction;
  }

  function subscribe(listener) {
    if (!isFunction(listener)) return;
    if (subscribeListeners.includes(listener)) return;

    subscribeListeners.push(listener);
    const unsubscribe = () => {
      subscribeListeners.splice(subscribeListeners.indexOf(listener), 1);
    };

    return unsubscribe;
  }
}

像这样,Redux 的功能就基本上实现了

相关推荐
xdscode4 天前
Spring AI 中的 Flux 与 SSE:流式输出完全解析
java·flux·sse·springai·stream流式输出
浑水摸鱼仙君17 天前
SpringSecurity和Flux同时使用报未认证问题
java·ai·flux·springsecurity·springai
IT星宿1 个月前
开发一个 TypeScript 语言服务插件:让 RTK Query 的"跳转到定义"更智能
typescript·redux
leolee181 个月前
react redux 简单使用
前端·react.js·redux
leolee181 个月前
Redux Toolkit 实战使用指南
前端·react.js·redux
hhzz2 个月前
【Vision人工智能设计 】ComfyUI 基础图生图设计
人工智能·flux·comfyui·视觉大模型·lora模型
CappuccinoRose2 个月前
React框架学习文档(二)
javascript·react.js·组件·redux·props·state·context api
Marshmallowc2 个月前
React 刷新页面 Token 消失?深度解析 Redux + LocalStorage 数据持久化方案与 Hook 避坑指南
javascript·react·数据持久化·redux·前端工程化
七夜zippoe3 个月前
响应式编程基石 Project Reactor源码解读
java·spring·flux·响应式编程·mono·订阅机制
Thetimezipsby3 个月前
Redux、React Redux 快速学习上手、简单易懂、知识速查
前端·react.js·redux