Привет Хабр! Предлагаю вашему вниманию свободный перевод статьи «React Patterns» Майкла Чана, с некоторыми моими примечаниями и дополнениями.
Прежде всего хотел бы поблагодарить автора оригинального текста. В переводе я использовал понятие «Простой компонент» как обозначение Stateless Component aka Dump Component aka Component vs Container
Конструктивная критика, а так же альтернативные паттерны и фичи React приветствуются в комментах.
Компоненты высшего порядка — Higher-order component
Поехали!
Stateless function
Функция без состояния (далее Простые Копоненты) прекрасный способ определить универсальный компонент. Они не содержат состояния (state) или ссылку на DOM элемент (ref), это просто функции.
const Greeting = () =>
Hi there!
В них передаются параметры (props) и контекст
const Greeting = (props, context) =>
Hi {props.name}!
Они могут определять локальные переменные, если используете блоки ({})
Распределение атрибутов это фитча JSX. Такой синтаксический наворот, чтобы передавать все свойства объекта как атрибуты JSX
Эти два примера эквивалентны:
— props написаны как атрибуты:
{children}
— props «распределены» из объекта:
Используйте перенаправление props в создаваемый объект
const FancyDiv = props =>
Теперь вы можете быть уверены, что нужный атрибут будет присутствовать (className), так же как и те которые вы не указали напрямую в функции, а передали в нее вместе с props
So Fancy
Результат:
So Fancy
Имейте ввиду, что порядок имеет значение. если props.className определено, то это свойство перепишет className определенное в FancyDiv
Результат:
We can make FancyDivs className always «win» by placing it after the spread props ({…props}).
Вы можете сделать так, что ваше свойство всегда перепишет переданные через props
const FancyDiv = props =>
Есть более изящный подход — объединить оба свойства.
const FancyDiv = ({ className, ...props }) =>
Destructuring Arguments
Деструктурирующее присвоение это фича стандарта ES2015. Она отлично сочетается с props для Простых Компонентов.
Эти примеры эквивалентны
const Greeting = props =>
Hi {props.name}!
const Greeting = ({ name }) =>
Hi {name}!
Синтаксис оператора rest (…) позволяет собрать оставшиеся свойства в объект
const Greeting = ({ name, ...props }) =>
Hi {name}!
Далее этот объект может быть использован для передачи не выделенных свойств далее в созданном компоненте
const Greeting = ({ name, ...props }) =>
Hi {name}!
Avoid forwarding non-DOM props to composed components. Destructuring makes this very easy because you can create a new props object without component-specific props.
Conditional Rendering
Можете использовать обычный if/else синтаксис в компонентах. Но условные (тернарные) операторы это ваши друзья
if
{condition && Rendered when `truthy` }
unless
{condition || Rendered when `falsey` }
if-else (tidy one-liners)
{condition
? Rendered when `truthy`
: Rendered when `falsey`
}
if-else (big blocks)
{condition ? (
Rendered when `truthy`
) : (
Rendered when `falsey`
)}
* Я предпочитаю не использовать конструкции из последнего примера, гораздо нагляднее в данном случае будет использование обычного if/else, хотя все зависит от конкретного кода.
Children Types
React может рендерить потомков любого типа. В основном это массив или строка
Строка
Hello World!
Массив
{["Hello ", World, "!"]}
Функции могут быть так же использованы как потомки. Однако, нужно координировать их поведение с родительским компонентом.
Функция
{() => { return "hello world!"}()}
Array as children
Использование массива потомков, это обычный паттерн, например так вы делаете списки в React.
Используйте map (), чтобы сделать массив элементов React, для каждого значения в массиве.
{["first", "second"].map((item) => (
{item}
))}
Это эквивалентно литералу массива с объектами
{[
first
,
second
,
]}
Такой паттерн может быть использован совместно с деструктуризацией, распределением атрибутов и другими фичами, чтобы упростить написание кода
Использование функций как потомков требует дополнительного внимания с вашей стороны, чтобы можно было извлечь из них пользу.
{() => { return "hello world!»}()}
Однако, они могут придать вашим компонентам супер силу, такая техника обычно называется рендер-коллбэк.
Эта мощная техника используется в таких библиотеках как ReactMotion. Когда вы применяете ее, логика рендера может управляйся из родительского компонента, вместо того, чтобы полностью передать ее самому компоненту.
Render callback
Вот пример компонента который использует рендер-коллбэк. Он в целом бесполезен, однако это хорошая иллюстрация возможностей, для начала.
const Width = ({ children }) => children(500)
Компонент вызывает потомков, как функцию с определенным аргументом. В данном случае это число 500.
Чтобы использовать этот компонент мы передаем ему функцию как потомка.
{width =>
window is {width}
}
Получим такой результат
window is 500
При таком подходе, можно использовать параметр (width), для условного рендеринга
{width =>
width > 600
?
min-width requirement met!
: null
}
Если планируем использовать такое условие много раз, то мы можем определить другой компонент, чтобы передать ему эту логику
const MinWidth = ({ width: minWidth, children }) =>
{width =>
width > minWidth
? children
: null
}
Очевидно, что статичный компонент Width, не очень полезен, но мы можем наблюдать за размерами окна браузера при таком подходе, это уже что-то
Многие предпочитают Компоненты Высшего Порядка для такого типа функционала. Это вопрос личных преференций.
Children pass-through
Вы можете создать компонент, чтобы применить контекст и рендерить потомков.
class SomeContextProvider extends React.Component {
getChildContext() {
return {some: "context"}
}
render() {
// how best do we return `children`?
}
}
Тут вам следует принять решение. Обернуть потомков в еще один html тэг (div), или вернуть только потомков. Первый вариант может повлиять на существующую разметку и может нарушить стили. Второй — приведет к ошибке (вы помните, что можно вернуть только один родительский элемент из компонента)
Вариант 1: дополнительный div
return
{children}
Вариант 2: ошибка
return children
Лучше всего управлять потомками при помощи специальных методов — React.Children. Например пример ниже позволяет вернуть только потомков и не требует дополнительной обертки
return React.Children.only(this.props.children)
Proxy component
(Не уверен, что это название вообще что-то значит) прим. автора статьи
Кнопки повсюду в приложении. И каждая из них должна иметь атрибут типа «button»
Писать такое сотни раз ручками — не наш метод. Мы можем написать компонент более высокого уровня, чтобы перенаправить props в компонент уровнем ниже.
const Button = props =>
Далее вы просто используете Button, вместо button, и можете быть уверены, что нужный атрибут будет присутствовать в каждой кнопке.
// вернет
Send Money
// вернет Send Money
Style component
Это Proxy Component примененный к стилям. Скажем, у нас есть кнопка. Она использует классы, чтобы выглядеть как «primary».
Использование этих компонентов вернет одинаковый результат
Такой подход может принести ощутимую пользу, так как изолирует определенные стили в определенном компоненте.
Event switch
При написании обработчиков событий, обычно мы используем соглашение о названии функций
handle{eventName}
handleClick(e) { /* do something */ }
Для компонентов, которые обрабатывают несколько событий, такое наименование может быть излишне повторяющемся. Сами по себе названия не дают нам никакой ценности, так как они просто направляют к действиям/функциям
Давайте напишем простой обработчик для всех событий с переключателем по типу события (event.type)
handleEvent({type}) {
switch(type) {
case "click":
return require("./actions/doStuff")(/* action dates */)
case "mouseenter":
return this.setState({ hovered: true })
case "mouseenter":
return this.setState({ hovered: false })
default:
return console.warn(`No case for event type "${type}"`)
}
}
Можно так же вызывать функции аргументы напрямую, используя функцию-стрелку
someImportedAction({ action: "DO_STUFF" })}
Не парьтесь насчет оптимизации производительности, пока не столкнулись с такими проблемами. Серьезно, не нужно.
* Лично я не считаю такой подход удачным, тк он не добавляет читаемости коду. Я предпочитаю использовать фичи React c функциями которые привязываются к контексту автоматом. То есть следующая нотация более не является необходимостью
this.handleClick = this.handleClick.bind(this)
вместо нее работает следующая нотация
handleClick = () => {…} // вместо handleClick() {...}
и далее где-то возможно просто
onClick={this.handleClick}
В таком случае контекст (this) не будет утерян, если внутри функция обработчик ссылается к нему. Соответственно, такие функции можно легко передавать в качестве props другим компонентам и вызывать в них.
Также, в случае, если мы передаем такую функцию в потомок, Простой Компонент, то можем получить ссылку на DOM элемент этого компонента через event.target в родительском компоненте, что иногда полезно.
Компоненты макета это что-то вроде статических элементов DOM. Скоро всего они не будут обновляться часто, если будут вообще.
Рассмотрим компонент который содержит два компонента горизонтально.
}
rightSide={}
/>
Мы можем агрессивно оптимизировать его работу.
Так как HorizontalSplit будет родительским компонентом для обоих компонентов он никогда не станет их владельцем. Мы можем сказать чтобы он никогда не обновлялся, при этом не прорывая жизненные циклы внутренних компонентов.
Мы можем писать различные компоненты-контейнеры для разных контекстов приложения.
Higher-order component
Функция высшего порядка это функция которая может принимать в качестве аргументов другие функции и/или возвращать функции. Не более сложно чем данное определение. Так что такое компоненты высшего порядка?
Вы уже используете компоненты-контейнеры, это просто контейнеры, обернутые в функцию. Давайте начнем с простого Greeting компонента.
const Greeting = ({ name }) => {
if (!name) { return
Connecting...
}
return
Hi {name}!
}
Если он получит props.name, он отрендерит данные. В противном случае он отрендерит «Connecting…». Теперь немного более высокий порядок:
const Connect = ComposedComponent =>
class extends React.Component {
constructor() {
super()
this.state = { name: "" }
}
componentDidMount() {
// this would fetch or connect to a store
this.setState({ name: "Michael" })
}
render() {
return (
)
}
}
Это функция которая возвращает компонент, который рендерит компонент, который мы передали в качестве аргумента (ComposedComponent)
Далее мы оборачиваем компоте в эту функцию.
const ConnectedMyComponent = Connect(Greeting)
Это очень мощный шаблон, чтобы компонент мог получать данные и раздавать их любому количеству простых компонентов.
Ссылки (все на английском):
Оригинальный текст
Компоненты высшего порядка в деталях
Паттерны привязки контекста в React
Комментарии (1)
MrCheater
7 сентября 2016 в 15:53
(комментарий был изменён)
0
↑
↓
Про key забыли.
{["first", "second"].map((item) => (
{item}
))}
{[
first
,
second
,
]}
Будет предупреждение:
Warning: Each child in an array or iterator should have a unique "key" prop. See https://fb.me/react-warning-keys for more information.