Java, UX, HTML, CSS, WEB-design

React Children и методы итерации

Краткое описание по статье React Children и методы итерации

Название: React Children и методы итерации . Краткое описание: ⭐ Арихант Ве . Дата публикации: 15.01.2022 . Автор: Алишер Валеев .

Для чего создан сайт Novosti-Nedeli.ru

Данный сайт посвящен новостям мира и мира технологий . Также тут вы найдете руководства по различным девайсам.

Сколько лет сайту?

Возраст составляет 3 года


  • Арихант Верма

  • 0 Комментарии

React Children и методы итерации

  • 14 минут чтения

  • Реакция, Инструменты, Производительность

Краткое резюме ↬

В этой статье мы обсудим и узнаем о варианте использования итерации по React. children и способы сделать это. В частности, мы углубимся в один из служебных методов, React.Children.toArray, который дает нам React, что помогает перебирать children способом, обеспечивающим производительность и детерминизм.

Наиболее очевидная и распространенная опора, с которой разработчики работают в React, — это children опора В большинстве случаев нет необходимости понимать, как children опора выглядит. Но в некоторых случаях мы хотим проверить children prop, чтобы, возможно, обернуть каждого дочернего элемента в другой элемент/компонент или изменить их порядок или нарезать их. В этих случаях проверка того, как children prop выглядит как становится необходимым.

В этой статье мы рассмотрим утилиту React. React.Children.toArray что позволяет нам подготовить children prop для проверки и итерации, некоторые из его недостатков и способы их преодоления — с помощью небольшого пакета с открытым исходным кодом, чтобы наш код React функционировал так, как он должен вести себя детерминистически, сохраняя производительность неизменной. Если вы знаете основы React и имеете хотя бы представление о том, что такое children prop в React есть, эта статья для вас.

Работая с React, большую часть времени мы не касаемся children prop больше, чем использовать его непосредственно в компонентах React.

function Parent({ children }) {
  return <div className="mt-10">{children}</div>;
}

Но иногда нам приходится повторять children prop, чтобы мы могли улучшать или изменять дочерние элементы, не заставляя пользователя компонентов делать это самостоятельно. Одним из распространенных вариантов использования является передача информации, связанной с индексом итерации, дочерним компонентам родителя, например:

import { Children, cloneElement } from "react";

function Breadcrumbs({ children }) {
  const arrayChildren = Children.toArray(children);

  return (
    <ul
      style={{
        listStyle: "none",
        display: "flex",
      }}
    >
      {Children.map(arrayChildren, (child, index) => {
        const isLast = index === arrayChildren.length - 1;

        if (! isLast && ! child.props.link ) {
          throw new Error(
            `BreadcrumbItem child no. ${index + 1}
            should be passed a 'link' prop`
          )
        } 

        return (
          <>
            {child.props.link ? (
              <a
                href={child.props.link}
                style={{
                  display: "inline-block",
                  textDecoration: "none",
                }}
              >
                <div style={{ marginRight: "5px" }}>
                  {cloneElement(child, {
                    isLast,
                  })}
                </div>
              </a>
            ) : (
              <div style={{ marginRight: "5px" }}>
                {cloneElement(child, {
                  isLast,
                })}
              </div>
            )}
            {!isLast && (
              <div style={{ marginRight: "5px" }}>
                >
              </div>
            )}
          </>
        );
      })}
    </ul>
  );
}

function BreadcrumbItem({ isLast, children }) {
  return (
    <li
      style={{
        color: isLast ? "black" : "blue",
      }}
    >
      {children}
    </li>
  );
}

export default function App() {
  return (
    <Breadcrumbs>
      <BreadcrumbItem
        link="https://goibibo.com/"
      >
        Goibibo
      </BreadcrumbItem>

      <BreadcrumbItem
        link="https://goibibo.com/hotels/"
      >
        Hotels
      </BreadcrumbItem>

      <BreadcrumbItem>
       A Fancy Hotel Name
      </BreadcrumbItem>
    </Breadcrumbs>
  );
}

