了解Redux并实现简易的Redux

一、Redux是什么

A Predictable State Container for JS Apps

Redux官方介绍中讲述自己是一个可预测进行状态管理JS库。它保证程序行为一致性且易于测试。

为什么我们需要Redux

  • 全局状态管理的需要
  • 不同组件之间数据的共享
  • 保证程序行为一致性且易于测试。

三大原则

  • 单一数据源

    •   整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。
  • State是只读的

    •   唯一改变 state 的方法就是触发 actionaction 是一个用于描述已发生事件的普通对象。
  • 使用纯函数来执行修改

    •   为了描述 action 如何改变 state tree ,你需要编写 reducers

二、Redux的基本使用

这里我们简单介绍Redux的基本使用,你可以参考redux.js.org

由于Redux并非仅支持React,甚至本身跟React并无关联关系,所以上方的教程仅说明了JSRedux的使用,下面我们结合React来做示范。

2.1 安装依赖

在我们项目下,新安装Redux相关依赖。

npm install redux

2.2 构建store

根据官方文档进行store的构建。(注意:官方更建议我们使用 @reduxjs/toolkit 不过这里还是采取redux)。

import { AnyAction, createStore } from 'redux';

function counterReducer(state = 0, action: AnyAction) {
  switch (action.type) {
    case 'incremented':
      return state + 1;
    case 'decremented':
      return state - 1;
    default:
      return state;
  }
}

const store = createStore(counterReducer);

export default store;

2.3 组件中使用

这里只用了类组件进行探讨。

import React, { Component } from 'react';
import store from '../store';

class ReduxExample extends Component {
  subscribe: any = null;

  componentDidMount() {
    this.subscribe = store.subscribe(() => {
      this.forceUpdate();
    });
  }

  componentWillUnmount() {
    this.subscribe();
  }

  incremented = () => {
    store.dispatch({ type: 'incremented' });
  }

  decremented = () => {
    store.dispatch({ type: 'decremented' });
  }

  render() {
    return (
      <div>
        count: {store.getState()}
        <div>
          <button onClick={this.incremented}>+</button>
          <button onClick={this.decremented}>-</button>
        </div>
      </div>
    );
  }
}

export default ReduxExample;

三、Redux的工作流程

我们在了解Redux的工作流程的时候,我们先明白几个前置概念。

概念

State

State (also called the state tree) is a broad term, but in the Redux API it usually refers to the single state value that is managed by the store and returned by getState(). It represents the entire state of a Redux application, which is often a deeply nested object.

状态在Redux中通常被Store所管理,并通过getState暴露得到。实际上,我们在页面/全局管理中,我们所使用的就是变量/状态。

Action

An action is a plain object that represents an intention to change the state. Actions are the only way to get data into the store. Any data, whether from UI events, network callbacks, or other sources such as WebSockets needs to eventually be dispatched as actions.

Action主要提供修改store中的state的能力。Action必须有对应的type

Reducer

A reducer (also called a reducing function) is a function that accepts an accumulation and a value and returns a new accumulation. They are used to reduce a collection of values down to a single value.

reducer是一个函数,它接受一个初始值,并返回一个新的值。实质上是对State的修改。

Dispatching Function

A dispatching function (or simply dispatch function) is a function that accepts an action or an async action; it then may or may not dispatch one or more actions to the store.

dispatch可以接收一个action, 告诉store要进行具体的action,之后store会通知reduces进行更新,并把更新值返回给store

Action Creator

An action creator is, quite simply, a function that creates an action.

这里很直白,是一个用于创建action的方法、

流程

这里的store你可以理解为存储中心,变量的最新状态都可以在这里去到。

  • Component通过store暴露的getState接口获取最新的state值。
  • Component可以通过dispatch,并传入对应的action,通知store需要做更新。
  • store接收通知之后,让reducers进行操作,并返回最新的值给store
  • Component通过store暴露的getState接口获取最新的state值。

四、实现简易的Redux

下面,我们就实现一个简易的redux,目前仅支持同步的版本,后续会更新迭代。

确定返回值

我们使用redux一般进行的是如下的操作,有getState,dispatch,subscribe等操作。

const store = createStore(xxxReducer);

const state = store.getState();

store.dispatch({ type: 'xxxx' });

store.subscribe(() => {}) ; 

于是,我们的redux函数可以返回为

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

我们编写对应的类型。

export interface Dispatch<A extends AnyAction> {
  (params: A): void;
}

export interface Action<T = any> {
  type: T
}

export interface AnyAction<T = any> extends Action<T> {
  [extraProps: string]: any
}

export type Reducer<S = any, A extends Action = AnyAction> = (
  state: S | undefined,
  action: A
) => S

