Понимание библиотеки управления состоянием React Lightweight

React.js

Когда React пишет приложения, неизбежно возникает проблема межкомпонентного взаимодействия. Решений уже много.

  • Собственный контекст React
  • Redux в сочетании с React-redux
  • Mobx в сочетании с mobx-реагированием

Новый контекстный API React по сути не является заменой инструментов управления состоянием, таких как React или Mbox, в лучшем случае это Дополнение к недостаткам собственного государственного управления. Две библиотеки, Redux и Mbox, не предназначены для React.Для некоторых небольших приложений React Тяжелее.

основная концепция

Unstated основан на контекстном API. То есть используйте React.createContext() для создания StateContext для передачи состояния,

  • Контейнер: классы управления состоянием, состояние хранения внутреннего состояния, достигаемое состоянием обновления setState, в соответствии с компонентом API дизайна React.
  • Provider: возвращает Provider, который используется для упаковки компонентов верхнего уровня, внедрения экземпляров управления состоянием в приложение и инициализации данных.
  • Подписка: По сути, Потребитель получает экземпляр управления состоянием и принудительно обновляет представление, когда экземпляр Контейнера обновляет состояние.

простой пример

Давайте возьмем пример наиболее распространенного счетчика, чтобы увидеть, как используется unstatement.Во-первых, давайте проясним структуру: Parent как родительский компонент содержит два дочерних компонента: Child1 и Child2. Child1 отображает числа, а Child2 управляет сложением и вычитанием чисел. Затем внешний слой родительского компонента обертывает корневой компонент.

состояние обслуживания

Во-первых, разделяемому состоянию нужно место для управления состоянием.В отличие от редюсера Redux, Unstated наследуется от экземпляра контейнера:

import { Container } from 'unstated';