Взгляните на демо Codesandbox. Здесь мы делаем следующее:

  1. Мы используем React.Children.toArray способ убедиться, что children prop всегда является массивом. Если мы этого не делаем, делая children.length может взорваться, потому что children prop может быть объектом, массивом или даже функцией. Кроме того, если мы попытаемся использовать массив .map метод на children прямо он может взорваться.
  2. В родительском Breadcrumbs компонент, который мы перебираем по его дочерним элементам, используя служебный метод React.Children.map.
  3. Потому что у нас есть доступ к index внутри функции итератора (второй аргумент функции обратного вызова React.Children.map) мы можем определить, является ли ребенок последним ребенком или нет.
  4. Если это последний дочерний элемент, мы клонируем элемент и передаем isLast поддержите его, чтобы ребенок мог стилизовать себя на его основе.
  5. Если это не последний дочерний элемент, мы гарантируем, что все дочерние элементы, которые не являются последними дочерними элементами, имеют link поддержите их, выдав ошибку, если они этого не сделают. Мы клонируем элемент, как мы это делали на шаге 4. и передаем isLast prop, как и раньше, но дополнительно оборачиваем этот клонированный элемент тегом привязки.

Пользователь Breadcrumbs и BreadcrumbItem не нужно беспокоиться о том, какие дочерние элементы должны иметь ссылки и как они должны быть оформлены. Внутри Breadcrumbs компонент, он автоматически обрабатывается.

Этот образец неявно передача реквизита и/или наличие state в родительском элементе и передача состояния и средств изменения состояния дочерним элементам в качестве реквизита называется шаблоном составного компонента. Возможно, вы знакомы с этим шаблоном из React Router. Switch компонент, который принимает Route компоненты как его дочерние элементы:

// example from react router docs
// https://reactrouter.com/web/api/Switch

import { Route, Switch } from "react-router";

let routes = (
  <Switch>
    <Route exact path="https://www.smashingmagazine.com/">
      <Home />
    </Route>
    <Route path="/about">
      <About />
    </Route>
    <Route path="/:user">
      <User />
    </Route>
    <Route>
      <NoMatch />
    </Route>
  </Switch>
);

Теперь, когда мы установили, что есть потребности, в которых мы должны перебирать children prop иногда и используя два дочерних служебных метода React.Children.map и React.Children.toArray, освежим в памяти один из них: React.Children.toArray.

Еще после прыжка! Продолжить чтение ниже ↓

React.Children.toArray

Давайте начнем с примера, что делает этот метод и где он может быть полезен.

import { Children } from 'react'

function Debugger({children}) {
  // let’s log some things
  console.log(children);
  console.log(
    Children.toArray(children)
  )
  return children;
}

const fruits = [
  {name: "apple", id: 1},
  {name: "orange", id: 2},
  {name: "mango", id: 3}
]

export default function App() {
  return (
    <Debugger>
        <a
          href="https://css-tricks.com/"
          style={{padding: '0 10px'}}
        >
          CSS Tricks
        </a>

        <a
          href="https://smashingmagazine.com/"
          style={{padding: '0 10px'}}
        >
          Smashing Magazine
        </a>

        {
          fruits.map(fruit => {
            return (
              <div key={fruit.id} style={{margin: '10px'}}>
                {fruit.name}
              </div>
            )
          })
        }
    </Debugger>
  )
}

Взгляните на демо Codesandbox. У нас есть Debugger компонент, который ничего не делает с точки зрения рендеринга — он просто возвращает children как есть. Но он регистрирует два значения: children и React.Children.toArray(children).

Если вы откроете консоль, вы сможете увидеть разницу.

  • Первый оператор, который регистрирует children prop, показывает следующее как структуру данных своего значения:
