Интенсивное чтение "неустановленного и неустановленного-следующего исходного кода"

JavaScript

1. Введение

unstatedявляется библиотекой управления потоком данных на основе компонента класса,unstated-nextЭто обновленная версия функционального компонента с оптимизированной поддержкой хуков.

По сравнению с библиотекой, подобной редуксу, дизайн этой библиотеки гениален, а количество строк исходного кода этих двух библиотек очень мало. пространство для воображения больше, а использование интуитивно понятно, поэтому интенсивное чтение на этой неделе будет анализировать эти две библиотеки с точки зрения использования и исходного кода.

2 Обзор

Прежде всего, что такое поток данных? React сам обеспечивает поток данных, т.е.setStateа такжеuseState, Смысл существования Framework DataFlow Framework для устранения перекрестного обмена данными и пакетом бизнеса.

Также существует поговорка о том, что React на первых порах позиционировал себя как UI-фреймворк и не заботился о данных, поэтому экосистеме нужно было предоставлять плагины для потока данных, чтобы компенсировать эту возможность. Но на самом деле то, что дает ReactcreateContextа такжеuseContextЭта проблема была решена, но использовать ее немного проблематично, и неустановленная серия призвана решить эту проблему.

unstated

Unstated решает проблему совместного использования данных компонентов в сценарии Class Component.

По сравнению с выбрасыванием употребления напрямую автор восстанавливает авторский мыслительный процесс: использование родногоcreateContextДля реализации потока данных требуются два компонента пользовательского интерфейса, и реализация является подробной:

const Amount = React.createContext(1);

class Counter extends React.Component {
  state = { count: 0 };
  increment = amount => {
    this.setState({ count: this.state.count + amount });
  };
  decrement = amount => {
    this.setState({ count: this.state.count - amount });
  };
  render() {
    return (
      <Amount.Consumer>
        {amount => (
          <div>
            <span>{this.state.count}</span>
            <button onClick={() => this.decrement(amount)}>-</button>
            <button onClick={() => this.increment(amount)}>+</button>
          </div>
        )}
      </Amount.Consumer>
    );
  }
}

class AmountAdjuster extends React.Component {
  state = { amount: 0 };
  handleChange = event => {
    this.setState({
      amount: parseInt(event.currentTarget.value, 10)
    });
  };
  render() {
    return (
      <Amount.Provider value={this.state.amount}>
        <div>
          {this.props.children}
          <input
            type="number"
            value={this.state.amount}
            onChange={this.handleChange}
          />
        </div>
      </Amount.Provider>
    );
  }
}

render(
  <AmountAdjuster>
    <Counter />
  </AmountAdjuster>
);

И что мы делаем,будуsetStateУдалено из определенного компонента пользовательского интерфейса для формирования сущности объекта данных, которую можно внедрить в любой компонент.

ЭтоunstatedКак использовать:

import React from "react";
import { render } from "react-dom";
import { Provider, Subscribe, Container } from "unstated";

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

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

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

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

render(
  <Provider>
    <Counter />
  </Provider>,
  document.getElementById("root")
);

первый дляProviderПравильное имя:ProviderЭто лучшее решение для хранилища размеров.Когда проект и компоненты используют поток данных, когда требуется объем,ProviderЭто пригодилось. Если проекту нужен только один поток данных Store, поместите его с корневым узлом.Providerэквивалентность.

ВторойCounterContainerСтаньте настоящим классом обработки данных, отвечающим только за хранение и обработку данных, через<Subscribe to={[CounterContainer]}>Метод RenderProps будетcounterВнедрено в функцию Render.

По существу неустановленное использование программыsetState, но будетsetStateУдален из пользовательского интерфейса и может быть легко внедрен в любой компонент.

Точно так же его модернизированная версияunstated-nextпо существу используетсяuseState, используя тот факт, что пользовательские хуки можно отделить от пользовательского интерфейса, а такжеuseContextУдобство использования менее 40 строк кода для достижения больше, чемunstatedболее мощные функции.

unstated-next

unstated-nextНазвал финальную версию библиотеки управления данными React в 40 строк кода, посмотрим, как она это сделает!

Или из процесса мышления автор обнаружил, что его README также обеспечивает соответствующий процесс мышления, и взял код в README в качестве примера.

Во-первых, используя функциональный компонент, вы должны использовать поток данных следующим образом:

