Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MVVM框架的数据状态管理的发展与探索 #1

Open
silent-tan opened this issue Jul 23, 2018 · 0 comments
Open

MVVM框架的数据状态管理的发展与探索 #1

silent-tan opened this issue Jul 23, 2018 · 0 comments

Comments

@silent-tan
Copy link
Owner

silent-tan commented Jul 23, 2018

前言:在前端应用日渐复杂的环境下,前端页面状态可控制和可跟踪成为解决开发和调试的重要手段,显然我们有必要了解状态管理方案可以解决的是什么问题,解决问题的核心方式是什么,目前前沿流行的解决方案有哪些?这些方案针对的背景是哪些?使用这些方案的技巧有哪些?这些问题本次分享都会有所涉及

一. 为什么我们要数据状态管理,状态管理为什么会复杂?

I. 复杂状态

页面的状态复杂与否决定是否需要数据状态管理。那么什么样的页面状态可以说是复杂呢?

  • 从交互和数据方面来说:

    • 页面数据有多个来源
    • 与服务器进行大量交互,例如websocket页面
    • 用户交互复杂
    • 参与页面操作角色不是唯一的,包含多角色不同交互,多角色协助
  • 组件来说,非组件状态的共享,包含多方面,相互影响,依赖关系等

更具体来说,因为react dom的树形结构,我们考虑组件的关系,以及其状态的维护与交流来看状态维护与管理的困难程度

  1. 自身与子组件的状态维护

    • 自身状态,通过管理和维护state,简单
    • 自身和子组件,管理和维护子组件props,简单
    • 自身和子组件的子组件,通过props二次传递,一般
    • 自身和多层子组件:①props传递 复杂或者说麻烦 ②context 简单,以下面例子说明
    # 旧版react的context存在缺陷,当中间父组件shouldComponentUpdate做优化处理返回false时,子孙组件无法获取更新的context。解决方案是传递的context作为一个事件的监听系统
    const AppContext = React.createContext({
      value: 'ceshi',
      changeValue: () => {}
    })
    
    class Parent extends React.Component {
      render() {
        return (
          <div>
            {this.props.children}
          </div>
        )
      }
    }
    
    class ChildOne extends React.Component {
      render() {
        return (
          <AppContext.Consumer>
            {({changeValue}) => (
              <input onChange={changeValue} />
            )}
          </AppContext.Consumer>
        )
      }
    }
    
    class ChildTwo extends React.Component {
      render() {
        return (
          <AppContext.Consumer>
            {({value}) => (
              <div>{value}</div>
            )}
          </AppContext.Consumer>
        )
      }
    }
    
    class App extends React.Component {
      constructor(props) {
        super(props)
        this.changeValue = (e) => {
          this.setState({
            value: e.target.value
          })
        }
        this.state = {
          value: '',
          changeValue: this.changeValue
        }
      }
      render() {
        return (
          <AppContext.Provider value={this.state}>
            <Parent>
              <ChildOne />
            </Parent>
            <Parent>
              <ChildTwo />
            </Parent>
          </AppContext.Provider>
        );
      }
    }
    // ========================================
    ReactDOM.render(
      <App />,
      document.getElementById('root')
    );
  2. 自身和兄弟组件的状态管理:

    • 兄弟状态:通过公共父组件,通过refs回调,一般
    • 兄弟子组件状态:父组件->兄弟->子组件 一般
    • 兄弟子组件的子孙组件:父组件->兄弟->子组件->子组件->... 复杂
  3. 自身和祖先状态

    • 父组件,refs回调,一般
    • 祖父组件,refs.refs回调,复杂
    • 祖先组件的兄弟(或者子组件) 复杂

总体上来说,这些状态的关联可以用下面图标表示

image

面对以上复杂问题,单凭react自身的state和props维护与工作方式,会让页面重构以及问题定位都陷入放飞自我的过程。

以上问题如果说明了我们是需要状态管理,那么我们需要怎样的状态管理,状态管理怎么为我们解决这些状态复杂的问题。这个需要根据我们时候的UI渲染方案的特点,本文就以我们目前使用的React进行探究

II. React特点(v16.4.1)

react是用以创建有相互影响的UI的一个库,在react应用中,表现层响应每一个状态,react保证高效地正确更新状态改变后的组件和让代码更加可预测和调试

每个React元素都是Immutable的,所以一旦创建React Element后就不可以直接改变它了。React DOM创建了一个树形结构的虚拟树,其实根据React的Component互相组合的特点也是可以想象出这个树形结构的虚拟树的。

这里顺便说一下React Component的几种形式:

  1. React.Component
  2. React.PureComponent
  3. 纯函数Component

可以根据组件特点来进行定义可以进一步加快ReactDom的比较和构建
还有就是关于contructor构造函数,contructor(props)是否要写?如果contructor里面有用到props可以加props,没有就没有必要

