Рубрики
Без рубрики

Простые шаблоны реагирования

Простые дизайн шаблонов для лучшего React Code.

Автор оригинала: Lucas Reis.

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

В качестве образца Spec, давайте построим приложение, которое выбирает информацию о планете Дагоба из API Star Wars и показывает его пользователю.

Простые повседневные узоры

Около 95% кода, написанного каждый день, будут либо простыми представлениями компонентов или компонентов с какой-то логикой. Это первый рисунок, и это самый простой:

Ваниль или смешанный узор

export default class Dagobah extends React.Component {
  // State:
  // { loading: true }
  // { loading: false, planet: { name, climate, terrain } }
  // { loading: false, error: any }
  state = { loading: true };

  componentDidMount() {
    fetch("https://swapi.co/api/planets/5")
      .then(res => res.json())
      .then(
        planet => this.setState({ loading: false, planet }),
        error => this.setState({ loading: false, error })
      );
  }

  renderLoading() {
    return 
Loading...
; } renderError() { return
I'm sorry! Please try again.
; } renderPlanet() { const { name, climate, terrain } = this.state.planet; return (

{name}

Climate: {climate}
Terrain: {terrain}
); } render() { if (this.state.loading) { return this.renderLoading(); } else if (this.state.planet) { return this.renderPlanet(); } else { return this.renderError(); } } }

Это Ваниль Актуальный компонент, то, что вы будете писать после прочтения документов. Написание компонента, подобного этому, имеет свои преимущества: главное, что это легко, и это самостоятельно. Подключите <Дагоба/> В любом месте в вашем приложении, и он будет получать и отображать данные.

Боковая заметка : Всякий раз, когда мы имеем дело с привлечением данных откуда-то таким образом, который может занять время или потерпеть неудачу, Нам нужно определить мнения для этих государств Отказ Нам всегда нужно определить представление для состояния загрузки и представление для состояния ошибки. Ни одна сеть не идеальна, и нам нужно подготовить наше приложение для проблем! Вы даже можете определить более сложную логику, например, ждущие миллисекунды, прежде чем показывать нагрузку, чтобы избежать мигающих экранов, и так далее. Это отличный предмет, но я не пойду дальше в этом посте в блоге. Я придерживаюсь простой загрузки/ошибки/шаблона успеха во всех примерах.

Итак, какие проблемы могут иметь этот компонент? Допустим, мы хотим использовать инструмент для руководства в стиле, как Статьгор Чтобы сделать компонент во всех трех государствах, чтобы иметь возможность пользы каждой версии или даже продемонстрировать ее другим командам. Является ли это возможным? Что, если я захочу установить проверку представления, не выбирая данные каждый раз, или без издевательства запросов? Это не произойдет.

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

Контейнер/вид вид

class PlanetView extends React.Component {
  renderLoading() {
    return 
Loading...
; } renderError() { return
I'm sorry! Please try again.
; } renderPlanet() { const { name, climate, terrain } = this.props.planet; return (

{name}

Climate: {climate}
Terrain: {terrain}
); } render() { if (this.props.loading) { return this.renderLoading(); } else if (this.props.planet) { return this.renderPlanet(); } else { return this.renderError(); } } } // State: // { loading: true } // { loading: false, planet: { name, climate, terrain } } // { loading: false, error: any } class DagobahContainer extends React.Component { state = { loading: true }; componentDidMount() { fetch("https://swapi.co/api/planets/5") .then(res => res.json()) .then( planet => this.setState({ loading: false, planet }), error => this.setState({ loading: false, error }) ); } render() { return ; } } export default DagobahContainer;

Вот и все. Вы только что видели большинство кода, я пишу сегодня. Простой универсальный компонент только для просмотра – только компонент Planet и компонент только для логики, который просто вызывает представление в своей функции рендера.

С разделенным компонентом View мы можем очень легко использовать его в руководстве в стиле, и тонкая настройте каждый из вариантов, просто обеспечивая разные реквизиты. Кроме того, мы можем легко проверить вид, используя Фермент например.