[
  Object1, ----> first anchor tag
  Object2, ----> second anchor tag
  [
    Object3, ----> first fruit
    Object4, ----> second fruit
    Object5] ----> third fruit
  ]
]
  • Второй оператор, который регистрирует React.Children.toArray(children) журналы:
[
  Object1, ----> first anchor tag
  Object2, ----> second anchor tag
  Object3, ----> first fruit
  Object4, ----> second fruit
  Object5, ----> third fruit
]

Давайте прочитаем документацию по методу в документации React, чтобы понять, что происходит.

React.Children.toArray возвращает children непрозрачная структура данных в виде плоского массива с ключами, назначенными каждому дочернему элементу. Полезно, если вы хотите манипулировать коллекциями дочерних элементов в ваших методах рендеринга, особенно если вы хотите изменить порядок или нарезать children прежде чем передать его вниз.

Давайте разберем это:

  1. Возвращает children непрозрачная структура данных в виде плоского массива.
  2. С ключами, назначенными каждому ребенку.

Первый пункт говорит о том, что children (которая является непрозрачной структурой данных, то есть может быть объектом, массивом или функцией, как описано ранее) преобразуется в плоский массив. Так же, как мы видели в примере выше. Кроме того, этот комментарий к проблеме GitHub также объясняет его поведение:

Это (React.Children.toArray) не вытягивает дочерние элементы из элементов и не сглаживает их, это не имеет никакого смысла. Он сглаживает вложенные массивы и объекты, т.е. [['a', 'b'],['c', ['d']]] становится чем-то похожим на ['a', 'b', 'c', 'd'].

React.Children.toArray(
  [
    ["a", "b"],
    ["c", ["d"]]
  ]
).length === 4;

Давайте посмотрим, что говорит второй пункт («С ключами, назначенными каждому дочернему элементу»), расширив каждый дочерний элемент из предыдущих журналов примера.

Расширенный дочерний элемент из console.log(children)

{
  $$typeof: Symbol(react.element),
  key: null,
  props: {
    href: "https://smashingmagazine.com",
    children: "Smashing Magazine",
    style: {padding: "0 10px"}
  },
  ref: null,
  type: "a",
  // … other properties
}

Расширенный дочерний элемент из console.log(React.Children.toArray(children))

{
  $$typeof: Symbol(react.element),
  key: ".0",
  props: {
    href: "https://smashingmagazine.com",
    children: "Smashing Magazine",
    style: {padding: "0 10px"}
  },
  ref: null,
  type: "a",
  // … other properties
}

Как видите, помимо выравнивания children prop в плоский массив, он также добавляет уникальные ключи к каждому из своих дочерних элементов. Из документов React:

React.Children.toArray() изменяет ключи, чтобы сохранить семантику вложенных массивов при выравнивании списков дочерних элементов. Это, toArray префикс каждого ключа в возвращаемом массиве, так что каждый ключ элемента ограничен входным массивом, содержащим его.

Поскольку .toArray метод может изменить порядок и место children, он должен убедиться, что поддерживает уникальные ключи для каждого из них для согласования и оптимизации рендеринга.

Давайте уделим немного больше внимания so that each element’s key is scoped to the input array containing it., просмотрев ключи каждого элемента второго массива (соответствующего console.log(React.Children.toArray(children))).

import { Children } from 'react'

function Debugger({children}) {
  // let’s log some things
  console.log(children);
  console.log(
    Children.map(Children.toArray(children), child => {
      return child.key
    }).join('n')
  )
  return children;
}

const fruits = [
  {name: "apple", id: 1},
  {name: "orange", id: 2},
  {name: "mango", id: 3}
]

export default function App() {
  return (
    <Debugger>
        <a
          href="https://css-tricks.com/"
          style={{padding: '0 10px'}}
        >
          CSS Tricks
        </a>
        <a
          href="https://smashingmagazine.com/"
          style={{padding: '0 10px'}}
        >
          Smashing Magazine
        </a>
        {
          fruits.map(fruit => {
            return (
              <div key={fruit.id} style={{margin: '10px'}}>
                {fruit.name}
              </div>
            )
          })
        }
    </Debugger>
  )
}
.0  ----> first link
.1  ----> second link
.2:$1 ----> first fruit
.2:$2 ----> second fruit
.2:$3 ----> third fruit

