переключение между локальным и глобальным состоянием

1656643089 pereklyuchenie mezhdu lokalnym i globalnym sostoyaniem

автор Диего Хаз

hsGY6oBk3sKTflDtEqRl8jGKiq5ITT7xFiBC

Рассмотрим компонент, обрабатывающий состояние видимости и передающий его своим дочерним элементам через атрибуты визуализации:

const PopoverContainer = () => (  <VisibilityContainer>    {({ toggle, hidden }) => (      <div>        <button onClick={toggle}>PopoverButton</button&gt;        <div hidden={hidden}>PopoverContent</div>      </div>    )}  </VisibilityContainer>);
bR7Wyd-qYPxK46wEMMsZyBwQuj8ZZc4pHrQy

Что бы вы думали о возможности создать такое состояние глобальный просто изменив a context свойство компонента?

const PopoverButton = () => (  <VisibilityContainer context="popover1">    {({ toggle }) => (      <button onClick={toggle}>PopoverButton</button>    )}  </VisibilityContainer>);
const PopoverContent = () => (  <VisibilityContainer context="popover1">    {({ hidden }) => (      &lt;div hidden={hidden}>PopoverContent</div>    )}  </VisibilityContainer>);

Именно этого мы собираемся добиться в этой статье.

Контекст и состояние

Во-первых, прежде чем говорить о контекст и состояние в React, позвольте предоставить вам некоторые контекст на состояние этой темы(!).

Несколько месяцев назад я опубликовал reas, экспериментальный набор пользовательских интерфейсов на базе React и styled-components.

Кроме самих компонентов, я хотел предоставить помощников для управления их состоянием. Подход, который я применил тогда, заключался в экспорте некоторых компонентов высокого порядка (HOC), таких как withPopoverContainerчтобы контролировать состояние видимости a Popover компонент. Посмотрите на этот пример:

import { Popover, withPopoverContainer } from "reas";
const MyComponent = ({ toggle, visible }) => (  <div>    <button onClick={toggle}>Toggle</button>    <Popover visible={visible}>Popover</Popover>  </div>);
export default withPopoverContainer(MyComponent);

Но HOC имеют некоторые проблемы, такие как столкновение имен. Что делать, если другой HOC или родительский компонент передает свой собственный toggle опора на MyComponent? Вещи обязательно сломаются.

Еще до этого, вдохновленная Майклом Джексоном и его великолепной речью, сообщество React начало использовать рендеринг вместо HOC.

Кроме того, React v16.3.0 представил новый контекстный API, заменивший старый нестабильный, используя рендеринг-пропсы.

Я научился критично смотреть на все те вещи, которые вызывают ажиотаж, особенно на то, что нарушает сообщество JavaScript. Это сохраняет мой разум в здравом уме и лишает меня потребности каждый день рефакторировать свой код с помощью новых интересных библиотек.

Наконец, я опубликовал твит с вопросом у людей, что они предпочитают: визуализацию или HOC. Все комментарии были привержены реперам визуализации, что в конечном итоге заставило меня превратить все HOC в reas в компоненты с рендерингами:

import { Popover } from "reas";
const MyComponent = () => (  <Popover.Container>    {({ toggle, visible }) => (      <div>        <button onClick={toggle}>Toggle&lt;/button&gt;        <Popover visible={visible}>Popover</Popover>      </div>    )}  </Popover.Container>);
export default MyComponent;

Popover.Container был обычным классом компонентов React с a toggle использование метода this.setState изменять this.state.visible. Просто как это.

Это было хорошо и работало достаточно хорошо. Однако в одном из моих проектов у меня был button который должен был контролировать Popover компонент, расположенный на совсем другом пути в дереве React.

Мне или нужен был какой-то глобальный менеджер состояния, как Redux, или мне нужно было переехать Popover.Container вверх на дереве в общем отце и передайте опоры вниз, пока они не коснутся обоих button и Popover . Но это звучало как ужасная идея.

Кроме того, настройка Redux и переписка всей логики, которую я уже имел this.setState в действии и редукторы просто иметь эту функциональность было бы ужасной работой.

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

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

Constate

pvU1j2TKHu1rm1dVouIoSLZpuwYhU8elzO3e

Давайте посмотрим как PopoverContainer будет выглядеть из constate:

import React from "react";import { Container } from "constate";
const PopoverContainer = props => (  <Container    initialState={{ visible: false }}    actions={{      toggle: () => state =>; ({ visible: !state.visible })    }}    {...props}  />);
export default PopoverContainer;