export interface Store<T extends any, A extends AnyAction> {
  getState: () => T | undefined;
  subscribe: (fn: () => void) => () => void;
  dispatch: Dispatch<A>;
}

详细功能实现

上方,我们讲到了我们需要去返回getState,dispatch,subscribe等函数。

我们先看整体,在来拆解,整体代码结构如下图所示。

import { Reducer, Store, Dispatch, Action, AnyAction } from './typings';

export function createStore<S, A extends AnyAction>(reducer: Reducer<S, A>): Store<S, A> {
  let currentState: S | undefined;
  let currentListeners: Array<() => void> = [];

  const getState = () => {};

  const subscribe: Store<S, A>['subscribe'] = (fn) => {
    
  }

  const dispatch: Dispatch<A> = (action) => {
    
  }

  return {
    getState,
    subscribe,
    dispatch,
  }
}

上图中,我们用来currentState来进行状态保存,同时使用currentListeners存放订阅函数。

getState--获取最新状态值

这里实现比较简单,直接把currentState返回即可。

const getState = () => currentState;

dispatch--通知修改

这里的我们的dispatch会接收对应的action,之后产生最新状态值的工作会交给reducer

const dispatch: Dispatch<A> = (action) => {
    currentState = reducer(currentState, action);
    currentListeners.forEach(listener => listener());
}

subscribe--订阅修改的回调

这里需要注意以下几点

  • 这里订阅的回调是在state发生修改后返回的。
  • 需要返回一个解绑回调的函数。
  • 可以存在多个回调。
const subscribe: Store<S, A>['subscribe'] = (fn) => {
    currentListeners.push(fn);
    return () => {
      const index = currentListeners.findIndex(fn);
      currentListeners.splice(index, 1);
    }
}

整体实现

代码

src\lib\redux-nut\index.ts

import { Reducer, Store, Dispatch, Action, AnyAction } from './typings';

export function createStore<S, A extends AnyAction>(reducer: Reducer<S, A>): Store<S, A> {
  let currentState: S | undefined;
  let currentListeners: Array<() => void> = [];

  const getState = () => currentState;

  const subscribe: Store<S, A>['subscribe'] = (fn) => {
    currentListeners.push(fn);
    return () => {
      const index = currentListeners.findIndex(fn);
      currentListeners.splice(index, 1);
    }
  }

  const dispatch: Dispatch<A> = (action) => {
    currentState = reducer(currentState, action);
    currentListeners.forEach(listener => listener());
  }

  dispatch({ type: 'SystemInit' } as A);

  return {
    getState,
    subscribe,
    dispatch,
  }
}

src\lib\redux-nut\typings.ts

export interface Dispatch<A extends AnyAction> {
  (params: A): void;
}

export interface Action<T = any> {
  type: T
}

export interface AnyAction<T = any> extends Action<T> {
  [extraProps: string]: any
}

export type Reducer<S = any, A extends Action = AnyAction> = (
  state: S | undefined,
  action: A
) => S

export interface Store<T extends any, A extends AnyAction> {
  getState: () => T | undefined;
  subscribe: (fn: () => void) => () => void;
  dispatch: Dispatch<A>;
}

src\store\index.ts

import { Action } from './../lib/redux-nut/typings';
import { createStore } from '../lib/redux-nut';

function counterReducer(state = 0, action: Action<'incremented' | 'decremented'>) {
  switch (action.type) {
    case 'incremented':
      return state + 1;
    case 'decremented':
      return state - 1;
    default:
      return state;
  }
}

const store = createStore(counterReducer);

export default store;

src/pages/ReduxExample.tsx

import React, { Component } from 'react';
import store from '../store';

class ReduxExample extends Component {
  subscribe: any = null;

  componentDidMount() {
    this.subscribe = store.subscribe(() => {
      this.forceUpdate();
    });
  }

  componentWillUnmount() {
    this.subscribe();
  }

  incremented = () => {
    store.dispatch({ type: 'incremented' });
  }

  decremented = () => {
    store.dispatch({ type: 'decremented' });
  }

  render() {
    return (
      <div>
        count: {store.getState()}
        <div>
          <button onClick={this.incremented}>+</button>
          <button onClick={this.decremented}>-</button>
        </div>
      </div>
    );
  }
}

export default ReduxExample;

效果

总结

这里我们简单的了解了redux的大概原理,也实现了一个最简单的redux。当然有很多其他的知识和概念没有提交,如中间件等,实际上redux还有很多知识点在里面,这些内容会在后续进行更新。

参考资料

猜你喜欢

转载自juejin.im/post/7118641465760153636