Рендер-пропсы¶
Термин «рендер-проп» относится к возможности компонентов React разделять код между собой с помощью пропа, значение которого является функцией.
Компонент с рендер-пропом берёт функцию, которая возвращает React-элемент, и вызывает её вместо реализации собственного рендера.
1 2 3 |
|
Такой подход, в частности, применяется в библиотеках React Router и Downshift.
В этой статье мы покажем, чем полезны и как писать рендер-пропсы.
Использование рендер-пропа для сквозных задач¶
Компоненты — это основа повторного использования кода в React. Однако бывает неочевидно, как сделать, чтобы одни компоненты разделяли своё инкапсулированное состояние или поведение с другими компонентами, заинтересованными в таком же состоянии или поведении.
Например, следующий компонент отслеживает положение мыши в приложении:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
|
Когда курсор перемещается по экрану, компонент отображает координаты (x, y) внутри <p>
.
Возникает вопрос: как мы можем повторно использовать это поведение в другом компоненте? То есть если другому компоненту необходимо знать о позиции курсора, можем ли мы как-то инкапсулировать это поведение, чтобы затем легко использовать его в этом компоненте?
Поскольку компоненты являются основой повторного использования кода в React, давайте применим небольшой рефакторинг. Пусть наш код полагается на компонент <Mouse>
, инкапсулирующий поведение, которое мы хотим применять в разных местах.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
|
Теперь компонент <Mouse>
инкапсулирует всё поведение, связанное с обработкой событий mousemove
и хранением позиций курсора (x, y), но пока не обеспечивает повторного использования.
Например, допустим у нас есть компонент <Cat>
, который рендерит изображение кошки, преследующей мышь по экрану. Мы можем использовать проп <Cat mouse={{ x, y }}>
, чтобы сообщить компоненту координаты мыши, и он знал, где расположить изображение на экране.
Для начала вы можете отрендерить <Cat>
внутри метода render
компонента <Mouse>
следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
|
Этот подход будет работать для конкретного случая, но мы не достигли основной цели — инкапсулировать поведение с возможностью повторного использования. Теперь, каждый раз когда мы хотим получить позицию мыши для разных случаев, нам требуется создавать новый компонент (т. е. другой экземпляр <MouseWithCat>
), который рендерит что-то специально для этого случая.
Вот здесь рендер-проп нам и понадобится: вместо явного указания <Cat>
внутри <Mouse>
компонента, и трудозатратных изменений на выводе рендера, мы предоставляем <Mouse>
функцию в качестве пропа, с которой мы используем динамическое определение того, что нужно передавать в рендер-проп.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
|
Теперь, вместо того, чтобы фактически клонировать компонент <Mouse>
и жёстко указывать что-нибудь ещё в методе render
, для решения специфичного случая, мы предоставляем рендер-проп компоненту <Mouse>
, который может динамически определить что рендерить.
Иными словами, рендер-проп – функция, которая сообщает компоненту что необходимо рендерить.
Эта техника позволяет сделать легко портируемым поведение, которое мы хотим повторно использовать. Для этого следует отрендерить компонент <Mouse>
с помощью рендер-пропа, который сообщит, где отрендерить курсор с текущим положением (x, y).
Один интересный момент касательно рендер-пропсов заключается в том, что вы можете реализовать большинство компонентов высшего порядка (HOC), используя обычный компонент вместе с рендер-пропом. Например, если для вас предпочтительней HOC withMouse
вместо компонента <Mouse>
, вы можете создать обычный компонент <Mouse>
вместе с рендер-пропом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
Таким образом, рендер-пропы позволяют реализовать любой из описанных выше паттернов.
Использование пропсов, отличных от render
(как название передаваемого свойства)¶
Важно запомнить, что из названия паттерна «рендер-проп» вовсе не следует, что для его использования вы должны обязательно называть проп render
. На самом деле, любой проп, который используется компонентом и является функцией рендеринга, технически является и «рендер-пропом».
Несмотря на то, что в вышеприведённых примерах мы используем render
, мы можем также легко использовать проп children
!
1 2 3 4 5 6 7 |
|
И запомните, проп children
не обязательно именовать в списке «атрибутов» вашего JSX-элемента. Вместо этого, вы можете поместить его прямо внутрь элемента!
1 2 3 4 5 6 7 |
|
Эту технику можно увидеть в действии в API библиотеки react-motion.
Поскольку этот метод не совсем обычен, вы, вероятно, захотите явно указать, что children
должен быть функцией в вашем propTypes
при разработке такого API.
1 2 3 |
|
Предостережения¶
Будьте осторожны при использовании рендер-проп вместе с React.PureComponent¶
Использование рендер-пропа может свести на нет преимущество, которое даёт React.PureComponent
, если вы создаёте функцию внутри метода render
. Это связано с тем, что поверхностное сравнение пропсов всегда будет возвращать false
для новых пропсов и каждый render
будет генерировать новое значение для рендер-пропа.
Например, в продолжение нашего <Mouse>
компонента упомянутого выше, если Mouse
наследуется от React.PureComponent
вместо React.Component
, наш пример будет выглядеть следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
В этом примере, при каждом рендере <MouseTracker>
генерируется новая функция в качестве значения пропа <Mouse render>
. Это сводит на нет эффекты React.PureComponent
, от которого наследует <Mouse>
!
Чтобы решить эту проблему, вы можете определить проп как метод экземпляра, например так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
В случаях, когда вы не можете определить проп статически (например, вам необходимо замкнуть пропсы и/или состояние компонента), <Mouse>
нужно наследовать от React.Component
.