Кроме того, мой опыт показывает, что этот шаблон масштабируется лучше: возможно, одно из состояний просмотра становится большим, и это просто извлечь его через новый компонент. На логике, также гораздо проще понимать и изменять код, не загрязненный с видом, связанным с ними.

Обратите внимание, что компонент View имеет некоторую «если» логику определить, что для рендеринга. Мы можем извлечь его в свой собственный компонент, в чем можно считать вариант контейнера/вид на вид:

Контейнер/филиал/вид вид

const LoadingView = () => 
Loading...
; const ErrorView = () =>
I'm sorry! Please try again.
; const PlanetView = ({ name, climate, terrain }) => (

{name}

Climate: {climate}
Terrain: {terrain}
); const PlanetBranch = ({ loading, planet }) => { if (loading) { return ; } else if (planet) { return ; } else { return ; } }; // State: // { loading: true } // { loading: false, planet: { name, climate, terrain } } // { loading: false, error: any } class DagobahContainer extends React.Component { state = { loading: true }; componentDidMount() { fetch("https://swapi.co/api/planets/5") .then(res => res.json()) .then( planet => this.setState({ loading: false, planet }), error => this.setState({ loading: false, error }) ); } render() { return ; } } export default DagobahContainer;

Теперь отдельные взгляды еще более изолированы, что может помочь тестированию, демонстрации и рабочем процессе развития. Решив, сколько сломать представление лучше всего сделать с помощью случая по сравнению с анализом, а правило большого пальца – это просто понять. Это может много варьироваться, поэтому используйте ваше лучшее суждение!

Единственная ситуация, которую эти начальные модели не полезны для того, когда нам нужно повторно использовать логику разными видами Отказ Это могут быть интересные случаи, и есть два основных способа борьбы с ними. Давайте начнем с «старшего» одного:

Компоненты высшего порядка

Компоненты более высокого порядка (HOCS) являются просто функциями, которые принимают как минимум один компонент в качестве параметра и возврат другого компонента. Обычно он добавляет опоры пропущенным компонентом после выполнения некоторой работы. Например, мы могли бы иметь сдагобах HOC, который выбирает информацию о Дагобе и передает результат как опоры:

const withDagobah = PlanetViewComponent =>
  class extends React.Component {
    state = { loading: true };

    componentDidMount() {
      fetch("https://swapi.co/api/planets/5")
        .then(res => res.json())
        .then(
          planet => this.setState({ loading: false, planet }),
          error => this.setState({ loading: false, error })
        );
    }

    render() {
      return ;
    }
  };

export default withDagobah(PlanetBranch);

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

Примечание: Если вы используете этот HOC в двух компонентах, представляемых на одном экране, он будет привлекать данные дважды. Получение данных – дорогой побочный эффект, и, как правило, мы стараемся сделать это как можно меньше. Я расскажу о том, как разобраться с ним в последнем шаблоне этого поста, так что продолжайте читать!

HOC также может принять разные параметры для определения его поведения. Мы могли бы иметь, например, спланет HOC, который выбирает разные планеты:

const hoc = withPlanet('tatooine');
const Tatooine = hoc(PlanetView);

// somewhere else inside a component:
render() {
  return (
    
); }

Пример HOC с этой способностью React – SizeMe Отказ Он получает объект параметров и компонент и возвращает другой компонент с помощью Размер опоры, содержащие высоту, ширину и информацию о положении.

Итак, каковы минусы HOCS? Первый болезненный – это то, что каждый вид, который будет использоваться с HOC, должен понять форму прошедших реквизитов. В нашем примере мы добавляем Загрузка , Ошибка . и планета И наши взгляды нужно подготовить к нему. Иногда мы должны иметь компонент, единственная цель которого преобразует реквизит в предполагаемые, и что чувствует неэффективность (интересно, одно из самых используемых HOC не имеет этой проблемы: React-redux ‘s Connect , потому что пользователь решает форму реквизитов, переданных на компонент просмотра).

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

Вариация: разветвление компонентов высшего порядка