class CounterContainer extends Container {
  constructor(initCount) {
    super(...arguments);
    this.state = {count: initCount || 0};
  }

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

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

export default CounterContainer

Похоже, это не очень знакомо? Реагировать как класс компонента. Контронтавец унаследован от неустойчивых открытыхContainerКласс, используйте состояние для хранения данных, setState поддерживает состояние, И setState согласуется с использованием React setState, который можно передать в функцию. То, что возвращается, является обещанием.

общее состояние

Давайте посмотрим на компонент Child1 для отображения чисел, используйтеSubscribeСвяжитесь с CounterContainer.

import React from 'react'
import { Subscribe } from 'unstated'
import CounterContainer from './store/Counter'
class Child1 extends React.Component {
  render() {
    return <Subscribe to={[CounterContainer]}>
      {
        counter => {
          return <div>{counter.state.count}</div>
        }
      }
    </Subscribe>
  }
}
export default Child1

Давайте еще раз взглянем на компонент Child2, который управляет сложением и вычитанием чисел:

import React from 'react'
import { Button } from 'antd'
import { Subscribe } from 'unstated'
import CounterContainer from './store/Counter'
class Child2 extends React.Component {
  render() {
    return <Subscribe to={[CounterContainer]}>
      {
        counter => {
          return <div>
            <button onClick={counter.increment}>增加</button>
            <button onClick={counter.decrement}>减少</button>
          </div>
        }
      }
    </Subscribe>
  }
}
export default Child2

SubscribeВнутренним возвратом является StateContext.Consumer, который связан с экземпляром CounterContainer через свойство to, Используйте режим renderProps для визуализации представления, а параметр функции, вызываемой в Subscribe, является экземпляром управления состоянием подписки.Child1а такжеChild2Подпишитесь на общий экземпляр управления состоянием через SubscribeCounterContainer, поэтому Child2 может вызвать Методы увеличения и уменьшения в CounterContainer обновляют состояние, а Child1 отображает данные в соответствии с обновлением.

Взгляните на родительский компонент Parent

import React from 'react'
import { Provider } from 'unstated'
import Child1 from './Child1'
import Child2 from './Child2'
import CounterContainer from './store/Counter'

const counter = new CounterContainer(123)

class Parent extends React.Component {
  render() {
    return <Provider inject={[counter]}>
      父组件
      <Child1/>
      <Child2/>
    </Provider>
  }
}

export default Parent

ProviderВозвращается StateContext.Provider, и Parent передаетProviderВставьте экземпляр управления состоянием в контекст компонента. Здесь экземпляры не могут быть внедрены. Без внедрения Subscribe не может заставить внедренный экземпляр инициализировать данные, то есть присвоить состоянию значение по умолчанию, например 123 выше.

Также можно внедрить несколько экземпляров:

<Provider inject={[count1, count2]}>
   {/*Components*}
</Provide>

Затем вы можете получить несколько экземпляров при подписке.

<Subscribe to={[CounterContainer1, CounterContainer2]}>
  {count1, count2) => {}
</Subscribe>

Принцип анализа

Прежде чем понять принцип, вам необходимо понять взаимосвязь между тремя API, предоставляемыми Unstated. По своему разумению нарисовал картинку:

Чтобы упорядочить весь процесс:

  1. Создайте класс управления состоянием, который наследуется от контейнера.
  2. Создайте контекст, новый экземпляр управления состоянием, задайте значение по умолчанию, введите Provider
  3. Подпишитесь на класс управления состоянием подписки. Внутри экземпляр управления состоянием инициализируется и подписывается на экземпляр с помощью метода _createInstances Конкретный процесс выглядит следующим образом:
  • Получить экземпляр управления состоянием из контекста. Если он получен, он напрямую инициализирует данные. Если он не получен Затем используйте переданный класс управления состоянием для инициализации экземпляра.
  • Когда функция onUpdate, которая обновляет само представление, подписывается на экземпляр управления состоянием для реализации внутреннего setState экземпляра, onUpdate вызывается для обновления представления.
  • Метод _createInstances возвращает созданный экземпляр управления состоянием, который передается в качестве параметра функции, вызываемой renderProps.Функция получает экземпляр и обрабатывает или отображает данные.

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();
      }
      // 返回一个新的state
      this.state = Object.assign({}, this.state, nextState);
      // 执行listener,也就是Subscribe的onUpdate函数,用来强制刷新视图
      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);
  }
}

Контейнер содержит три метода: state, listeners и setState, subscribe и unsubscribe.

  • State для хранения данных, listeners — это массив, храните функцию для обновления представления.

  • subscribe поместит обновленную функцию (onUpdate внутри компонента Subscribe) в linsteners.

  • Установите и отреагируйте сходство SetState. При выполнении он возвращает новое состояние на основе изменений, В то время как циркулирующие слушатели звоните, в котором функция обновления. Достичь эффекта обновления страницы.

  • unsubscribe используется для отказа от подписки.

Provider

Поставщик по существу возвращает StateContext.Provider.

export function Provider(ProviderProps) {
  return (
    <StateContext.Consumer>
      {parentMap => {
        let 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>
  );
}

Он сам получает свойство inject, и после обработки оно передается в контекст как значение context. Видно, что входящее значение представляет собой карту, использующую класс Container в качестве ключа и экземпляр класса Container в качестве значения. Подписчик получит эту карту и сначала использует ее для создания экземпляра класса Container,Данные инициализации.

Некоторые люди могли заметить, что Provider — это не StateContext.Provider, возвращаемый напрямую, а слой. StateContext.Потребитель. Цель этого состоит в том, что провайдеры также могут быть вложены в провайдеры. Значение внутреннего провайдера может быть унаследовано от внешнего уровня.

Subscribe

Проще говоря, это мост между компонентами и классами управления состоянием, который можно представить как роль подключения в react-redux.

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>
    );
  }
}

Более важными здесь являются два метода _createInstances и onUpdate. StateContext.Consumer получает карту, переданную Provider, Он передается в _createInstances вместе с to, полученным реквизитами.

onUpdate: больше ничего не делает, просто обновляет представление с помощью setState и возвращает обещание. Смысл его существования в том, что при подписке, Передайте подписку класса Container в качестве параметра, разверните массив слушателей класса Container, а затем после того, как setState класса Container изменит состояние, Каждый элемент в слушателях цикла — это метод onUpdate, который выполняет и обновляет представление.

_createInstances: map — это данные экземпляра управления состоянием инжекта в провайдере. Если вводится, используйте карту для создания экземпляра данных, В противном случае создайте его экземпляр с классом управления состоянием this.props.to. Затем вызовите метод instance.subscribe (то есть подпишитесь в Container), Передайте собственный onUpdate для реализации подписки. Смысл его существования заключается в создании экземпляра класса Container и подписке собственного onUpdate на экземпляр класса Container, Наконец, экземпляр этого класса Container возвращается как параметр this.props.children и вызывается, поэтому внутри компонента можно выполнять такие операции:

 <Subscribe to={[CounterContainer]}>
   {
     counter => {
       return <div>
         <Button onClick={counter.increment}>增加</Button>
         <Button onClick={counter.decrement}>减少</Button>
       </div>
     }
   }
</Subscribe>

Суммировать

Unstated легко начать, и нетрудно понять исходный код. Ключевым моментом является понимание идеи публикации (класс Container) и компонента Subscribe для реализации подписки. Дизайн его API соответствует философии дизайна React. То есть, если вы хотите изменить пользовательский интерфейс, вы должны установить состояние. Кроме того, вы можете избежать написания большого количества шаблонного кода, такого как Redux.

В процессе понимания исходного кода меня вдохновили следующие две статьи, за что искренне благодарю:

Чисто минимальный компонент управления состоянием реакции не указан

Анализ неустановленного