Unstated淺析

現狀

react狀態管理的主流方案通常是Redux和Mobx。react

Redux是函數式的解決方案,須要寫大量的樣板文件,可使用Dva或者Rematch來簡化開發。redux

Mobx是經過proxy和defineProperty來劫持數據,對每一個數據變更進行響應。api

在項目裏使用Redux和Mobx,都須要配合一些其餘的功能庫,好比react-redux。數組

若是隻是想進行簡單的組件通訊,去共享一些數據,Redux和Mobx可能會比較重。若是直接使用context,可能要本身封裝一些組件。promise

Unstated

Unstated是一個輕量級的狀態管理工具,而且在api設計的時候沿用react的設計思想,可以快速的理解和上手。若是隻是爲了解決組件的通訊問題,能夠嘗試使用它,不須要依賴其餘的第三方庫。bash

一個簡單的栗子

import React, { Component } from 'react';
import { Button, Input } from 'antd';
import { Provider, Subscribe, Container } from 'unstated';

class CounterContainer extends Container {
  constructor(initCount) {
    super();

    this.state = { count: initCount || 0 };
  }

  increment = () => {
    this.setState({ count: this.state.count + 1 });
  };

  decrement = () => {
    this.setState({ count: this.state.count - 1 });
  };
}

const Counter = () => (
  <Subscribe to={[CounterContainer]}>
    {
      counter => (
        <div>
          <span>{counter.state.count}</span>
          <Button onClick={counter.decrement}>-</Button>
          <Button onClick={counter.increment}>+</Button>
        </div>
      )
    }
  </Subscribe>
);


export default class CounterProvider extends Component {
  constructor(props) {
    super(props);
    this.state = {};
  }

  render() {
    return (
      <Provider>
        <Counter />
      </Provider>
    );
  }
}

複製代碼

淺析

Unstated拋出三個對象,分別是Container、Subscribe和Provider。antd

Unstated會使用React.createContext來建立一個StateContext對象,用來進行狀態的傳遞。
app

Container

// 簡單處理過的代碼

export class Container {
  constructor() {
    CONTAINER_DEBUG_CALLBACKS.forEach(cb => cb(this));
    this.state = null;
    this.listeners = [];
  }

  setState(updater, callback) {
    return Promise.resolve().then(() => {
      let nextState = null;

      if (typeof updater === 'function') {
        nextState = updater(this.state);
      } else {
        nextState = updater;
      }

      if (nextState === null) {
        callback && callback();
      }

      this.state = Object.assign({}, this.state, nextState);

      const promises = this.listeners.map(listener => listener());

      return Promise.all(promises).then(() => {
        if (callback) {
          return callback();
        }
      });
    });
  }

  subscribe(fn) {
    this.listeners.push(fn);
  }

  unsubscribe(fn) {
    this.listeners = this.listeners.filter(f => f !== fn);
  }
}

複製代碼

Container類包含了setState、subscribe和unsubscribe三個方法和state、listeners兩個屬性。async

state用來存儲數據,listeners用來存放訂閱的方法。ide

subscribe和unsubscribe分別用來訂閱方法和解除訂閱,當setState方法被調用時,會去觸發listeners中全部的訂閱方法。從代碼中能夠看出,subscribe訂閱的方法要返回一個promise。

setState用來更新state。這個方法和react中的setState相似,能夠接收一個對象或者方法,最後會使用Object.assign對state進行合併生成一個新的state。

setState()注意點

不要在設置state後當即讀取state

class CounterContainer extends Container {
  state = { count: 0 };
  increment = () => {
    this.setState({ count: 1 });
    console.log(this.state.count); // 0
  };
}
複製代碼

若是須要上一個state來計算下一個state,請傳入函數

class CounterContainer extends Container {
  state = { count: 0 };
  increment = () => {
    this.setState(state => {
      return { count: state.count + 1 };
    });
  };
}
複製代碼

Unstated的setState返回一個promise,可使用await

class CounterContainer extends Container {
  state = { count: 0 };
  increment = async () => {
    await this.setState({ count: 1 });
    console.log(this.state.count); // 1
  };
}
複製代碼

Subscribe

// 簡單處理過的代碼

class Subscribe extends React.Component {
  constructor(props) {
    super(props);
    this.state = {};
    this.instances = [];
    this.unmounted = false;
  }

  componentWillUnmount() {
    this.unmounted = true;
    this.unsubscribe();
  }

  unsubscribe() {
    this.instances.forEach((container) => {
      container.unsubscribe(this.onUpdate);
    });
  }

