Автор оригинала: Rico Kahler.
И как крючки вступают в игру
Недавно я принял новую философию, которая меняет способ, которым я делаю компоненты. Это не обязательно новая идея, а скорее тонкий новый образ мышления.
Золотое правило компонентов
Опять же, это тонкое утверждение, и вы можете подумать, что вы уже следуете за этим Но это легко пойти против этого.
Например, скажем, у вас есть следующий компонент:
Если вы определили этот компонент «естественно», то вы, вероятно, напишите это со следующим API:
PersonCard.propTypes = {
name: PropTypes.string.isRequired,
jobTitle: PropTypes.string.isRequired,
pictureUrl: PropTypes.string.isRequired,
};Что довольно прост – исключительно смотрит на то, что он должен функционировать, вам просто нужно имя, заголовок работы и URL-адрес изображения.
Но скажем, у вас есть требование показать «официальную» изображение в зависимости от пользовательских настроек. Возможно, вы испытываете соблазн написать API так:
PersonCard.propTypes = {
name: PropTypes.string.isRequired,
jobTitle: PropTypes.string.isRequired,
officialPictureUrl: PropTypes.string.isRequired,
pictureUrl: PropTypes.string.isRequired,
preferOfficial: PropTypes.boolean.isRequired,
};Это может показаться, что компонент нуждается в этих дополнительных реквизитах для функционирования, но в действительности компонент не выглядит различным и не нуждается в этих дополнительных реквизитах для функции. Что эти дополнительные реквизиты делают это пару этого Предпочтение Установка с вашим компонентом и делает любое использование компонента за пределами того, что контекст чувствует себя действительно неестественно.
Преодоление разрыва
Поэтому, если логика для переключения URL-адреса изображений не принадлежит к самому компоненту, где это принадлежит?
Как насчет индекс файл?
Мы приняли структуру папки, где каждый компонент входит в самозагруженный папку, где индекс Файл отвечает за преодоление разрыва между вашим «натуральным» компонентом и внешним миром. Мы называем этот файл «контейнер» (вдохновленный из концепция redux redux “контейнер” компонентов ).
/PersonCard -PersonCard.js ------ the "natural" component -index.js ----------- the "container"
Мы определяем Контейнеры Как кусок кода, который мосты на этот разрыв между вашим природным компонентом и внешним миром. По этой причине мы также иногда называем эти вещи «форсунки».
Ваш Натуральный компонент Является ли код, который вы создадим, если вам показано только изображение того, что вам нужно сделать (без деталей, как вы получите данные или где это будет помещено в приложение – все, что вы знаете, это то, что это должен функционировать).
Внешний мир Это ключевое слово, которое мы будем использовать для обозначения любого ресурса вашего приложения (например, Store redux), которые могут быть преобразованы, чтобы удовлетворить реквизиты вашего природного компонента.
Цель для этой статьи: Как мы можем сохранить компоненты «естественными», не загрязняя их ненужным от внешнего мира? Почему это лучше?
Хотя эта статья сосредоточена на компонентах, контейнеры занимают основную часть этой статьи.
Почему?
Изготовление природных компонентов – легко, весело даже. Подключение ваших компонентов к внешнему миру – немного сложнее.
То, как я его вижу, есть три основных причины, по которым вы загрязняете свой естественный компонент с мусором из внешнего мира:
- Странные структуры данных
- Требования за пределами объема компонента (как пример выше)
- Сжигание событий на обновлениях или на горе
Следующие несколько разделов попытаются охватывать эти ситуации примерами с различными типами реализаций контейнера.
Работа со странными структурами данных
Иногда для того, чтобы сделать необходимую информацию, вам нужно связать данные вместе и преобразовывать ее во что-то более разумное. Для отсутствия лучшего слова «странные» структуры данных являются просто структурами данных, которые неестественно для использования вашего компонента.
Очень заманчиво передавать странные структуры данных непосредственно в компонент и выполнять преобразование внутри самого компонента, но это приводит к запутанным и часто сложном для тестирования компонентов.
Я попадал в эту ловушку недавно, когда мне было поручено создать компонент, который получил свои данные из определенной структуры данных, которые мы используем для поддержки определенного типа формы.
ChipField.propTypes = {
field: PropTypes.object.isRequired, // <-- the "weird" data structure
onEditField: PropTypes.func.isRequired, // <-- and a weird event too
};Компонент взял на себя этот странный поле Структура данных как опоры. В практичности это было бы хорошо, если бы нам никогда не пришлось снова дотронуться до того, как это стало реальной проблемой, когда нас попросили использовать его снова в другом месте, не связанным с этой структурой данных.
Поскольку компонент требовал этой структуры данных, невозможно было повторно повторно повторно повторно, и она сбивала с толку рефактору. Испытания, которые мы первоначально писали, также были запутаны, потому что они издевались на эту странную структуру данных. У нас были проблемы с пониманием тестов и неприятностей повторно написать их, когда мы в конечном итоге откинули.
К сожалению, странные структуры данных неизбежны, но использование контейнеров – отличный способ бороться с ними. Один вынос здесь, что архитектуру ваших компонентов таким образом дает вам Опция извлечения и окончания компонента в многоразовый. Если вы передаете странную структуру данных в компонент, вы потеряете этот вариант.
Реализация контейнеров с использованием компонентов функций
Если вы строго отображаете реквизиты, простой вариант реализации – использовать другой компонент функции:
import React from 'react';
import PropTypes from 'prop-types';
import getValuesFromField from './helpers/getValuesFromField';
import transformValuesToField from './helpers/transformValuesToField';
import ChipField from './ChipField';
export default function ChipFieldContainer({ field, onEditField }) {
const values = getValuesFromField(field);
function handleOnChange(values) {
onEditField(transformValuesToField(values));
}
return ;
}
// external props
ChipFieldContainer.propTypes = {
field: PropTypes.object.isRequired,
onEditField: PropTypes.func.isRequired,
};И структура папки для компонента, как это выглядит что-то вроде:
/ChipField
-ChipField.js ------------------ the "natural" chip field
-ChipField.test.js
-index.js ---------------------- the "container"
-index.test.js
/helpers ----------------------- a folder for the helpers/utils
-getValuesFromField.js
-getValuesFromField.test.js
-transformValuesToField.js
-transformValuesToField.test.jsВозможно, вы думаете: «Это слишком много работы» – и если вы, тогда, я получаю это. Может показаться, что есть еще больше работы, поскольку есть больше файлов и немного косвенного, но вот часть, которую вы пропустите:
import { connect } from 'react-redux';
import getPictureUrl from './helpers/getPictureUrl';
import PersonCard from './PersonCard';
const mapStateToProps = (state, ownProps) => {
const { person } = ownProps;
const { name, jobTitle, customPictureUrl, officialPictureUrl } = person;
const { preferOfficial } = state.settings;
const pictureUrl = getPictureUrl(preferOfficial, customPictureUrl, officialPictureUrl);
return { name, jobTitle, pictureUrl };
};
const mapDispatchToProps = null;
export default connect(
mapStateToProps,
mapDispatchToProps,
)(PersonCard);Это все же такое же количество работы независимо от того, если вы преобразовали данные вне компонента или внутри компонента. Разница в том, что когда вы преобразуете данные вне компонента, вы даете себе более явное место для проверки того, что ваши преобразования верны, а также разделяющие проблемы.
Выполнение требований за пределами объема компонента
Подобно приведенному выше приведенное выше пример карты, это, вероятно, что, когда вы принимаете это «золотое правило» мышления, вы поймете, что определенные требования находятся за пределами объема фактического компонента. Так как вы выполняете их?
Вы догадались: контейнеры?
Вы можете создавать контейнеры, которые делают немного дополнительной работы, чтобы сохранить свой компонент натуральной. Когда вы делаете это, вы заканчиваете более целенаправленным компонентом, который намного проще, а контейнер, который лучше протестирован.
Давайте реализуем контейнер Personcard, чтобы проиллюстрировать пример.
Реализация контейнеров с использованием компонентов более высокого порядка
React Redux использует Компоненты высшего порядка Чтобы реализовать контейнеры, которые нажимают и отображают реквизиты из магазина Redux. Поскольку мы получили эту терминологию от React Redux, это не удивительно, что React Redux’s соединить это контейнер Отказ
Независимо от того, если вы используете функциональный компонент для отображения реквизитов, или если вы используете компоненты более высокого порядка для подключения к магазину Redux, золотое правило и задание контейнера все еще одинаковы. Во-первых, напишите свой естественный компонент, а затем используйте компонент более высокого порядка, чтобы преодолеть пробел.
Структура папок выше:
/PersonCard
-PersonCard.js ----------------- natural component
-PersonCard.test.js
-index.js ---------------------- container
-index.test.js
/helpers
-getPictureUrl.js ------------ helper
-getPictureUrl.test.jsЕсли вы использовали Redux раньше, пример выше, что вы, вероятно, уже знакомы. Опять же, это золотое правило не обязательно новая идея, но и тонкий новый образ мышления.
Кроме того, когда вы реализуете контейнеры с компонентами более высокого порядка, у вас также есть возможность функционально составлять компоненты более высокого порядка – проходящие выбросы от одного компонента более высокого порядка к следующему. Исторически мы расценили несколько компонентов высшего порядка, чтобы реализовать один контейнер.
Ты обещал мне крючки
Реализация контейнеров с помощью крючков
Почему крючки представлены в этой статье? Поскольку реализация контейнеров становится намного проще с крючками.
Если вы не знакомы с реактивными крючками, то я бы порекомендовал смотреть Дэн Абрамов и разговоры Райана Флоренции, представляющие концепцию во время React Cont 2018 Отказ
Гист заключается в том, что крючки – это реагирование в реагированной команде на вопросы Компоненты высшего порядка и Аналогичные модели Отказ Реактивные крючки предназначены для превосходной замены для как в большинстве случаев.
Это означает, что реализация контейнеров можно сделать с помощью функционального компонента и крючками?
В приведенном ниже примере мы используем крючки Useroute и Useredux представлять «внешний мир», и мы используем помощник GetValues Чтобы сопоставить внешний мир в реквизит Используемый вашим природным компонентом. Мы также используем помощник преобразования преобразовать выход вашего компонента на внешний мир, представленный отправлять .
import React from 'react';
import PropTypes from 'prop-types';
import { useRouter } from 'react-router';
import { useRedux } from 'react-redux';
import actionCreator from 'your-redux-stuff';
import getValues from './helpers/getVaules';
import transformValues from './helpers/transformValues';
import FooComponent from './FooComponent';
export default function FooComponentContainer(props) {
// hooks
const { match } = useRouter({ path: /* ... */ });
// NOTE: `useRedux` does not exist yet and probably won't look like this
const { state, dispatch } = useRedux();
// mapping
const props = getValues(state, match);
function handleChange(e) {
const transformed = transformValues(e);
dispatch(actionCreator(transformed));
}
// natural component
return ;
}
FooComponentContainer.propTypes = { /* ... */ };А вот эталонная структура папки:
/FooComponent ----------- the whole component for others to import
-FooComponent.js ------ the "natural" part of the component
-FooComponent.test.js
-index.js ------------- the "container" that bridges the gap
-index.js.test.js and provides dependencies
/helpers -------------- isolated helpers that you can test easily
-getValues.js
-getValues.test.js
-transformValues.js
-transformValues.test.jsСжигание событий в контейнерах
Последний тип сценария, где я нахожу себя расходящимся от натурального компонента, заключается в том, когда мне нужно пожарить события, связанные с изменяющимися видами или монтажными компонентами.
Например, скажем, вам поручено сделать приборную панель. Команда дизайна вручает вам макет приборной панели, и вы преобразуете это в реактивный компонент. Теперь вы сейчас, где вы должны заполнить эту приборную панель с данными.
Вы замечаете, что вам нужно позвонить в функцию (например Отправка (Fetchaction) ) Когда ваше компонент может быть, чтобы этого произойти.
В таких сценариях я нашел добавление ComponentDidmount и ComponentDidupdate Методы жизненного цикла и добавление OnMount или OndashboardidChanged реквизит, потому что мне нужно было какое-то событие, чтобы стрелять, чтобы связать мой компонент к внешнему миру.
Следуя золотому правилу, эти OnMount и OndashboardidChanged реквизиты неестественные и поэтому должны жить в контейнере.
Хорошая вещь о крючках заключается в том, что он делает диспетчеринговые события OnMount Или на моменте изменения намного проще!
Сжигание событий на горе:
Чтобы выстрелить событие на горе, позвоните Useffect с пустым массивом.
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import { useRedux } from 'react-redux';
import fetchSomething_reduxAction from 'your-redux-stuff';
import getValues from './helpers/getVaules';
import FooComponent from './FooComponent';
export default function FooComponentContainer(props) {
// hooks
// NOTE: `useRedux` does not exist yet and probably won't look like this
const { state, dispatch } = useRedux();
// dispatch action onMount
useEffect(() => {
dispatch(fetchSomething_reduxAction);
}, []); // the empty array tells react to only fire on mount
// https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects
// mapping
const props = getValues(state, match);
// natural component
return ;
}
FooComponentContainer.propTypes = { /* ... */ };
Сжигание событий на изменение опор:
Useffect Имеет возможность смотреть вашу недвижимость между повторными рендурами и вызывает функцию, которую вы даете, когда свойство изменяется.
До Useffect Я обнаружил, что добавляю неестественные методы жизненного цикла и OnPropertyChanged реквизит, потому что у меня не было способа сделать свойство, отличную от компонента:
import React from 'react';
import PropTypes from 'prop-types';
/**
* Before `useEffect`, I found myself adding "unnatural" props
* to my components that only fired events when the props diffed.
*
* I'd find that the component's `render` didn't even use `id`
* most of the time
*/
export default class BeforeUseEffect extends React.Component {
static propTypes = {
id: PropTypes.string.isRequired,
onIdChange: PropTypes.func.isRequired,
};
componentDidMount() {
this.props.onIdChange(this.props.id);
}
componentDidUpdate(prevProps) {
if (prevProps.id !== this.props.id) {
this.props.onIdChange(this.props.id);
}
}
render() {
return // ...
}
}Сейчас с useffect. Существует очень легкий способ пожара от изменений опор, и наш фактический компонент не должен добавлять реквизит, которые не нужны его функции.
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import { useRedux } from 'react-redux';
import fetchSomething_reduxAction from 'your-redux-stuff';
import getValues from './helpers/getVaules';
import FooComponent from './FooComponent';
export default function FooComponentContainer({ id }) {
// hooks
// NOTE: `useRedux` does not exist yet and probably won't look like this
const { state, dispatch } = useRedux();
// dispatch action onMount
useEffect(() => {
dispatch(fetchSomething_reduxAction);
}, [id]); // `useEffect` will watch this `id` prop and fire the effect when it differs
// https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects
// mapping
const props = getValues(state, match);
// natural component
return ;
}
FooComponentContainer.propTypes = {
id: PropTypes.string.isRequired,
};
Каковы преимущества здесь?
Компоненты остаются развлечениями
Для меня создание компонентов – самая веселая и удовлетворяющая часть предельной разработки. Вы можете превратить идеи и мечты вашей команды в реальный опыт, и это хорошее чувство, которое я думаю, мы все относились и поделитесь.
Там никогда не будет сценарий, где API и опыт вашего компонента разрушаются «внешним миром». Ваш компонент становится тем, что вы себе представляли без дополнительных реквизитов – это мое любимое преимущество этого золотого правила.
Больше возможностей для тестирования и повторного использования
Когда вы принимаете такую архитектуру, подобную этой, вы по сути, вы приносите новый слой данных-Y на поверхность. В этом «слое» вы можете переключать передачу, где вы больше беспокоитесь о правильности данных, идущих в ваш компонент против того, как работает ваш компонент.
Если вы знаете об этом или нет, этот слой уже существует в вашем приложении, но он может быть связан с презентационной логикой. То, что я нашел, это то, что когда я поверхыщу этот слой, я могу сделать много оптимизаций кода и повторно использовать много логики, что я бы иначе переписал, не зная об общественности.
Я думаю, что это станет еще более очевидным с добавлением Пользовательские крючки . Пользовательские крючки дают нам гораздо более простые способы извлечения логики и подписаться на внешние изменения – то, что функция помощника не может сделать.
Максимизировать пропускную способность команды
При работе над командой вы можете разделить разработку контейнеров и компонентов. Если вы договоритесь о API заранее, вы можете одновременно работать на:
- Веб-API (I.e. Back-End)
- Получение данных из веб-API (или аналогичного) и преобразования данных в API компонента
- Компоненты
Есть ли какие-либо исключения?
Так же, как настоящее золотое правило, это золотое правило также является золотым правлением. Существуют некоторые сценарии, где имеет смысл написать, казалось бы, неестественное компонент API для снижения сложности некоторых преобразований.
Простой пример бы названия реквизит. Это сделало бы все более сложные, если инженеры переименованы в основные ключи данных под аргументом, что это более «естественно».
Определенно возможно принять эту идею слишком далеко, когда вы слишком рано в конечном итоге перенаправляете, и это также может быть ловушкой.
Суть
Более или менее, это «золотое правило» просто повторно перемешивается существующее представление о презентационных компонентах и компонентов контейнеров в новом свете. Если вы оцениваете, что требуется ваш компонент на фундаментальном уровне, вы, вероятно, вы получите проще и более читаемые детали.
Спасибо!
Оригинал: “https://www.freecodecamp.org/news/how-the-golden-rule-of-react-components-can-help-you-write-better-code-127046b478eb/”