Как видите, фрукты, которые изначально были вложенным массивом внутри исходного children массив, имеют ключи с префиксом .2. То .2 соответствует тому факту, что они были частью массива. Суффикс, а именно :$1 ,:$2, :$3, соответствует родителю jsx div элемент, соответствующий фруктам. Если бы вместо этого мы использовали индекс в качестве ключа, мы бы получили :0, :1, :2 как суффиксы.

Итак, предположим, что у вас есть три уровня вложенности внутри children массив, например:

import { Children } from 'react'

function Debugger({children}) {
  const retVal = Children.toArray(children)
  console.log(
    Children.map(retVal, child => {
      return child.key
    }).join('n')
  )
  return retVal
}

export default function App() {
  const arrayOfReactElements = [
    <div key="1">First</div>,
    [
      <div key="2">Second</div>,
      [
        <div key="3">Third</div>
      ]
    ]
  ];
  return (
    <Debugger>
      {arrayOfReactElements}
    </Debugger>
  )
}

Ключи будут выглядеть

.$1
.1:$2
.1:1:$3

Проверьте демо-версию Codesandbox. То $1, $2, $3 суффиксы из-за того, что оригинальные ключи помещены в элементы React в массиве, в противном случае React жалуется на отсутствие ключей 😉 .

Из того, что мы прочитали до сих пор, мы можем прийти к двум вариантам использования для React.Children.toArray.

  1. Если есть абсолютная необходимость в этом children всегда должен быть массивом, вы можете использовать React.Children.toArray(children) вместо. Он будет работать отлично, даже если children также является объектом или функцией.

  2. Если вам нужно отсортировать, отфильтровать или нарезать children опора, на которую можно положиться React.Children.toArray чтобы всегда сохранять уникальные ключи всех детей.

Есть проблема с React.Children.toArray 🤔. Давайте посмотрим на этот фрагмент кода, чтобы понять, в чем проблема:

import { Children } from 'react'

function List({children}) {
  return (
    <ul>
      {
        Children.toArray(
          children
        ).map((child, index) => {
          return (
            <li
              key={child.key}
            >
              {child}
            </li>
          )
        })
      }
    </ul>
  )
}

export default function App() {
  return (
    <List>
      <a
        href="https://css-tricks.com"
        style={{padding: '0 10px'}}
      >
        Google
      </a>
      <>
        <a
          href="https://smashingmagazine.com"
          style={{padding: '0 10px'}}
        >
          Smashing Magazine
        </a>
        <a
          href="https://arihantverma.com"
          style={{padding: '0 10px'}}
        >
          {"Arihant’s Website"}
        </a>
      </>
    </List>
  )
}

Проверьте демо-версию Codesandbox. Если вы посмотрите, что отображается для дочерних элементов фрагмента, вы увидите, что обе ссылки отображаются внутри одного li тег! 😱

Это потому что React.Children.toArray не переходит на фрагменты. Итак, что мы можем с этим поделать? К счастью, ничего 😅 . У нас уже есть пакет с открытым исходным кодом под названием react-keyed-flatten-children. Это небольшая функция, которая творит чудеса.

Давайте посмотрим, что он делает. В псевдокоде (эти точки связаны в фактическом коде ниже) он делает это:

  1. Это функция, которая принимает children как единственный необходимый аргумент.
  2. Повторяет React.Children.toArray(children) и собирает дочерние элементы в массив аккумуляторов.
  3. Во время итерации, если дочерний узел является строкой или числом, он помещает значение как есть в массив аккумуляторов.
  4. Если дочерний узел является действительным элементом React, он клонирует его, дает ему соответствующий ключ и помещает его в массив аккумуляторов.
  5. Если дочерний узел является фрагментом, то функция вызывает себя с дочерними элементами фрагмента в качестве аргумента (так она проходит через фрагмент) и помещает результат вызова самого себя в массив аккумуляторов.
  6. Делая все это, он отслеживает глубину обхода (фрагментов), чтобы дочерние элементы внутри фрагментов имели правильные ключи, так же, как ключи работают с вложенными массивами, как мы видели ранее выше.