  onUpdate = () => new Promise((resolve) => {
    if (!this.unmounted) {
      this.setState(DUMMY_STATE, resolve);
    } else {
      resolve();
    }
  })

  createInstances(map, containers) {
    this.unsubscribe();

    if (map === null) {
      throw new Error('You must wrap your <Subscribe> components with a <Provider>');
    }

    const safeMap = map;
    const instances = containers.map((ContainerItem) => {
      let instance;

      if (
        typeof ContainerItem === 'object' &&
        ContainerItem instanceof Container
      ) {
        instance = ContainerItem;
      } else {
        instance = safeMap.get(ContainerItem);

        if (!instance) {
          instance = new ContainerItem();
          safeMap.set(ContainerItem, instance);
        }
      }

      instance.unsubscribe(this.onUpdate);
      instance.subscribe(this.onUpdate);

      return instance;
    });

    this.instances = instances;
    return instances;
  }

  render() {
    return (
      <StateContext.Consumer>
        {
          map => this.props.children.apply(
            null,
            this.createInstances(map, this.props.to),
          )
        }
      </StateContext.Consumer>
    );
  }
}
複製代碼

Unstated的Subscribe是一個react Component,且返回的是StateContext的Consumer。

兩個關鍵的方法是createInstances和onUpdate。

onUpdate

這個方法用來被Container對象進行訂閱,調用這個方法會觸發Subscribe的setState,進而從新渲染Subscribe組件。

createInstances

createInstances接收兩個參數,map是StateContext.Provider傳過來的值,第二個參數是組件接收的to這個prop。

對props傳進來的Container類進行處理。若是safeMap沒有這個Container的實例化對象,那麼先實例化一個instance,而後將這個Container增長到safeMap中,Container做爲鍵,instance做爲值;若是傳進來的safeMap已經有這個Container類的實例,那麼直接賦值給instance。Container的實例生成後,訂閱onUpdate方法。

Provider

// 簡單處理過的代碼

function Provider(props) {
  return (
    <StateContext.Consumer>
      {
        (parentMap) => {
          const childMap = new Map(parentMap);

          if (props.inject) {
            props.inject.forEach((instance) => {
              childMap.set(instance.constructor, instance);
            });
          }

          return (
            <StateContext.Provider value={childMap}>
              {
                props.children
              }
            </StateContext.Provider>
          );
        }
      }
    </StateContext.Consumer>
  );
}
複製代碼

Provider接收inject這個prop,inject是一個數組,數組的每一項都是Container的實例。

經過上面的代碼能夠看出,context的值是一個map,這個map的鍵是Container類,值是Container類的實例。Unstated經過Provider的inject屬性,讓咱們能夠作一些Container類初始化的工做。在Subscribe接收的map中,若是已經存在某個Container類的鍵值對,那麼就直接使用這個實例進行處理

import React, { Component } from 'react';
import { Button, Input } from 'antd';
import { Provider, Subscribe, Container } from 'unstated';

class AppContainer extends Container {
  constructor(initAmount) {
    super();
    this.state = {
      amount: initAmount || 1,
    };
  }

  setAmount(amount) {
    this.setState({ amount });
  }
}

class CounterContainer extends Container {
  state = {
    count: 0,
  };

  increment(amount) {
    this.setState({ count: this.state.count + amount });
  }

  decrement(amount) {
    this.setState({ count: this.state.count - amount });
  }
}

function Counter() {
  return (
    <Subscribe to={[AppContainer, CounterContainer]}>
      {
        (app, counter) => (
          <div>
            <span>Count: {counter.state.count}</span>
            <Button onClick={() => counter.decrement(app.state.amount)}>-</Button>
            <Button onClick={() => counter.increment(app.state.amount)}>+</Button>
          </div>
        )
      }
    </Subscribe>
  );
}

function App() {
  return (
    <Subscribe to={[AppContainer]}>
      {
        app => (
          <div>
            <Counter />
            <span>Amount: </span>
            <Input
              type="number"
              value={app.state.amount}
              onChange={(event) => {
                app.setAmount(parseInt(event.currentTarget.value, 10));
              }}
            />
          </div>
        )
      }
    </Subscribe>
  );
}

export default class CounterProvider extends Component {
  constructor(props) {
    super(props);
    this.state = {};
  }

  render() {
    const initAppContainer = new AppContainer(3);
    return (
      <Provider inject={[initAppContainer]}>
        <App />
        {/* <Counter /> */}
      </Provider>
    );
  }
}複製代碼
相關文章
相關標籤/搜索