function CounterDisplay() {
  let [count, setCount] = useState(0);
  let decrement = () => setCount(count - 1);
  let increment = () => setCount(count + 1);
  return (
    <div>
      <button onClick={decrement}>-</button>
      <p>You clicked {count} times</p>
      <button onClick={increment}>+</button>
    </div>
  );
}

Если вы хотите отделить данные от пользовательского интерфейса, вы можете сделать это с помощью Custom Hooks без какого-либо фреймворка:

function useCounter() {
  let [count, setCount] = useState(0);
  let decrement = () => setCount(count - 1);
  let increment = () => setCount(count + 1);
  return { count, decrement, increment };
}

function CounterDisplay() {
  let counter = useCounter();
  return (
    <div>
      <button onClick={counter.decrement}>-</button>
      <p>You clicked {counter.count} times</p>
      <button onClick={counter.increment}>+</button>
    </div>
  );
}

Если вы хотите поделиться этими данными с другими компонентами, используйтеuseContextЭто можно сделать без каких-либо рамок:

function useCounter() {
  let [count, setCount] = useState(0);
  let decrement = () => setCount(count - 1);
  let increment = () => setCount(count + 1);
  return { count, decrement, increment };
}

let Counter = createContext(null);

function CounterDisplay() {
  let counter = useContext(Counter);
  return (
    <div>
      <button onClick={counter.decrement}>-</button>
      <p>You clicked {counter.count} times</p>
      <button onClick={counter.increment}>+</button>
    </div>
  );
}

function App() {
  let counter = useCounter();
  return (
    <Counter.Provider value={counter}>
      <CounterDisplay />
      <CounterDisplay />
    </Counter.Provider>
  );
}

Но это все еще показывает, чтоuseContextAPI и дляProviderПакет не образует фиксированного шаблона, которыйusestated-nextпроблема, которую нужно решить.

Так что этоunstated-nextКак использовать:

import { createContainer } from "unstated-next";

function useCounter() {
  let [count, setCount] = useState(0);
  let decrement = () => setCount(count - 1);
  let increment = () => setCount(count + 1);
  return { count, decrement, increment };
}

let Counter = createContainer(useCounter);

function CounterDisplay() {
  let counter = Counter.useContainer();
  return (
    <div>
      <button onClick={counter.decrement}>-</button>
      <p>You clicked {counter.count} times</p>
      <button onClick={counter.increment}>+</button>
    </div>
  );
}

function App() {
  return (
    <Counter.Provider>
      <CounterDisplay />
      <CounterDisplay />
    </Counter.Provider>
  );
}

можно увидеть,createContainerЛюбые хуки можно обернуть в объект данных, который имеетProviderа такжеuseContainerдва API, гдеProviderиспользуется для ввода данных в область видимости, аuseContainerВы можете взять экземпляр этого объекта данных в текущей области.

initialStateУстановите данные инициализации, и различные области могут быть вложены друг в друга и им могут быть назначены разные значения инициализации:

function useCounter(initialState = 0) {
  let [count, setCount] = useState(initialState);
  let decrement = () => setCount(count - 1);
  let increment = () => setCount(count + 1);
  return { count, decrement, increment };
}

const Counter = createContainer(useCounter);

function CounterDisplay() {
  let counter = Counter.useContainer();
  return (
    <div>
      <button onClick={counter.decrement}>-</button>
      <span>{counter.count}</span>
      <button onClick={counter.increment}>+</button>
    </div>
  );
}

function App() {
  return (
    <Counter.Provider>
      <CounterDisplay />
      <Counter.Provider initialState={2}>
        <div>
          <div>
            <CounterDisplay />
          </div>
        </div>
      </Counter.Provider>
    </Counter.Provider>
  );
}

Видно, что React Hooks уже очень подходит для управления состоянием, а экология должна максимально использовать его возможности для инкапсуляции паттернов.

Кто-то может спросить, а как насчет извлечения и побочных эффектов?redux-sagaНикакого другого промежуточного программного обеспечения, этот поток данных является кастрированной версией?

Сначала давайте посмотрим, зачем Redux нужно промежуточное ПО для работы с побочными эффектами. Это потому чтоreducerЭто синхронная чистая функция, и ее возвращаемое значение заключается в том, что в результате операции не может быть асинхронности, и не может быть побочных эффектов, поэтому нам нужен асинхронный вызов.dispatchметод или побочная функция для хранения этой "грязной" логики.