import {
  Children,
  isValidElement,
  cloneElement
} from "react";

import { isFragment } from "react-is";

import type {
  ReactNode,
  ReactChild,
} from 'react'

/*************** 1. ***************/
export default function flattenChildren(
  // only needed argument
  children: ReactNode,
  // only used for debugging
  depth: number = 0,
  // is not required, start with default = []
  keys: (string | number)[] = [] 
): ReactChild[] {
  /*************** 2. ***************/
  return Children.toArray(children).reduce(
    (acc: ReactChild[], node, nodeIndex) => {
      if (isFragment(node)) {
        /*************** 5. ***************/
        acc.push.apply(
          acc,
          flattenChildren(
            node.props.children,
            depth + 1,
            /*************** 6. ***************/
            keys.concat(node.key || nodeIndex)
          )
        );
      } else {
        /*************** 4. ***************/
        if (isValidElement(node)) {
          acc.push(
            cloneElement(node, {
              /*************** 6. ***************/
              key: keys.concat(String(node.key)).join('.')
            })
          );
        } else if (
          /*************** 3. ***************/
          typeof node === "string"
          || typeof node === "number"
        ) {
          acc.push(node);
        }
      }
      return acc; 
    },
    /*************** Acculumator Array ***************/
    []
  );
}

Давайте повторим наш предыдущий пример, чтобы использовать эту функцию, и убедимся, что она решает нашу проблему.

import flattenChildren from 'react-keyed-flatten-children'
import { Fragment } from 'react'

function List({children}) {
  return (
    <ul>
      {
        flattenChildren(
          children
        ).map((child, index) => {
          return <li key={child.key}>{child}</li>
        })
      }
    </ul>
  )
}
export default function App() {
  return (
    <List>
      <a
        href="https://css-tricks.com"
        style={{padding: '0 10px'}}
      >
        Google
      </a>
      <Fragment>
        <a
          href="https://smashingmagazine.com"
          style={{padding: '0 10px'}}>
          Smashing Magazine
        </a>
        
        <a
          href="https://arihantverma.com"
          style={{padding: '0 10px'}}
        >
          {"Arihant’s Website"}
        </a>
      </Fragment>
    </List>
  )
}

И вот окончательный результат (на Codesandbox)! Ууууууу! Оно работает.

В качестве дополнения, если вы новичок в тестировании — как и я на момент написания этой статьи — вас могут заинтересовать 7 тестов, написанных для этой служебной функции. Будет интересно прочитать тесты, чтобы определить функциональность функции.

Долгосрочная проблема с Children Утилиты

React.Children является дырявой абстракцией и находится в режиме обслуживания».

— Дэн Абрамов

Проблема с использованием Children методы изменения children поведение заключается в том, что они работают только для одного уровня вложенности компонентов. Если мы обернем один из наших children в другом компоненте мы теряем компонуемость. Давайте посмотрим, что я подразумеваю под этим, взяв первый пример, который мы увидели — хлебные крошки.

import { Children, cloneElement } from "react";

