Основа React це компоненти. Ви можете вкладати компоненти один в одного так само, як ви вкладаєте один в одного HTML теги, що спрощує роботу з JSX, який власне і нагадує HTML.
Коли я вперше побачив React, то я подумав, що потрібно просто використовувати props.children. Але як же сильно я помилявся.
Так як ми працюємо з JavaScript, то ми можемо робити з дочірніми елементами (children) все, що завгодно. Ми можемо сміливо ставити їм конкретні властивості, вирішувати чи потрібно їх рендерити, ну і звичайно ж, управляти ними так, як нам треба. Давайте розглянемо всю красу роботи з children на React.
Дочірні компоненти
Припустимо, що у нас є компонент <Grid />, який може містити в собі компоненти <Row />. Ми б написали це так:
<Grid>
<Row />
<Row />
<Row />
</ Grid>
Ці три компоненти Row передаються компоненту Grid як props.children. Використовуючи контейнер для виразу (expression container це технічний термін для цих хвилястих дужок в JSX ) батьки можуть рендерити свої дочірні елементи:
class Grid extends React.Component {
render ( ) {
return <div> { this.props.children } </ div>
}
}
Батьківські елементи також можуть і не рендерити своїх нащадків або можуть робити з ними будь-які дії перед рендерингом. Для прикладу, цей компонент. <Fullstop /> взагалі не рендерить свої дочірні елементи:
class Fullstop extends React.Component {
render ( ) {
return <h1> Hello world! </ h1>
}
}
Абсолютно неважливо які дочірні елементи будуть передані цьому компоненту, він просто буде завжди показувати "Hello world!" і нічого більше.
Зверніть увагу, що h1 в прикладі вище рендерить свої дочірні елементи, в нашому випадку це "Hello world!".
Дочірнім елементом може бути все, що завгодно
Children в React не обов'язково будуть компонентами, вони можуть бути всім, чим завгодно. Для прикладу, ми можемо передати нашому вищезазначеному компоненту <Grid /> будь-який текст в вигляді children і все буде прекрасно працювати:
<Grid> Hello world! </ Grid>
JSX автоматично видалить прогалини спочатку і наприкінці рядка, а разом з ними і порожні рядки. Також порожні рядки в середині строкового літерала перетворюються в один пробіл.
Це означає те, що всі ці приклади нижче відрендерять одне і теж:
<Grid> Hello world! </ Grid>
<Grid>
Hello world!
</ Grid>
<Grid>
Hello
world!
</ Grid>
<Grid>
Hello world!
</ Grid>
Ви також можете суміжно використовувати різні типи дочірніх елементів:
<Grid>
Here is a row:
<Row />
Here is another row:
<Row />
</ Grid>
Функція як дочірній елемент
Ми можемо передати будь-який вираз JavaScript, як дочірній елемент. І функції не виняток.
Щоб показати те, як це виглядає, подивіться на цей компонент, який виконує функцію, яка була передана як дочірній елемент:
class Executioner extends React.Component {
render ( ) {
// Зверніть увагу на те, як ми викликаємо дочірній елемент, як функцію
// ↓
return this.props. children ( )
}
}
Ви б могли викликати цей компонент так:
<Executioner>
{ ( ) => <H1> Hello World! </ H1> }
</ Executioner>
Це звичайно не зовсім придатний приклад, але він показує саму ідею.
Уявіть, що вам треба підтягнути будь-які дані з сервера. Ви могли б зробити це кількома способами, але це можливо зробити з цим function-as-a-child патерном:
<Fetch url = "api.myself.com" >
{ ( Result ) => <p> { result } </ p> }
</ Fetch>
Не переживайте, якщо код вище вас збентежив. Все, що я хотів зробити це те, щоб ви не здивувалися побачивши щось подібне в реальному житті. З children можливо все.
Управління children
Якщо ви подивіться на документацію React, то ви побачите там, що " children це непрозора структура даних ". Насправді вони нам кажуть про те, що props.children може бути будь-яким типом даних, наприклад таким як масив, функція, об'єкт і тп. Загалом, це може бути всім чим завгодно.
У React є кілька допоміжних функцій, які можуть допомогти керувати children досить легко і без зайвих проблем. Доступні вони в React.Children.
Проходимся циклом по children
Дві найбільш очевидних з вищезазначених функцій це React.Children.map і React.Children.forEach. Вони працюють так само як і їх колеги з методів масивів, за винятком того, що вони також працюють в момент передачі функції, об'єкта чи чогось ще безпосередньо в children.
class IgnoreFirstChild extends React.Component {
render ( ) {
const children = this.props.children
return (
<Div>
{ React.Children. map ( children, ( child, i ) => {
// ігноруємо перший нащадок
if ( i < 1 ) return
return child
} ) }
</ Div>
)
}
}
<IgnoreFirstChild /> компонент з коду вище, проходиться по всім своїм нащадкам, ігноруючи перший і повертаючи всіх інших.
<IgnoreFirstChild>
<H1> First </ h1>
<h1> Second </ h1> // <- Only this is rendered
</ IgnoreFirstChild>
У нашому випадку ми б могли також використовувати this.props.children.map. Але щоб сталося у випадку, коли хто-небудь би передав функцію як нащадка? this.props.children був би функцією, а не масивом і ми б зловили помилку.
Але а з React.Children.map це взагалі не буде проблемою:
<IgnoreFirstChild>
{ ( ) => <H1> First </ h1> } // <- Ignored?
</ IgnoreFirstChild>
Рахуємо children
Так як this.props.children може бути абсолютно будь-яким типом даних, перевірка кількості нащадків у компонента може бути досить складним процесом. Наївно написавши this.props.children.length ви отримаєте 12, якщо станете перевіряти рядок "Hello World!", хоч там і буде один нащадок.
Саме тому у нас і є React.Children.count:
class ChildrenCounter extends React.Component {
render ( ) {
return <p> React.Children. count ( this.props.children ) </ p>
}
}
Так ми отримаємо кількість нащадків, незалежно від їх типу даних:
// Renders "1"
<ChildrenCounter>
Second!
</ ChildrenCounter>
// Renders "2"
<ChildrenCounter>
<P> First </ p>
<ChildComponent />
</ ChildrenCounter>
// Renders "3"
<ChildrenCounter>
{ ( ) => <H1> First! </ H1> }
Second!
<P> Third! </ P>
</ ChildrenCounter>
Конвертуємо children в масив
Ну і під кінець, якщо жоден з методів вище вам не підходить, то ви можете конвертувати children в масив за допомогою методу React.Children.toArray. Це було б корисно в разі сортування, наприклад:
class Sort extends React.Component {
render ( ) {
const children = React.Children. toArray ( this.props.children )
return <p> { children. sort ( ) . join ( '' ) } </ p>
}
}
<Sort>
// Тут ми використовуємо контейнери виразів, щоб переконатися в тому, що наші рядки
// передані як три окремих нащадка, а не як один рядок
{ 'Bananas' } { 'oranges' } { 'apples' }
</ Sort>
Приклад вище рендерить рядки, але вже відсортовані:
Зверніть увагу, щоReact.Children.toArray масив не містить нащадків з типом функція, а тільки React.Element або рядки.
Один єдиний нащадок
Якщо ви згадаєте про попередньому компоненті <Executioner />, то він працює з одним нащадком, який є функцією.
class Executioner extends React.Component {
render ( ) {
return this.props. children ( )
}
}
Ми можемо уточнити цю умову за допомогою propTypes, що буде виглядати приблизно таким чином:
Executioner.propTypes = {
children: React.PropTypes.func.isRequired,
}
Тут вийде повідомлення в консоль, яке в принципі буде проігноровано розробником. Замість цього ми можемо використовувати React.Children.only всередині рендеру.
class Executioner extends React.Component {
render ( ) {
return React.Children. only ( this.props.children ) ( )
}
}
В цьому випадку відрендериться тільки один єдиний нащадок в this.props.children. Якщо їх буде більше, ніж один, то нам викине помилку, отже, робота додатку буде припинена.
Редагування children
Ми можемо рендерити довільні компоненти як children. Але продовжувати контролювати їх з батьків, а не з компонента з якого ми їх рендеримо. Давайте подивимося на цей момент уважніше. Уявімо, що у нас є компонент RadioGroup, який може включати в себе декілька компонентів RadioButton. (які рендерять <input type="radio">)
RadioButton'и НЕ рендяряться з RadioGroup самі по собі, а використовуються як Children. Що говорить про те, що десь в нашому додатку у нас є такий код:
render ( ) {
return (
<RadioGroup>
<RadioButton value = "first" > First </ RadioButton>
<RadioButton value = "second" > Second </ RadioButton>
<RadioButton value = "third" > Third </ RadioButton>
</ RadioGroup>
)
}
У цьому коді є невелика проблемка. Інпути не згруповані, що призводить до такого результату:
Щоб їх згрупувати їм всім потрібно мати однаковий атрибут name. Ми б звичайно могли б відразу призначити name кожному RadioButton:
<RadioGroup>
<RadioButton name = "g1" value = "first" > First </ RadioButton>
<RadioButton name = "g1" value = "second" > Second </ RadioButton>
<RadioButton name = "g1" value = "third" > Third </ RadioButton>
</ RadioGroup>
Але це нудно і можливо призведе до помилок. Але ж у нас в руках вся сила JavaScript! Чи можемо ми використовувати її, щоб автоматично вказати RadioGroup потрібне нам name для всіх його children?
Міняємо props у Children
Отже, в RadioGroup ми додамо новий метод під назвою renderChildren, який буде редагувати пропси у children:
class RadioGroup extends React.Component {
constructor ( ) {
super ( )
this.renderChildren = this.renderChildren. bind ( this )
}
renderChildren ( ) {
return this.props.children
}
render ( ) {
return (
<div className = "group" >
{ This. renderChildren ( ) }
</ Div>
)
}
}
Давайте пробіжимося по всіх нащадках, щоб отримати кожного з них окремо:
renderChildren ( ) {
return React.Children. map ( this.props.children, child => {
return child
} )
}
Відмінно, але як тепер ми можемо редагувати їх властивості?
Імутабельно клонуємо елементи
І ось тут в гру вступає останній допоміжний метод. Як і мається на увазі під його назвою, React.cloneElement клонує елемент. Ми передаємо йому елемент, який бажаємо скопіювати, як перший аргумент, а потім другим аргументом передаємо об'єкт з пропсами, які повинні бути виставлені вже склонованому елементу:
const cloned = React. cloneElement ( element, {
new: 'yes!'
} )
Тепер елемент cloned матиме пропс new зі значенням "yes!".
І це саме те, що нам потрібно для завершення RadioGroup. Ми клонуємо кожного нащадка і виставляємо йому пропс name зі значенням this.props.name.
renderChildren ( ) {
return React.Children. map ( this.props.children, child => {
return React. cloneElement ( child, {
name: this.props.name
} )
} )
}
Останнім кроком ми передамо унікальний name до RadioGroup:
<RadioGroup name = "g1" >
<RadioButton value = "first" > First </ RadioButton>
<RadioButton value = "second" > Second </ RadioButton>
<RadioButton value = "third" > Third </ RadioButton>
</ RadioGroup>
Працює! Замість вказувати вручну атрибути для кожного RadioButton, ми просто можемо вказати RadioGroup те, що нам потрібно, щоб було name і далі він подбає про це.
Висновок
Children змушують компоненти в React поводитися як елементи розмітки, а не окремі об'єкти. Використовуючи силу JavaScript і деякі допоміжні функції React, ми можемо сміливо з ними працювати для створення декларативних API і взагалі для спрощення в цілому робочого процесу.
0 комментариев
Добавить комментарий