А в Крючки мы можем позвонить в любое времяuseStateкоторый предоставилsetterФункция изменяет значение, которое давно решено естественным образомreducerПроблема невозможности быть асинхронной также решаетсяredux-chunkфункция.

И асинхронная функция тожеuseEffectЭто официальная замена React Hook.Мы видим, что это решение может использовать возможности, официально предоставляемые React, чтобы полностью покрыть возможности промежуточного программного обеспечения Redux и выполнить атаку уменьшения размерности на библиотеку Redux, поэтому решение для потока данных следующего поколения действительно существует с реализацией хуков..

Наконец, по сравнению со стоимостью самого REDUX и его экологической библиотеки (автор не талантлив, это уже давно понятно, когда MiddleWare), а Хуков явно меньше.

Часто люди отказываются от новой технологии не потому, что новая технология не очень хороша, а потому, что она может полностью свести на нет «конкурентное преимущество», приносимое старыми навыками, которые они осваивали в течение многих лет. Возможно, что опытный ткацкий мастер может ткать вручную в 5 раз эффективнее начинающего ученика, но после перехода на ткацкий станок эта разница вскоре сгладится, и старый ткацкий мастер окажется перед кризисом ликвидации, так что поддерживайте это старое ремесло Просто для защиты своих интересов. Надеюсь, что старые ткачи в каждой команде смогут взять на себя инициативу по внедрению ткацкого станка.

Глядя на промежуточное ПО выборки, нам обычно нужно решитьПолучение инкапсуляции бизнес-логикиа такжевыборка состояния инкапсуляции, может быть инкапсулирован через промежуточное ПО redux, черезdispatchрешить.

На самом деле, при мышлении Крюков, используяswr useSWRТо же самое можно решить:

function Profile() {
  const { data, error } = useSWR("/api/user");
}

Бизнес-логика извлечения инкапсулирована вfetcher, Это вSWRConfigContext.ProviderОн был введен, и объем также можно контролировать! Полностью используйте возможности контекста, предоставляемые React, вы можете почувствовать последовательность и простоту основного принципа, тем более простые и красивые математические формулы являются правдой.

И состояние выборки было инкапсулировано вuseSWR, с возможностью приостановки вам даже не нужно заботиться о статусе загрузки.

3 Интенсивное чтение

unstated

Давайте разберемсяunstatedЧто делает эта библиотека.

  1. использоватьProviderОбъявите сферу деятельности.
  2. поставкаContainerКак наследуемый класс, наследуйте его класс как хранилище.
  3. поставкаSubscribeStore внедряется как использование RenderProps, инжектируемый экземпляр Store указывается параметромtoОпределяется полученным экземпляром класса.

Для первого пункта,ProviderДля инициализации в среде Class ComponentStateContext, так что вSubscribeиспользуется в:

const StateContext = createReactContext(null);

export function Provider(props) {
  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>
  );
}

По второму пункту, дляContainer, который необходимо предоставить в МагазинsetStateAPI, согласно ReactsetStateСтруктура для достижения этого снова.

_listenersобъект и может бытьsubscribeа такжеunsubscribeДополнения и удаления.

_listenersНа самом деле сохраняется текущий связанный компонентonUpdateЖизненный цикл, затем вsetStateАктивировать рендеринг соответствующего компонента.onUpdateжизненный цикл поSubscribeФункция предоставляется, и последний вызовthis.setState, это вSubscribeчасть описания.

Ниже приведеныContainerРеализация кода:

export class Container<State: {}> {
  state: State;
  _listeners: Array<Listener> = [];

  constructor() {
    CONTAINER_DEBUG_CALLBACKS.forEach(cb => cb(this));
  }