Мы можем положить логику ветвления внутри HOC:

const withDagobah = ({
  LoadingViewComponent,
  ErrorViewComponent,
  PlanetViewComponent
}) =>
  class extends React.Component {
    state = { loading: true };

    componentDidMount() {
      fetch("https://swapi.co/api/planets/5")
        .then(res => res.json())
        .then(
          planet => this.setState({ loading: false, planet }),
          error => this.setState({ loading: false, error })
        );
    }

    render() {
      if (this.state.loading) {
        return ;
      } else if (this.state.planet) {
        return ;
      } else {
        return ;
      }
    }
  };

// and the HOC would be called like this:
export default withDagobah({
  LoadingViewComponent: LoadingView,
  ErrorViewComponent: ErrorView,
  PlanetViewComponent: PlanetView
});

Здесь есть компромисс: даже если взгляды проще, внутри HOC больше логики. Это того стоит только, если вы знаете, что будет использоваться более одного представления и что логика ветвления будет одинаковым каждый раз. Пример разветвленного HOC является React-Loadable , который принимает как динамически загруженный компонент, так и компонент загрузки, который обрабатывает как загрузку, так и состояние ошибки.

Рендеринг

Существует еще один широко используемый рисунок, который отделяет логику с вида, рендеринговые реквизиты (также известные как дети как функция). Некоторые люди клянутся этим и некоторые люди Рассмотрим его анти-образцом Отказ В стороне мнения, вот как наша логика Дагоба будет реализована в виде рендеринга:

class DagobahRP extends React.Component {
  state = { loading: true };

  componentDidMount() {
    fetch("https://swapi.co/api/planets/5")
      .then(res => res.json())
      .then(
        planet => this.setState({ loading: false, planet }),
        error => this.setState({ loading: false, error })
      );
  }

  render() {
    return this.props.render(this.state);
  }
}

// notice that a function is passed to the render prop:
export default () => (
   {
      if (loading) {
        return ;
      } else if (planet) {
        return ;
      } else {
        return ;
      }
    }}
  />
);

Примечание: В рендеринговых дебатах реквизиты выпуск производительности был поднят много раз. Этот пост показывает, как Это не простой выпуск Отказ В любое время мы говорим о производительности, мы также должны говорить о измерениях. Если у вас есть какие-либо сомнения о производительности по конкретной проблеме, загрузите свои профилировщики и измерите его!

Я склонен чувствовать, что преимущества HOCs по сравнению с рендерингом варьируются от ситуации в ситуации. На моей предыдущей работе мы имели тенденцию писать больше рендеринга, и на моей нынешней работе мы склонны писать больше HOC, и я не чувствую, что эти выборы сделали любой из команд более продуктивными, либо код более читаемыми в целом. Я чувствую, что один узор лучше другого, всякий раз, когда я вижу это в коде, но, как я уже сказал, это в каждом конкретном случае. Как всегда, используйте ваше лучшее суждение.

Первый раз, когда я видел, что образец рендеринга был в Библиотека движения реагирования Отказ React Router V4 Это еще одна большая библиотека, реализующая ее. Два авторам, вероятно, являются самыми влиятельными энтузиастами рендеринга, и у них есть пара других Небольшие библиотеки Используя это Отказ

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

Вариация: разветвление рендеринга

class DagobahRP extends React.Component {
  state = { loading: true };

  componentDidMount() {
    fetch("https://swapi.co/api/planets/5")
      .then(res => res.json())
      .then(
        planet => this.setState({ loading: false, planet }),
        error => this.setState({ loading: false, error })
      );
  }

  render() {
    if (this.state.loading) {
      return this.props.renderLoading();
    } else if (this.state.planet) {
      return this.props.renderPlanet(this.state.planet);
    } else {
      return this.props.renderError(this.state.error);
    }
  }
}

// different callback for different branches:
export default () => (
   }
    renderError={error => }
    renderPlanet={planet => }
  />
);

Вот и все.

Что если побочные эффекты дорого?

