Занурюємося в роботу з children на React

  • 21 февраля, 14:35
  • 5816
  • 0

Основа React це компоненти. Ви можете вкладати компоненти один в одного так само, як ви вкладаєте один в одного HTML теги, що спрощує роботу з JSX, який власне і нагадує HTML.

Коли я вперше побачив React, то я подумав, що потрібно просто використовувати props.children. Але як же сильно я помилявся.

Занурюємося в роботу з children на ReactЗанурюємося в роботу з children на React

Так як ми працюємо з JavaScript, то ми можемо робити з дочірніми елементами (children) все, що завгодно. Ми можемо сміливо ставити їм конкретні властивості, вирішувати чи потрібно їх рендерити, ну і звичайно ж, управляти ними так, як нам треба. Давайте розглянемо всю красу роботи з  children на React.

Дочірні компоненти

Припустимо, що у нас є компонент  <Grid />, який може містити в собі компоненти  <Row />. Ми б написали це так:

<Grid>   <Row />   <Row />   <Row /> </ Grid>

Занурюємося в роботу з children на React

Ці три компоненти 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>

Занурюємося в роботу з children на React

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>

Занурюємося в роботу з children на React

Функція як дочірній елемент

Ми можемо передати будь-який вираз 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 комментариев
Сортировка:
Добавить комментарий

IT Новости

Смотреть все