Рендер-пропсы¶
Термин «рендер-проп» относится к возможности компонентов 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.