Во многих ситуациях логика, предоставленная HOC или рендерингом, приводит к дорогим коде, который мы хотим избегать, если это возможно. Наиболее распространенным корпусом является удаленно получает данные. В нашем случае Dagobah мы хотели бы, например, чтобы получить только данные планеты только один раз, и сделать его доступным для просмотра компонентов через HOCS или рендеринг. Как бы мы этого достигли?

Схема провайдера

Это один из самых мощных узоров в реакции. Это относительно простое: вы собираете свои данные, поместите его в объект ACCExt Context, а затем в HOC (или RENDER SPOP) вы получаете доступ к объекту контекста и передаете его как опоры на предполагаемые компоненты. Если вы не знаете, какой объект контекста находится в реакции, пожалуйста, Перейти к официальным документам Отказ

Давайте реализуем его для нашего прибора Дагоба. Во-первых, нам нужно реализовать Дагобахпровидер :

import React from "react";
import PropTypes from "prop-types";

// IMPORTANT: we need to define childContextTypes
// to be able to access the context object in React
const contextTypes = {
  dagobah: PropTypes.shape({
    loading: PropTypes.bool,
    error: PropTypes.object,
    planet: PropTypes.shape({
      name: PropTypes.string,
      climate: PropTypes.string,
      terrain: PropTypes.string
    })
  })
};

export class DagobahProvider extends React.Component {
  state = { loading: true };

  componentDidMount() {
    fetch("https://swapi.co/api/planets/5")
      .then(res => res.json())
      .then(
        planet => this.setState({ loading: false, planet }),
        error => this.setState({ loading: false, error })
      );
  }

  static childContextTypes = contextTypes;

  getChildContext() {
    return { dagobah: this.state };
  }

  render() {
    return this.props.children;
  }
}

Поставщик использует ту же логику, которую мы имели раньше, и он обрабатывается в ComponentDidmount метод. Единственное отличие от предыдущих реализаций в том, что он добавляет Дагоба Свойство на объект контекста, через getchildcontext метод. Затем он просто делает своих детей, вернув детей реквизит в метод оказания.

Теперь любой компонент под провайдером будет иметь доступ к Дагоба объект внутри контекста. Но доступ к объекту контекста в компоненте обычно считается плохим практикой, поскольку контекст своего рода «невидимый» вход, и он делает тестирование и рассуждение по поводу кода немного более жесткой. Давайте реализуем HOC для доступа к контексту и ввести Дагоба Объект в компонентных реквизитах:

const withDagobah = PlanetViewComponent =>
  class extends React.Component {
    static contextTypes = contextTypes;

    render() {
      const { props, context } = this;
      return ;
    }
  };

Легко, верно? Обратите внимание на ContextTypes Недвижимость: нам нужно, чтобы он был определен с той же схемой провайдера, который сможет получить доступ к контексту. Затем мы распространяем его до прохожденного компонента. Таким образом, мы можем использовать столько сдагобах На одном экране, и данные будут вынесены только один раз!

И, конечно же, мы также могли получить доступ к контексту через рендеринг ред:

const DagobahRp = ({ render }, { dagobah }) => render(dagobah);

DagobahRp.contextTypes = contextTypes;

Очень просто! И именно так мы могли бы использовать его в приложении:

const DagobahPlanet = withDagobah(View);

class App extends Component {
  render() {
    return (
      
        
        
        
         } />
         } />
         } />
      
    );
  }
}

Дагоба будет оказана шесть раз, а данные будут только один раз.

Многие библиотеки используют шаблон провайдера, включая вышеупомянутые React-redux и React Router V4 Отказ Racted-intl также хороший пример картины.

Возвращаясь к процентам, я бы сказал, что мой React Code (и большинство я сталкивался) составляет около 99%, написанные эти шаблоны. Другой 1% был бы странным интеграционным кодом с некоторыми недействительными библиотеками. Кроме того, большинство основных библиотек React Reacts упадут в одну из категорий выше! Узнайте, как они работают, почему они существуют, и вы поймете большую часть мира реагирования

Резюме

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

Код, используемый в этом посте, может быть найденным здесь Отказ