  setState(
    updater: $Shape<State> | ((prevState: $Shape<State>) => $Shape<State>),
    callback?: () => void
  ): Promise<void> {
    return Promise.resolve().then(() => {
      let nextState;

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

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

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

      let promises = this._listeners.map(listener => listener());

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

  subscribe(fn: Listener) {
    this._listeners.push(fn);
  }

  unsubscribe(fn: Listener) {
    this._listeners = this._listeners.filter(f => f !== fn);
  }
}

Для третьего пункта,Subscribeизrenderфункция будетthis.props.childrenВыполняется как функция и передает соответствующий экземпляр Store в качестве параметра, что выполняется_createInstancesреализация функции.

_createInstancesиспользоватьinstanceofНайдите соответствующий экземпляр в классе Class и передайтеsubscribeсобственные компонентыonUpdateФункция передается в соответствующий Store_listeners, вызывается при отвязкеunsubscribeОтмените привязку, чтобы предотвратить ненужный рендеринг.

Ниже приведеныSubscribeИсходный код:

export class Subscribe<Containers: ContainersType> extends React.Component<
  SubscribeProps<Containers>,
  SubscribeState
> {
  state = {};
  instances: Array<ContainerType> = [];
  unmounted = false;

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

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

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

  _createInstances(
    map: ContainerMapType | null,
    containers: ContainersType
  ): Array<ContainerType> {
    this._unsubscribe();

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

    let safeMap = map;
    let 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Состояние — это внешний пользовательский прослушиватель, реализованный в магазине.setStateСрабатывает при сбореSubscribeРерандер компонента.

unstated-next

unstated-next

  1. поставкаcreateContainerИнкапсулирует пользовательские крючки в качестве объекта данных, предоставляяProviderвводят сuseContainerПолучите эти два метода Store.

Как было сказано в предыдущем анализе,unstated-nextМожно сказать, что хуки используются до предела.Считается, что хуки полностью оснащены всеми возможностями управления потоками данных.Нам нужно только упаковать слой спецификаций:

export function createContainer(useHook) {
  let Context = React.createContext(null);

  function Provider(props) {
    let value = useHook(props.initialState);
    return <Context.Provider value={value}>{props.children}</Context.Provider>;
  }

  function useContainer() {
    let value = React.useContext(Context);
    if (value === null) {
      throw new Error("Component must be wrapped with <Container.Provider>");
    }
    return value;
  }

  return { Provider, useContainer };
}

видимый,ProviderЭто дляvalueограниченный,Закрепите значение, возвращаемое хуками, непосредственно какvalueПерейти кContext.Providerэта спецификация.

а такжеuseContainerвот такReact.useContext(Context)упаковка.

Другой логики действительно нет.

Единственное, о чем стоит подумать, так это о том, что в кастомных хуках мы используемuseStateуправлять данными илиuseReducerВопрос управления данными является вопросом мнения. Однако мы можем вкладывать и инкапсулировать пользовательские хуки для поддержки некоторых более сложных сценариев данных, таких как:

function useCounter(initialState = 0) {
  const [count, setCount] = useState(initialState);
  const decrement = () => setCount(count - 1);
  const increment = () => setCount(count + 1);
  return { count, decrement, increment };
}

function useUser(initialState = {}) {
  const [name, setName] = useState(initialState.name);
  const [age, setAge] = useState(initialState.age);
  const registerUser = userInfo => {
    setName(userInfo.name);
    setAge(userInfo.age);
  };
  return { user: { name, age }, registerUser };
}

function useApp(initialState) {
  const { count, decrement, increment } = useCounter(initialState.count);
  const { user, registerUser } = useUser(initialState.user);
  return { count, decrement, increment, user, registerUser };
}

const App = createContainer(useApp);

4 Резюме

заниматьunstated-nextСлоганец: «Никогда не думай о реагирование библиотекам по управлению государством»unstated-nextНикогда больше не рассматривайте другое управление состоянием библиотеки React.

И что интересно,unstated-nextЭто всего лишь модель инкапсуляции хуков.Хуки могут очень хорошо решить проблему управления состоянием.Нам действительно не нужно «перестраивать» инструмент потока данных React.

Адрес обсуждения:Интенсивное чтение «неустановленного и неустановленного-следующего исходного кода» · Выпуск № 218 · dt-fe/weekly

Если вы хотите принять участие в обсуждении, пожалуйста,кликните сюда, с новыми темами каждую неделю, выходящими по выходным или понедельникам. Интерфейс интенсивного чтения — поможет вам отфильтровать надежный контент.

Сфокусируйся наАккаунт WeChat для интенсивного чтения в интерфейсе

Заявление об авторских правах: Бесплатная перепечатка - некоммерческая - не производная - сохранить авторство (Лицензия Creative Commons 3.0)