Code Monkey home page Code Monkey logo

blog's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

blog's Issues

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

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

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

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之类的操作开销

四. 参考

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.