【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 的功能就基本上实现了

相关推荐
小龙在山东2 天前
基于Flux的文生高清图片
stable diffusion·flux
ChinaZ.AI8 天前
ComfyUI 速度更快,显存占用更低的图像反推模型Florence2PromptGen,效果媲美JoyCaption,还支持Flux训练打标
人工智能·stable diffusion·aigc·flux·comfyui·florence
SpikeKing2 个月前
ComfyUI - 在 ComfyUI 配置与测试图像生成 Flux 模型教程
aigc·flux·huggingface·comfyui·clip
玩AI的小胡子2 个月前
超越sd3!比肩Midjourney-v6?AI绘画大模型FLUX1.0详细评测与本地部署方法(附安装文件)
人工智能·aigc·midjourney·ai绘画·flux·stablediffusion
庞德公2 个月前
Flux:Midjourney的新图像模型挑战者
人工智能·stable diffusion·midjourney·flux·text2img
且陶陶º2 个月前
【案例】使用React+redux实现一个Todomvc
javascript·react.js·redux
Orzak3 个月前
[译]全栈Redux实战
redux
摇光935 个月前
React + 项目(从基础到实战) -- 第十期
前端·javascript·react.js·redux
NullPointerExpection5 个月前
动手模拟 java Flux
java·开发语言·java-ee·flux·webflux
石小石Orz6 个月前
不要滥用Pinia和Redux了!多组件之间交互可以手写一个调度器!
前端·vuex·redux