function Breadcrumbs({ children }) {
  return (
    <ul
      style={{
        listStyle: "none",
        display: "flex",
      }}
    >
      {Children.map(children, (child, index) => {
        const isLast = index === children.length - 1;
        // if (! isLast && ! child.props.link ) {
        //   throw new Error(`
        //     BreadcrumbItem child no.
        //     ${index + 1} should be passed a 'link' prop`
        //   )
        // } 
        return (
          <>
            {child.props.link ? (
              <a
                href={child.props.link}
                style={{
                  display: "inline-block",
                  textDecoration: "none",
                }}
              >
                <div style={{ marginRight: "5px" }}>
                  {cloneElement(child, {
                    isLast,
                  })}
                </div>
              </a>
            ) : (
              <div style={{ marginRight: "5px" }}>
                {cloneElement(child, {
                  isLast,
                })}
              </div>
            )}
            {!isLast && (
              <div style={{ marginRight: "5px" }}>></div>
            )}
          </>
        );
      })}
    </ul>
  );
}

function BreadcrumbItem({ isLast, children }) {
  return (
    <li
      style={{
        color: isLast ? "black" : "blue",
      }}
    >
      {children}
    </li>
  );

}
const BreadcrumbItemCreator = () =>
  <BreadcrumbItem
    link="https://smashingmagazine.com"
  >
    Smashing Magazine
  </BreadcrumbItem>

export default function App() {
  return (
    <Breadcrumbs>
      <BreadcrumbItem
        link="https://goibibo.com/"
      >
        Goibibo
      </BreadcrumbItem>

      <BreadcrumbItem
        link="https://goibibo.com/hotels/"
      >
        Goibibo Hotels
      </BreadcrumbItem>

      <BreadcrumbItemCreator />

      <BreadcrumbItem>
        A Fancy Hotel Name
      </BreadcrumbItem>
    </Breadcrumbs>
  );
}

Взгляните на демо Codesandbox. Хотя наш новый компонент <BreadcrumbItemCreator /> вынесено, наше Breadcrumb компонент не имеет возможности извлечь link prop из него, из-за чего он не отображается как ссылка.

Чтобы решить эту проблему, команда React разработала — ныне несуществующий — экспериментальный API под названием react-call-return.

Видео Райана Флоренса подробно объясняет эту проблему и то, как react-call-return починил это. Поскольку пакет никогда не публиковался ни в одной версии React, есть планы вдохновиться им и сделать что-то готовое к производству.

Вывод

В заключение мы узнали о:

  1. То React.Children служебные методы. Мы видели два из них: React.Children.map чтобы увидеть, как использовать его для создания составных компонентов, и React.Children.toArray глубоко.
  2. Мы видели, как React.Children.toArray преобразует непрозрачный children prop — который может быть либо объектом, либо массивом, либо функцией — в плоский массив, чтобы над ним можно было работать нужным образом — сортировать, фильтровать, склеивать и т. д.
  3. Мы узнали, что React.Children.toArray не проходит через React Fragments.
  4. Мы узнали о пакете с открытым исходным кодом под названием react-keyed-flatten-children и понял, как это решает проблему.
  5. Мы видели это Children утилиты находятся в режиме обслуживания потому что они плохо сочиняют.

Вам также может быть интересно прочитать, как использовать другие Children методы, чтобы сделать все, что вы можете сделать с children в блоге Макса Штойбера React Children Deep Dive.

Ресурсы

  • Составные компоненты с реактивными хуками
  • React.Children.toArray объяснение проблемы выравнивания массива github
  • Примирение с реакцией: рекурсия на дочерних элементах
  • React.Children.toArray не переходит на фрагменты
  • react-keyed-flatten-children
  • react-keyed-flatten-children тесты
  • ответ-вызов-возврат
  • Видео Райана Флоренса, объясняющее реакцию-вызов-возврат
  • План команды React по замене Children утилиты с чем-то более составным
  • Макс Штойбер React Children Глубокое погружение
  • React.Children является дырявой абстракцией и находится в режиме обслуживания
Сокрушительная редакция
(кс, вф, ык, ил)




Source: https://smashingmagazine.com

Заключение

Вы ознакомились с статьей — React Children и методы итерации

Пожалуйста оцените статью, и напишите комментарий.

Похожие статьи

Добавить комментарий

Ваш адрес email не будет опубликован.

Кнопка «Наверх»