Теперь мы можем обратить наш компонент PopoverContainer чтобы иметь доступ к visible и toggle участники уже прошли мимо Container к children функция как довод.

Также обратите внимание, что мы передаем все реквизиты, полученные от PopoverContainer к Container. Это означает, что мы можем составить его для создания нового производного компонента состояния, например AdvancedPopoverContainerс новым initialState и actions.

Под капотом

Если вы похожи на меня и хотите знать, как все было реализовано под капотом, вы, вероятно, думаете о том, как Container было реализовано. Итак, давайте воспроизведем простой Container компонент:

import React from "react";
class Container extends React.Component {  state = this.props.initialState;
  render() {    return this.props.children({      ...this.state,      ...mapStateToActions(...)    });  }}
export default Container;

mapStateToActions это полезная функция, передающая состояние каждому члену actions. Именно это позволяет определить нашу toggle функционировать так:

const actions = {  toggle: () =&gt; state => ({ visible: !state.visible})};

Однако наша цель состоит в том, чтобы иметь возможность использовать то же самое PopoverContainer как глобальное правительство. С constate нам просто нужно передать a context опора на Container:

<PopoverContainer context="popover1">  {({ toggle }) => (    <button onClick={toggle}>PopoverToggle</button>  )}</PopoverContainer>

Теперь каждый Container с context="popover1" будет иметь то же состояние.

Конечно, вам интересно как Container справляется с этим context опора Итак, вот:

import React from "react";import Consumer from "./Consumer";
class Container extends React.Component {  state = this.props.initialState;
  render() {    if (this.props.context) {      return <Consumer {...this.props} />;    }
    return this.props.children({      ...this.state,      ...mapStateToActions(...)    });  }}
export default Container;

Хорошо извините. Эти четыре добавленные строчки не говорят вам много. Создать Consumerнам нужно понять, как работать с новым React Context API.

Контекст реакции

Мы можем разбить новый React Context API на три части: Context, Provider и Consumer.

Давайте создадим контекст:

import React from "react";
const Context = React.createContext();
export default Context;

Тогда мы создаем свой Providerкоторый использует Context.Provider и проходит state и setState вниз:

import React from "react";import Context from "./Context";
class Provider extends React.Component {  handleSetState = fn => {    this.setState(state => ({      state: fn(state.state)    }));  };
  state = {    state: this.props.initialState,    setState: this.handleSetState  };
  render() {    return (      <Context.Provider value={this.state}>        {this.props.children}      </Context.Provider>    );  }}
export default Provider;

Это может быть немного сложно. Мы не можем просто пройти { state, setState } как буквальный объект к Context.Provider‘s value поскольку он воспроизводил этот объект во время каждого рендера. Узнайте больше здесь.

Наконец-то наш Consumer требует использования Context.Consumer доступа state и setState проходил мимо Provider:

import React from "react";import Context from "./Context";
const Consumer = ({ context, children, actions }) => (  <Context.Consumer>    {({ state, setState }) =&gt; children({      ...state[context],      ...mapContextToActions(...)    })}  </Context.Consumer>);
export default Consumer;

mapContextToActions похож на mapStateToActions. Разница в том, что прежние карты state[context] вместо просто state.

Последним шагом является обертывание нашей программы Provider:

import React from "react";import ReactDOM from "react-dom";import Provider from "./Provider";
const App = () => (  <Provider>    ...  &lt;/Provider>);
ReactDOM.render(<App />, document.getElementById("root"));

Наконец-то мы переписали constate. Теперь можно использовать Container компонент для лёгкого переключения меж локальным и глобальным состоянием.

Вывод

Вы можете подумать, что начало проекта с чем-то вроде constate также может быть преждевременной оптимизацией. И, пожалуй, вы правы. Вы должны соблюдать this.setState без абстракций, пока это возможно.

Однако не все преждевременная оптимизация — корень всего зла. Вы должны найти хороший баланс между простотой и масштабируемостью. То есть вам следует стремиться к простым реализации, особенно если вы создаете небольшие программы. Но если вы планируете развиваться, вам следует искать простые реализации, которые также легко масштабировать.

Спасибо, что прочли это!

Если вам это нравится и полезно, вот несколько вещей, которые вы можете сделать, чтобы выразить свою поддержку:

Добавить комментарий

Ваш адрес email не будет опубликован.