react Component提供一些钩子函数,叫做生命周期函数,这些生命周期函数反应了React组件的创建过程,包括初始化,装载,更新,卸载的过程。

III. 如果没有状态管理的库,那么也许我们需要的是

  1. 对象存储,对象即是View的状态,存储了共享的状态

  2. 对象状态的查询,View可以从存储状态读取View所需要的数据和状态

  3. 对象状态改变导致对象存储的改变的描述,当数据状态因为页面的交互发生变化时,对应来自对象存储中的数据和状态怎么变化

  4. 对象存储变化的时候,怎么通知页面,或者说页面引用的状态怎么保证与对象存储中的状态同步

    例子

image

二. 发展成熟的状态管理方案

React只是一个UI渲染的库,它不是一个架构,它没有明确地说明页面数据必须怎么样流动,但是其state和props的以及render机制说明单向数据流有利于其机制运行

I. 数据驱动

1. Flux

Flux是最早的一个状态管理方案(架构),它的主要核心是Dispatcher、Store、View。主要流程是当用户进行操作的时候,会从组件发出一个 action,这个 action 流到 store 里面,触发 store 对状态进行改动,然后 store 又触发组件基于新的状态重新渲染。其实质就是强制每个数据状态的改变都通过store进行记录。

架构设计思路:

  • ①实例化模块状态类(比如TodoStore),该类继承FluxReduceStore,FluxReduceStore继承FluxStore类。所以必须要实现getInitialState方法和reduce方法。当状态类实例化后该类会向Dispatcher中注册一个回调,这个回调会调用状态类中的reduce方法,然后比较reduce方法返回的state和之前的state做比较,如果不同,会调用该Store中响应__changeEvent的事件

    Dispatcher

    FluxStore

    FluxReduceStore

  • ②创建顶层Container,作用是结合React的渲染机制,使得数据进行单向流动,即使得如果状态集合中如果有状态发生改变,子组件能够进行更新机制。

    创建这个Container一般使用createFunctional方法,它接受四个参数,第一个参数是一个纯函数React组件,这个组件是我们代码中的顶层AppContainer; 第二个参数是Store的数组形式;第三个参数是一个函数,这个函数返回我们Container中的state, 这个state会传给我们的AppContainer; 第四个参数是额外的选项,选项包括是否渲染成PureContainer和传递props和context

    function createFunctional<Props, State, A, B>(
    viewFn: (props: State) => React.Element<State>,
    getStores: (props?: ?Props, context?: any) => Array<FluxStore>,
    calculateState: (
        prevState?: ?State,
        props?: ?Props,
        context?: any
    ) => State,
    options?: Options

): ReactClass
```
Container实例化的时候会实例化一个FluxContainerSubscriptions类
image

会为这个Subscriptions实例的`_callbacks`属性添加回调函数, 这个回调函数会调用Container组件的`setState`方法,新的state会是`calculateState`返回的state

然后会调用Subscriptions实例的setStore方法,setStore方法有两个作用:第一是为每一个实例的状态类监听`__changeEvent`的一个回调函数,这个回调函数作用是设置Subscriptions实例的change为true或者打印一下有状态改变的状态类实例。***第二是创建一个状态集合FluxStoreGroup类的实例`new FluxStoreGroup(stores, setStateCallback)`。Group类初始化的时候会向Dispatcher 注册一个回调。该回调会执行注册中心Dispatcher的`waitFor()`方法并且`执行setStateCallback`, waitFor方法会执行dispatch中心注册的非处理中回调函数(这些函数包括每个状态store实例初始化时候注册的回调函数)***
  • ③经过上面两个步骤,flux为应用做了响应数据状态改变的准备。

  • ④当用户触发一些action,比如点击按钮之类的UI操作或者其他触发action的操作。也就是调用了类似这类的函数

    const Actions = {
        addTodo(text) {
            TodoDispatcher.dispatch({
                type: TodoActionTypes.ADD_TODO,
                text,
            });
        }
    }

    实质就是转发中心实例调用dispatch(action: Object)方法。该方法会调用所有转发中心里面注册过的函数,我们回忆一下向转发中心里面注册的回调是:所有store初始化注册的函数,Group实例初始化时注册的函数。即是先执行FluxReduceStore实例的__invokeOnDispatch(),该方法会调用状态类中的reduce方法,然后比较reduce方法返回的state和之前的state做比较,如果不同把新的state赋值给状态实例的_state属性,然后执行Group类初始化的时候会向Dispatcher 注册的回调(waitfor + setStateCallback)。
    state发生改变就会触发react的渲染机制了,这就形成了flux的单数据流解决方案,用下图表示

    Flux单向数据流

根据上面的思路和图表,更加细致地看一下这四个核心部分的源码:

1.1 Action

action描述用户行为,比如添加一个Todo的描述,值得注意的是,这个描述是会发给所有Dispatcher注册的回调的

const Actions = {
  addTodo(text) {
    TodoDispatcher.dispatch({
      type: TodoActionTypes.ADD_TODO,
      text,
    });
  }
}

1.2 Dispatcher

转发中心,作用将Action转发到store处理

在分析Dispatcher前,先说一个工具函数invariant, invariant(condition: boolean, format: string, a: any, b: any, c: any, d: any, e: any, f: any)。它接受一个返回true或者false的条件,如果false就抛出错误报告,abcdef是打印的参数,format字符串中的的%s字符会被这些参数填充填充

然后我们快速看下Dispatcher的源码

var invariant = require('invariant');

export type DispatchToken = string;

var _prefix = 'ID_';

class Dispatcher<TPayload> {
  _callbacks: {[key: DispatchToken]: (payload: TPayload) => void};
  _isDispatching: boolean;
  _isHandled: {[key: DispatchToken]: boolean};
  _isPending: {[key: DispatchToken]: boolean};
  _lastID: number;
  _pendingPayload: TPayload;

  constructor() {
    this._callbacks = {};
    this._isDispatching = false;
    this._isHandled = {};
    this._isPending = {};
    this._lastID = 1;
  }

  /**
   * 注册一个可以被分发的函数,返回一个回调ID,这个ID可以被waitfor函数使用,这个注册函数默认未处理未完成
   * 往实例变量_callbacks添加key-value
   */
  register(callback: (payload: TPayload) => void): DispatchToken {
    var id = _prefix + this._lastID++;
    this._callbacks[id] = callback;
    return id;
  }

  /**
   * 根据回调ID删除注册函数
   * 删除_callbacks中的key
   */
  unregister(id: DispatchToken): void {
    invariant(
      this._callbacks[id],
      'Dispatcher.unregister(...): `%s` does not map to a registered callback.',
      id
    );
    delete this._callbacks[id];
  }

  /**
   * 在执行当前回调之前等待指定执行的回调完成
   * 执行所有非isPending状态的回调函数
   */
  waitFor(ids: Array<DispatchToken>): void {
    invariant(
      this._isDispatching,
      'Dispatcher.waitFor(...): Must be invoked while dispatching.'
    );
    for (var ii = 0; ii < ids.length; ii++) {
      var id = ids[ii];
      if (this._isPending[id]) {
        invariant(
          this._isHandled[id],
          'Dispatcher.waitFor(...): Circular dependency detected while ' +
          'waiting for `%s`.',
          id
        );
        continue;
      }
      invariant(
        this._callbacks[id],
        'Dispatcher.waitFor(...): `%s` does not map to a registered callback.',
        id
      );
      this._invokeCallback(id);
    }
  }

  /**
   * 转发参数给所有注册的回调函数,不允许转发中心在转发状态下进行转发
   */
  dispatch(payload: TPayload): void {
    invariant(
      !this._isDispatching,
      'Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch.'
    );
    this._startDispatching(payload);
    try {
      for (var id in this._callbacks) {
        if (this._isPending[id]) {
          continue;
        }
        this._invokeCallback(id);
      }
    } finally {
      this._stopDispatching();
    }
  }

  /**
   * 转发中心是否处于转发状态
   */
  isDispatching(): boolean {
    return this._isDispatching;
  }

  /**
   * 根据回调函数的ID和使用参数pendingPayload去调用函数
   * 同时改变回调函数的状态 isPending=true
   * 运行后,修改该回调的运行状态 isHandled=true
   */
  _invokeCallback(id: DispatchToken): void {
    this._isPending[id] = true;
    this._callbacks[id](this._pendingPayload);
    this._isHandled[id] = true;
  }

  /**
   * 开始分发的时候将所有注册回调的pending状态和handle状态清空,即是设置为false
   * 然后赋值转发回调函数需要的参数pendingPayload
   * 并改变转发状态isDispatching=true
   */
  _startDispatching(payload: TPayload): void {
    for (var id in this._callbacks) {
      this._isPending[id] = false;
      this._isHandled[id] = false;
    }
    this._pendingPayload = payload;
    this._isDispatching = true;
  }

  /**
   * 清空转发状态的标记
   * 删除_pendingPayload和设置isDispatching=false
   */
  _stopDispatching(): void {
    delete this._pendingPayload;
    this._isDispatching = false;
  }
}

module.exports = Dispatcher;
  • callbacks,就是DispatchToken和函数回调的一个Dictionary
  • isDispatching,体现当前Dispatcher是否处于dispatch状态
  • isHandled通过token去检测一个函数是否被处理过了
  • isPending,通过token去检测一个函数是否被提交Dispatcher了,即是函数是否处于处理中的状态
  • lastID,最近一次被加入Dispatcher的函数体的UniqueID,即DispatchToken。
  • pendingPayload,需要传递给调用函数的参数,iOS开发者可以理解为userInfo。

1.3 Store

store的作用有如下作用:

  • 缓存数据
  • 向外暴露get方法获取store的数据
  • 响应订阅的action
  • 数据改动的时候分发这个改动,换句话就是触发回调

我们看一下Flux的store设计

import type Dispatcher from 'Dispatcher';

const {EventEmitter} = require('fbemitter');

// 这个类是内部基类,不直接继承
class FluxStore {
    // 私有
    _dispatchToken: string;
    // 保护属性,可以子类去使用
    __changed: boolean;
    __changeEvent: string;
    __className: any;
    __dispatcher: Dispatcher<any>;
    __emitter: EventEmitter;
    
    // 对象初始化
    constructor(dispatcher: Dispatcher<any>): void {
        this.__className = this.constructor.name;
        this.__changed = false;
        this.__changeEvent = 'change';
        this.__dispatcher = dispatcher;
        this.__emitter = new EventEmitter();
        // store 初始化会默认注册一个回调
        this._dispatchToken = dispatcher.register((payload) => {
          this.__invokeOnDispatch(payload);
        });
    }
    
    // 添加监听
    addListener(callback: (eventType?: string) => void): {remove: () => void}
    
    // 获取转发中心
    getDispatcher(): Dispatcher<any>
    
    // 获取转发中心注册回调的ID,也就是每个store注册的回调
    getDispatchToken(): string
    
    // 最近的调度中,store是否发生改变
    hasChanged(): boolean
    
    // 触发store改变后,修改__changed属性为true
    __emitChange(): void
    
    // 继承该类的类需要重写该方案,否则__onDispatch会抛出错误
    // 封装了所有__onDispatch的逻辑,该函数在store的注册回调中被调用
    __invokeOnDispatch(payload: Object): void
    
    // 这个回调会在实例化期间被Dispatcher注册
    __onDispatch(payload: Object): void
}

class FluxReduceStore<TState> extends FluxStore {
    // 私有
    _state: TState;

    constructor(dispatcher: Dispatcher<Object>) {
        super(dispatcher);
        this._state = this.getInitialState();
    }
    
    // 如果store不是Immutable类型,这个方法是要重写的
    // 返回_state
    getState(): TState
    
    // 需要重写
    // 初始化_state的具体函数
    getInitialState(): TState
    
    // 需要重写
    // 根据动作状态描述,返回新的状态
    reduce(state: TState, action: Object): TState
    
    // 如果Tstate是Immutable类型的话不需要重写
    // 比较前后state是否相同
    areEqual(one: TState, two: TState): boolean
    
    // 重写FluxStore的__invokeOnDispatch
    __invokeOnDispatch(action: Object): void
}

// 检测所有的store初始化dispatcher对象为同一个对象
function _getUniformDispatcher(stores: Array<FluxStore>): Dispatcher<any>

/**
 * 初始化接受两个参数
 * FluxStore[], callback
 */
class FluxStoreGroup {
    // 私有
    _dispatcher: Dispatcher<any>;
    _dispatchToken: DispatchToken;
    
    constructor(stores: Array<FluxStore>, callback: Function): void {
        this._dispatcher = _getUniformDispatcher(stores);
    
        // Precompute store tokens.
        var storeTokens = stores.map(store => store.getDispatchToken());
    
        // Register with the dispatcher.
        this._dispatchToken = this._dispatcher.register(payload => {
          this._dispatcher.waitFor(storeTokens);
          callback();
        });
    }

    // 取消回调的注册
    release(): void
}

1.4 Views

有两种方式去创建顶层View

function create<DefaultProps, Props, State>(
    Base: ReactClass<Props>,
    options?: ?Options
): ReactClass<Props>

function createFunctional<Props, State, A, B>(
    viewFn: (props: State) => React.Element<State>,
    getStores: (props?: ?Props, context?: any) => Array<FluxStore>,
    calculateState: (
        prevState?: ?State,
        props?: ?Props,
        context?: any
    ) => State,
    options?: Options
): ReactClass<Props>

下图是完整的flux架构示意图:

Flux响应交互单向数据流

2. Redux

Redux是基于观察模式来设计的。

回顾一下观察者模式的实现思路:

  • 被观察者需要有一个存储观察者方法的数组,如subscribers
  • 添加观察者方法用于添加观察者到subscribers数组中去,比如addSubscriber
  • 删除观察者方法用于从subscribers删除观察者, 比如removeSubscriber
  • 最后是有一个方法去调用subscribers中的所有观察者方法,即一个遍历函数调度subscribers, 比如dispatch

一种方案运行都有它自身的约定原则,redux的几个原则是:

  • 必须使用基本对象来描述应用状态
  • 使用基本对象来描述系统变化
  • 使用纯函数来处理系统中的业务逻辑

同上面flux介绍,根据一个简单的例子我们先来了解一下

  • ①我们一般会先整理好我们的store集合组成,这里先不关注UI,尽管我们的store的初始化是根据UI上面的状态来进行设置的,如下面两个模块的reducer,然后我们会使用combineReducers方法来合并reducer,这是因为redux方案需要的是一个store

    const todos = (state = [], action) => {
    	switch(action.type) {
    		case 'ADD_TODO':
    			return [
    				...state, {
    				id: action.id,
    				text: action.text,
    				completed: false
    				}
    			]
    		case 'TOGGLE_TODO':
    			return state.map(todo => {
    				if (todo.id !== action.id) {
    					return todo;
    				}
    				return {
    					...todo,
    					completed: !todo.completed
    				}
    			});
    		default:
    			return state;
    	}
    };
    
    const visibilityFilter = (state = 'SHOW_ALL', action) => {
    	switch (action.type) {
    		case 'SET_VISIBILITY_FILTER':
    			return action.filter;
    		default:
    			return state;
    	}
    };
    const { combineReducers } = Redux;
    const todoApp = combineReducers({
    	todos,
    	visibilityFilter
    });
    
  • ②使用createStore构建状态树

    const { combineReducers, createStore } = Redux;
    const store = createStore(todoApp);
  • ③向store添加监听函数,当dispatch函数调用的时候都会重新render

    store.subscribe(render)

以上就是最简单的redux demo,而在实际开发中,我们一般不会每次都重新render的和直接把所有props传递给组件,也不会手动去添加监听函数,一般引入react-redux帮我们处理这些问题,react-redux就不在这里介绍了

redux主要向外暴露五个api:

  • createStore,
  • combineReducers,
  • bindActionCreators,
  • applyMiddleware,
  • compose

先介绍下combineReducers

2.1 combineReducers

combineReducers的用法如下,作用是合并reducer为一个,是一个curry化的函数

const authReducer = (state = initState, action) => {}
const bindCardReducer = (state = initState, action) => {}

const appReducer = combineReducer({ authReducer, bindCardReducer })

回到他的源码我们快速过一下:

export default function combineReducers(reducers) {
  // 筛选符合的reducer,reducer为function
  const reducerKeys = Object.keys(reducers)
  const finalReducers = {}
  for (let i = 0; i < reducerKeys.length; i++) {
    const key = reducerKeys[i]

    if (process.env.NODE_ENV !== 'production') {
      if (typeof reducers[key] === 'undefined') {
        warning(`No reducer provided for key "${key}"`)
      }
    }

    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }
  const finalReducerKeys = Object.keys(finalReducers)

  let unexpectedKeyCache
  if (process.env.NODE_ENV !== 'production') {
    unexpectedKeyCache = {}
  }

  let shapeAssertionError
  try {
    // 遍历finalReducers中的reducer,检查传入reducer的state是否合法
    assertReducerShape(finalReducers)
  } catch (e) {
    shapeAssertionError = e
  }

  return function combination(state = {}, action) {
    // 传入的reducer的state如果不合法,就抛出错误
    if (shapeAssertionError) {
      throw shapeAssertionError
    }
    // 如果不是正式环境就抛出警告
    if (process.env.NODE_ENV !== 'production') {
      const warningMessage = getUnexpectedStateShapeWarningMessage(
        state,
        finalReducers,
        action,
        unexpectedKeyCache
      )
      if (warningMessage) {
        warning(warningMessage)
      }
    }

    let hasChanged = false
    const nextState = {}
    // 遍历所有的key和reducer,分别将reducer对应的key所代表的state,代入到reducer中进行函数调用
    for (let i = 0; i < finalReducerKeys.length; i++) {
      const key = finalReducerKeys[i]
      const reducer = finalReducers[key]
      // combineReducers黑魔法--要求传入的Object参数中,reducer function的名称和要和state同名的原因
      const previousStateForKey = state[key]
      const nextStateForKey = reducer(previousStateForKey, action)
      if (typeof nextStateForKey === 'undefined') {
        const errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    return hasChanged ? nextState : state
  }
}
**

2.2 createStore

redux的观者者createStore实现

/**
 *@param reducer 是一个函数,返回下一个store的的状态
 *@param preloadedState是一个初始态的状态
 *@param enhancer函数 是redux store的中间件
 *@returns store
 */
export default function createStore(reducer, preloadedState, enhancer) {
  // 检测preloadState和enhancer是否传反
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }

  // enhancer中间件
  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }

    return enhancer(createStore)(reducer, preloadedState)
  }

  if (typeof reducer !== 'function') {
    throw new Error('Expected the reducer to be a function.')
  }

  let currentReducer = reducer
  let currentState = preloadedState
  let currentListeners = []
  let nextListeners = currentListeners
  let isDispatching = false

  // 如果nextListeners和currentListeners指向同一个栈,浅复制一份
  // 目的是修改nextListeners不会影响到currentListeners
  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }

  /**
   * 通过方法获取state. 在中间件一般都会用到这个函数来获取state
   */
  function getState() {
    if (isDispatching) {
      throw new Error(
        'You may not call store.getState() while the reducer is executing. ' +
          'The reducer has already received the state as an argument. ' +
          'Pass it down from the top reducer instead of reading it from the store.'
      )
    }

    return currentState
  }

  /**
   * 注册listener,同时返回一个取消事件注册的方法。当调用store.dispatch的时候调用listener
   * 每次调度动作时都会调用它
   * 而如果状态树的某些部分可能已经发生了变化那么你可以调用getState()来读取回调中的当前状态树
   */
  function subscribe(listener) {
    if (typeof listener !== 'function') {
      throw new Error('Expected the listener to be a function.')
    }

    if (isDispatching) {
      throw new Error(
        'You may not call store.subscribe() while the reducer is executing. ' +
          'If you would like to be notified after the store has been updated, subscribe from a ' +
          'component and invoke store.getState() in the callback to access the latest state. ' +
          'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.'
      )
    }

    let isSubscribed = true

    ensureCanMutateNextListeners()
    nextListeners.push(listener)

    /*
     * 返回删除该listener的函数
     * 使用:
     * const unsubscribe = store.subscribe(() => { // dosomethin })
     * unsubscribe()
     */
    
    return function unsubscribe() {
      if (!isSubscribed) {
        return
      }

      if (isDispatching) {
        throw new Error(
          'You may not unsubscribe from a store listener while the reducer is executing. ' +
            'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.'
        )
      }

      isSubscribed = false

      ensureCanMutateNextListeners()
      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
    }
  }

  /**
   * 分发一个action.这是唯一改变state的方法
   * reducer函数可以被当前状态树和给以的action对象调用以创建状态集合。它的返回值会作为下一个状态数,会被所有listeners监听
   * 基本的实现仅仅支持普通对象的分发,如果你Promise,Observable,thunk或其他东西,那么就要使用中间件去实现了,比如redux-thunk
   */
  function dispatch(action) {
    if (!isPlainObject(action)) {
      throw new Error(
        'Actions must be plain objects. ' +
          'Use custom middleware for async actions.'
      )
    }

    if (typeof action.type === 'undefined') {
      throw new Error(
        'Actions may not have an undefined "type" property. ' +
          'Have you misspelled a constant?'
      )
    }
    
    // 防止多个dipatch同时进行
    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.')
    }

    try {
      isDispatching = true
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }
    
    // 分发的时候,nextListeners作为新的listeners
    const listeners = (currentListeners = nextListeners)
    // 遍历调用listener
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

    return action
  }

  /**
   * 替换reducer
   */
  function replaceReducer(nextReducer) {
    if (typeof nextReducer !== 'function') {
      throw new Error('Expected the nextReducer to be a function.')
    }

    currentReducer = nextReducer
    dispatch({ type: ActionTypes.REPLACE })
  }

  // 实例化store的时候初始化分发action
  dispatch({ type: ActionTypes.INIT })

  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer
  }
}

2.3 compose组合函数

将多个函数组合在一起,从从右到左运行

export default function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  }

  if (funcs.length === 1) {
    return funcs[0]
  }

  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

2.4 applyMiddleware中间件函数

回顾下我们中间件在开发中的用法

import thunkMiddleware from 'redux-thunk'

const store = createStore(reducers, state, applyMiddleware(thunkMiddleware))

为什么要用到thunkMiddleware?

Action 发出以后,Reducer 立即算出 State,这叫做同步。那么Action发出一段时间后,再执行Reducer,就是异步了。

而thunkMiddleware可以解决这个问题。

什么是中间件?

我们把对观察者的扩展叫做中间件

根据Redux的核心部分,我们看哪个地方适合我们扩展

(1)Reducer:纯函数,只承担计算 State 的功能,不合适承担其他功能,也承担不了,因为理论上,纯函数不能进行读写操作。

(2)View:与 State 一一对应,可以看作 State 的视觉层,也不合适承担其他功能。

(3)Action:存放数据的对象,即消息的载体,只能被别人操作,自己不能进行任何操作。

所以相对来说,只有发送 Action 的这个步骤,即store.dispatch()方法,可以添加功能。举例来说,要添加日志功能,把 Action 和 State 打印出来,可以对store.dispatch进行如下改造

let next = store.dispatch;
store.dispatch = function dispatchAndLog(action) {
  console.log('dispatching', action);
  next(action);
  console.log('next state', store.getState());
}

code:

export default function applyMiddleware(...middlewares) {
  // 最终返回一个以createStore为参数的匿名函数
  // 这个函数返回另一个以reducer, initialState, enhancer为参数的匿名函数
  return createStore => (...args) => {
    const store = createStore(...args)
    let dispatch = () => {
      throw new Error(
        `Dispatching while constructing your middleware is not allowed. ` +
          `Other middleware would not be applied to this dispatch.`
      )
    }

    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args)
    }
    // 每个 middleware 都以 middlewareAPI 作为参数进行注入,返回一个新的链。
    // 此时的返回值相当于调用 thunkMiddleware 返回的函数: (next) => (action) => {} ,接收一个next作为其参数
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    // 并将链代入进 compose 组成一个函数的调用链
    // compose(...chain) 返回形如(...args) => f(g(h(...args))),f/g/h都是chain中的函数对象。
    // 在目前只有 thunkMiddleware 作为 middlewares 参数的情况下,将返回 (next) => (action) => {}
    // 之后以 store.dispatch 作为参数进行注入
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}

2.5 bindActionCreators

function bindActionCreator(actionCreator, dispatch) {
  return (...args) => dispatch(actionCreator(...args))
}

// bindActionCreators期待一个Object作为actionCreators传入,里面是 key: action
export default function bindActionCreators(actionCreators, dispatch) {
  // 如果只是传入一个action,则通过bindActionCreator返回被绑定到dispatch的函数
  if (typeof actionCreators === 'function') {
    return bindActionCreator(actionCreators, dispatch)
  }

  if (typeof actionCreators !== 'object' || actionCreators === null) {
    throw new Error(
      `bindActionCreators expected an object or a function, instead received ${actionCreators === null ? 'null' : typeof actionCreators}. ` +
      `Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?`
    )
  }

  // 遍历并通过bindActionCreator分发绑定至dispatch
  var keys = Object.keys(actionCreators)
  var boundActionCreators = {}
  for (var i = 0; i < keys.length; i++) {
    var key = keys[i]
    var actionCreator = actionCreators[key]
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  return boundActionCreators
}

3. Flux VS Redux

3.1 相似点

  • redux = flux + fp,设计思想是一样的
  • 均保障了数据的单向流动
  • 要求state不可变
  • 采用事件机制的方式驱动state的变更

3.2 不同点

  • flux功能较简单,核心是实现了一个Dispatcher类,后来进行了重构,但是功能实现上比不上redux
  • flux里面可以存在多个store,但是redux只能由一个store
  • redux引入了函数式编程,在代码复用、单元测试上更有优势

3.3 观察者模式和订阅/发布模式的对比

  • 最大的区别在于调度的地方不同,观察者模式是由具体目标调度的,而发布/订阅模式是由调度中心调度的。所以观察者模式下,订阅者和发布者是有依赖的,而发布/订阅不会

4. Mobx

Mobx的源码比较复杂和多,所以这里主要分析它的原理和主要api

  • observable
  • computed
  • autorun
  • action
  • observer

4.1 mobx基本概念(暂不讲)

  • transaction 事务。表示一组原子性的操作,mobx正是因为此避免不必要的重新计算。主要涉及到startBatch和endBatch两个函数用于开始事务和结束事务

    export function startBatch() {
        globalState.inBatch++
    }
    
    export function endBatch() {
        if (--globalState.inBatch === 0) {
            // 执行所有 Reaction
            runReactions()
            // 处理不再被观察的 Observable
            const list = globalState.pendingUnobservations
            for (let i = 0; i < list.length; i++) {
                const observable = list[i]
                observable.isPendingUnobservation = false
                if (observable.observers.size === 0) {
                    if (observable.isBeingObserved) {
                        observable.isBeingObserved = false
                        observable.onBecomeUnobserved()
                    }
                    if (observable instanceof ComputedValue) {
                        observable.suspend()
                    }
                }
            }
            globalState.pendingUnobservations = []
        }
    }
  • atom。任何能用于存储状态的值在mobx中被称为Atom,它会在被观察时自身发生改变时发出通知

    export class Atom implements IAtom {
        // 标志属性,不再被观察时为 true
        isPendingUnobservation = false
        isBeingObserved = false
        // 观察者集合
        observers = new Set()
    
        diffValue = 0
        // 上一次被使用时,Derivation 的 runId
        lastAccessedBy = 0
        // 状态最新的观察者所处的状态
        lowestObserverState = IDerivationState.NOT_TRACKING
        
        constructor(public name = "Atom@" + getNextId()) {}
    
        public onBecomeUnobserved() {
            // noop
        }
    
        public onBecomeObserved() {
            /* noop */
        }
    
        // 被使用时触发
        public reportObserved() : boolean {return reportObserved(this)}
    
        // 发生变化时触发
        public reportChanged() {
            startBatch()
            propagateChanged(this)
            endBatch()
        }
    
        toString() {
            return this.name
        }
    }

    ObservableValue 正是继承自 Atom。可以看到,reportObserverd 和 reportChanged 分别调用了 reportObserved 和 propagateChanged 两个方法,这正是 Observable 用于「通知被观察」和「通知自身变化」的两个函数

    可以让用户能够基于它定制一些可观察的数据类型

  • Derivation。任何 源自状态并且不会再有任何进一步的相互作用的东西就是衍生。 衍生以多种形式存在

    • 用户界面
    • 衍生数据,比如剩下的待办事项的数量。
    • 后端集成,比如把变化发送到服务器端

4.2 mobx的要点或者说使用步骤

例子:https://codepen.io/farzer/pen/QBaeWB?editors=0011

  • ① 定义状态并使其可观察

    import {observable} from 'mobx';
    
    var appState = observable({
        timer: 0
    });
    • observable函数会将参数对象转为可观察对象,当有人请求可观察对象的值比如timer的时候,会触发mobx提供的reportObserved方法
    • 当可观察对象的值发生变化,比如timer的值发生改变的时候,会触发propagateChanged方法
  • ② 跟踪变化

    mobx.autorun(() => {
        console.log(appState.timer)
    })
    • autorun会首先创建Reaction对象,这个对象的作用是监督和控制任务执行
    • 然后会分配任务,即是我们传递的函数
    • 立即执行一次任务,即是reaction.schedule()
  • ③ 创建视图以响应状态的变化,以react为例

    import {observer} from 'mobx-react';
    
    @observer
    class TimerView extends React.Component {
        render() {
            return (<button onClick={this.onReset.bind(this)}>
                    Seconds passed: {this.props.appState.timer}
                </button>);
        }
    
        onReset () {
            this.props.appState.resetTimer();
        }
    };
    
    ReactDOM.render(<TimerView appState={appState} />, document.body);

    observer 函数/装饰器可以用来将 React 组件转变成响应式组件。 它用 mobx.autorun 包装了组件的 render 函数以确保任何组件渲染中使用的数据变化时都可以强制刷新组件

  • ④ 更改状态

    appState.resetTimer = action(function reset() {
        appState.timer = 0;
    });
    
    setInterval(action(function tick() {
        appState.timer += 1;
    }), 1000);

5. Mobx VS Redux

5.1 相似点

  • 作用是一样的,均用于state的管理
  • 数据流动均是单向的

5.2 不同点

  • store:redux是单个store,mobx可以是多个
  • 数据结构:redux使用正常的javascript对象,而mobx进行包裹,得到observable数据
  • immutable:redux要求数据的不可变形,而mobx则不坐要求
  • action:redux通过action来驱动数据的变化,是必选项,而mobx则为可选项
  • 代码量:mobx代码量小,可以快速完成简单业务开发
  • 耦合性:redux耦合度低,可以便于复用,也方便进行单元测试
  • 生态环境:redux的生态环境优于mobx
  • 使用场景:mobx适用于简单的业务,快速完成开发;redux适用于复杂场景
  • 时间回溯:mobx无法做时间回溯,因为只有数据只有一份引用
  • mobx自始至终一份引用,不需要 immutable,也没有复制对象的额外开销
  • mobx是自动收集依赖,所以很多逻辑隐藏在其内部实现,做扩展不容易

5.3 选择

数据流复杂就使用redux,不复杂使用mobx。

II. 模型驱动

mobx-state-tree

我们在实际react开发中使用mobx方案都是会使用到mobx-react方法结合使用,mobx-react提供了Provider和inject,observer等方法。

而mobx-state-tree的出现就包含了mobx,mobx-react的功能,并且弥补了mobx的跟踪调试差的问题以及快照等功能

III. 不知道应该怎么归类的rxjs,应该更多属于事件驱动吧

rxjs是一种响应式的编程方式,对异步问题的解决非常优雅的,它把一个函数的运行看做一个流,然后通过api对这个流进行阶段操作或者更多细致化的操作,比如一个例子

三. 状态性能优化方案(减少重复渲染,提高性能)

I. Immutable

Immutable解决的是什么问题呢?首先是改变的object对象的时候,层次深的时候复制原来object开销大,其次PureComponent浅比较的时候作比较不优雅。当然其恶心的api却是让人默默继续用回原来的方法

let newRate = Object.assign({}, state.list[0].roomInfo.rateList[0])
newRate.score = 90;

II. Immer

新一代的Immutable Data方案,和Immuatable.js的区别是使用原生的数据结构,这样的话就减少了toJS之类的操作开销

四. 参考

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant