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

  • 21 февраля, 14:35
  • 5546
  • 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 Новости

Смотреть все