Skip to content

Instantly share code, notes, and snippets.

@zagazat
Last active May 30, 2025 07:13
Show Gist options
  • Select an option

  • Save zagazat/db926ec7ab69061934246a55b64913c3 to your computer and use it in GitHub Desktop.

Select an option

Save zagazat/db926ec7ab69061934246a55b64913c3 to your computer and use it in GitHub Desktop.
Перевод статьи React reconciliation: how it works and why should we care

Источник: https://www.developerway.com/posts/reconciliation-in-react

Автор: Надя Макаревич

Углубимся в детали работы алгоритма React Reconciliation, и посмотрим как он влияет на код, который мы пишем каждый день. Изучим особенности условного рендеринга, атрибута key и разберёмся почему не нужно объявлять компоненты внутри других компонентов.

Как работает алгоритм React Reconciliation

Каждый раз, когда я думаю, что знаю всё о рендеринге компонентов в React, Вселенная находит способ удивить меня. Что-то безобидное, вроде обычной конструкции if, может просто взорвать мозг. Это и случилось в минувшую субботу, когда я рандомно шерстила документацию React вместо полезных занятий из списка "Дела на выходные". Очередной момент серии "Стоп, это не может быть правдой" привёл к тому, что планы на выходные резко потеряли важность, последовало ещё одно исследование и родилась эта статья. Кому вообще нужны эти todo-листы с планами на выходные, это ведь не так важно, правда?

Итак, давайте посмотрим на эту мини-загадку, поруинившую мои выходные договорённости. Мы попытаемся понять, как работает реконсиляция и разберёмся с некоторыми появляющимися в процессе вопросами. Вы когда-нибудь задумывались над тем, почему, если мы объявляем компоненты внутри другого компонента, то они будет пересоздаваться заново при каждом ре-рендере? Или, например, почему нам не нужен проп key за пределами списков, даже если мы рендерим одинаковые данные с одинаковым поведением? Пришло время разобраться.

Загадка: условный рендеринг компонентов

Для начала сама загадка.

Представьте обычный условный рендеринг компонента: если "что-то", покажи мне такой компонент; если нет, то покажи другой. Например, мы делаем форму регистрации на сайте и часть формы предназначена только для компаний, а часть для обычных посетителей веб-сайта. Так что я хочу показать поле "Company Tax ID", только в случае если отмечен чекбокс "Sign up as a company", а если не отмечен, напишу что-нибудь вроде "Нам не нужен твой Tax ID, счастливчик".

Получится что-то похожее на это:

const Form = () => {
  const [isComany, setIsCompany] = useState(false);
	
  return (
    <React.Fragment>
      // чекбокс где-то там
      { isCompany ? (
        <Input id="company-tax-id-number" placeholder="Enter you company Tax ID" />
      ) : (
        <TextPlaceholder />
      )}
    </React.Fragment>
  )
}

Что здесь должно произойти с точки зрения монтирования и рендеров, если пользователь отметит, что он выступает от лица компании и изменит значение isCompany с дефолтного false на true?

Здесь никаких сюрпризов: компонент формы будет перерисовываться, Input смонтируется, а TextPlaceholder размонтируется. Если я снова уберу флажок, Input будет снова размонтирован, а TextPlaceholder снова смонтирован.

С точки зрения пользователя получится, что если он введёт что-то в поле ввода, затем уберёт флажок, а затем вернёт его обратно, то всё введённое пропадёт. У компонента Input свой стейт, который хранит ввод. Этот стейт будет уничтожен при размонтировании, и снова создастся при монтировании.

Окей. Что если мы и с обычного человека захотим собирать TaxID. То есть будет такой же компонент Input с таким же поведением. Но у него будет другие id, placeholder, onChange, и остальные свойства тоже другие. Ваш код будет примерно такой:

const Form = () => {
  const [isComany, setIsCompany] = useState(false);
	
  return (
    <React.Fragment>
      // чекбокс где-то там
      { isCompany ? (
        <Input id="company-tax-id-number" placeholder="Enter you company Tax ID" />
      ) : (
        <Input id="person-tax-id-number" placeholder="Enter you person Tax ID" />
      )}
    </React.Fragment>
  )
}

Что произойдёт сейчас?

Конечно же ответ довольно интуитивен и ожидаем для любого здравомыслящего человека) Размонтирование теперь не происходит! Если я введу что-то в инпут, а затем прожму чекбокс, то введённый текст останется! React думает, что это одно и то же, и вместо того, чтобы размонтировать первый инпут и смонтировать второй, он просто запустит ре-рендер компонента с новыми данными.

Вот ссылка на Code Sandbox. Перейдите, откройте консоль и поиграйтесь с чекбоксами, полями ввода и посмотрите как монтирование работает в первом примере и не работает во втором.

Ну, а если вы без колебаний можете ответит "А, так это из-за [тут причина]", то круто! Можно взять ваш автограф? Для тех у кого от такого поведения появился тик в глазах и небольшая боль в голове, настало время погрузиться в процесс реконсиляции.

Алгоритм React Reconciliation

Это всё о DOM и о том, как медленно в него что-то добавляется или удаляется. Когда мы пишем что-то на React, наша главная цель это позволить ему преобразовать всё, что мы ему даём в обычные DOM-элементы с соответствующими данными. Когда мы пишем что-то вроде этого:

const Input = ({ placeholder }) => {
  return <input type="text" placeholder={ placeholder } />
}

// использование компонента где-то в коде
<Input placeholder="Input something here" />

Мы ожидаем, что React создаст обычный HTML тег input с плейсхолдером в соответствующем месте DOM. Когда мы внутри компонента поменяем проп placeholder, мы ожидаем что React обновит наш DOM элемент с соответствующим значением и мы увидим это значение на экране. В идеале мгновенно. Мы не можем просто удалить старый инпут и создать новый с новыми данными, это будет долго и затратно. Вместо этого нам нужно идентифицировать этот DOM элемент и просто обновить значение его атрибута. Без React мы бы сделали это так:

const input = document.getElementById('input-id');
input.placeholder = "new data";

К счастью нам больше не нужно это делать вручную. React это делает за нас. И делает он это создавая и модифицируя виртуальный DOM (Virtual DOM). Virtual DOM это огромный JavaScript объект со всеми компонентами которые нужно отрисовать, все их свойства и дочерние компоненты (children), которые по своей сути являются такими же объектами. Обычное дерево.

Компонент Input из примера выше будет представлен примерно так:

{
  type: "input", // тип элемент который нужно отрендерить
  props: {...}, // свойства инпута, например placeholder, type, id
  ... // куча всяких внутренних вещей
}

Если бы, например, наш компонент должен был рендерить нечто большее, то вот так:

const Input = ({ id, label }) => {
  return (
    <React.Fragment>
      <label htmlFor={ id }>{ label }</label>
      <input type="text" id={ id } />
    </React.Fragment>
  )
}

Тогда label и input с точки зрения React были бы просто массивом объектов.

[
  {
    type: 'label',
    ... // другие свойства
  },
  {
    type: 'input',
    ... // другие свойства
  }
]

DOM элементы вроде input и label имеют свой тип, представленный строкой, и React знает как напрямую преобразовывать это в DOM. Но компоненты не связаны напрямую с DOM элементами, и поэтому React должен как-то это обойти.

const Component = () => {
  return <Input />
}

В таком случае в качестве type используется ссылка на функцию. React просто берёт функцию, известную как Input и использует её в качестве значения поля type.

{
  type: Input, // ссылка на функцию Input, которую мы объявили ранее
  ... // другие свойства
}

И затем, когда React получает команду на рендер интерфейса (первичный рендер), он итерационно проходит по всему дереву со следующими инструкциями:

  • если type является строкой, то генерируем указанный в строке HTML-элемент.
  • если type это функция (как например наш компонент), то вызываем её и проходимся по дереву, которое эта функция нам вернула.

И так до тех пор, пока не получится дерево DOM элементов готовых к рендеру. Например:

const Component = () => {
  return (
    <div>
       <Input placeholder="Text1" id="1" />
       <Input placeholder="Text2" id="2" />
    </div>
  )
}

будет представлен как:

{
  type: 'div',
  props: {
    // children are props!
    children: [
      {
        type: Input,
        props: { id: "1", placeholder: "Text1" }
      },
      {
        type: Input,
        props: { id: "2", placeholder: "Text2" }
      }
    ]
  }
}

Ну и наконец, когда всё дерево будет готово, React добавит Virtual DOM элементы в актуальный document используя appendChild.

Reconciliation и обновление стейта

После этого начинается самое интересное. Предположим, один из компонентов в нашем дереве имеет стейт и сработало его обновление (повторный рендеринг). React должен обновить все элементы, которые зависят от новых данных нашего состояния. Или, возможно, добавить или удалить какие-то элементы.

Таким образом, React снова начинает свой путь по дереву, начиная с места, где произошло обновление стейта. Итак, у нас есть следующий код:

const Component = () => {
  // возвращаем только 1 элемент
  return <Input />
}

React понимает, что функция Component вернёт следующий объект:

{
  type: Input,
  ... // другие свойства
}

Он сравнит поле type этого объекта до и после обновления стейта. Если type такой же, то компонент Input будет помечен "needs update" и запустится его обновление. Если type изменился, тогда React в процессе цикла ре-рендеров удалит (размонтирует) старый компонент и добавит (смонтирует) новый. В нашем случае type не изменился, это была та же ссылка на функцию Input, именно поэтому React не создал новый компонент, а просто перерисовал его с новыми данными.

Если же в условном рендеринге мы вернём новый компонент:

const Component = () => {

  if (isCompany) return <Input />

  return <TextPlaceholder />
}

и предположим, что обновление было запущено из-за смены значения isCompany с true на false, то объекты, которые сравнивает React будут такими:

// до обновления isCompany равно "true"
{
  type: Input,
  ...
}

// после обновления, isCompany уже "false"
{
  type: TextPlaceholder,
  ...
}

Вы обратили внимание, да? Type изменился с ссылки Input на ссылку TextPlaceholder, поэтому React размонтирует компонент Input и удалит из DOM всё, что с ним связано. React смонтирует компонент TextPlaceholder и впервые добавит его в DOM. А всё, что было связано с Input будет удалено, включая стейт. И, соответственно, всё что в него было введено, будет уничтожено.

Ответ на загадку

Теперь, имея все эти знания посмотрим ещё раз на пример из начала статьи:

const Form = () => {
  const [isCompany, setIsCompany] = useState(false);

  return (
    <React.Fragment>
      ... // checkbox somewhere here
      {isCompany ? (
        <Input id="company-tax-id-number" placeholder="Enter you company Tax ID" ... />
      ) : (
        <Input id="person-tax-id-number" placeholder="Enter you personal Tax ID" ... />
      )}
    </React.Fragment>
  )
}

Если переменная isCompany изменит значение с true на false, какие объекты React будет сравнивать?

До, когда isCompany равно true

{
  type: Input,
  ... // другие значения, включая id="company-tax-id-number"
}

После, isCompany равно false

{
  type: Input,
  ... // другие значения, включая id="person-tax-id-number"
}

С точки зрения React type не изменил значения. Type в обоих этих объектах ссылается на одну и ту же функцию: Input. Всё, что изменилось это лишь свойства: id, placeholder, итд.

Так что в этом случае React делает то, что и должен: он возьмёт компонент Input, и просто обновит его с новыми данными. То есть произойдёт ре-рендер. Всё что было связано с инпутом как было в DOM, так там и осталось. Ничего не уничтожилось. Это и приводит к тому поведению, которое мы увидели: я что-то ввожу в поле ввода, прожимаю чекбокс, и текст всё ещё остаётся.

Такое поведение не всегда плохо. Зачастую это именно то, что нужно: ре-рендерить компонент, а не маунтить его заново. Но конкретно в данном случае я хочу это исправить и быть уверенным, что эти компоненты создаются заново. Они разные с точки зрения бизнес-логики и я не хочу переиспользовать один и тот же инпут.

Есть два простых способа исправить это: массивы и key.

Reconciliations и массивы

До текущего момента просто упоминалось, что в нашем дереве есть массивы. Маловероятно, что кто-то сможет написать React приложение, в котором все компоненты возвращают лишь один элемент. Теперь пришло время детальнее поговорить о массивах компонентов и о том, как они себя ведут, в разрезе повторных рендеров. Вернёмся к нашему компоненту Form, который по сути тоже возвращает массив компонентов, ведь помимо Input там есть ещё и чекбокс. Взглянем на него ещё раз:

const Form = () => {
  const [isCompany, setIsCompany] = useState(false);

  return (
    <React.Fragment>
      <Checkbox onChange={() => setIsCompany(!isCompany)} />
      {isCompany ? (
        <Input id="company-tax-id-number" ... />
      ) : (
        <Input id="person-tax-id-number" ... />
      )}
    </React.Fragment>
  )
}

В процессе ре-рендера, когда React видит массив дочерних компонентов вместо одного элемента, он просто перебирает каждый из них и сравнивает type "до" и "после" в соответствии с их положением в массиве.

По сути, если я переключу чекбокс, то React увидит следующий список объектов:

[
  {
    type: Checkbox,
  },
  {
    type: Input, // наш условный рендеринг Input
  }
]

и пройдётся по ним, один за другим. Первый элемент. type имеет значение Checkbox - переиспользуем его и ре-рендерим. Второй элемент type имеет значение Input - то же самое, тот же процесс. И так далее.

Даже если какой-то условный рендеринг выполнен следующим образом:

isCompany ? <Input /> : null

React по-прежнему будет иметь точное количество элементов массива. Просто иногда один из элементов массива будет null. Перепишем компонент Form с использованием этого способа:

const Form = () => {
  const [isCompany, setIsCompany] = useState(false);

  return (
    <>
      <Checkbox onChange={() => setIsCompany(!isCompany)} />
      {isCompany ? <Input id="company-tax-id-number" ... /> : null}
      {!isCompany ? <Input id="person-tax-id-number" ... /> : null}
    </>
  )
}

Это будет массив из трёх элементов: Checkbox, Input или null, и Input или null.

Итак, что же произойдёт, если состояние изменится и произойдёи повторный рендеринг компонента Form?

До, когда isCompany имеет значение true:

[
  { type: Checkbox },
  null,
  { type: Input }
]

И после, когда isCompany меняется на false:

[
  { type: Checkbox },
  { type: Input },
  null
]

Когда React начнёт сравнивать их элемент за элементом, то получим следующую картину:

  • Первый элемент, Checkbox до и после → перерисовываем Checkbox * Второй элемент, null до и Input после → монтируем Input * Третий элемент, Input до, null после → размонтируем Input

И... Магия! Изменяя положение компонента внутри массива, но при этом не меняя логики, баг исправлен. И поля ввода ведут себя так, как ожидалось. Вот пример на Code Sandbox

Reconciliation и "key"

Есть ещё один способ пофиксить такое поведение. С помощью аттрибута "key".

Свойство "key" должно быть знакомо тем, кто использовал списки в компонентах. React заставляет нас использовать это свойство, в случаях если мы итерируем список данных внутри компонента.

const data = ["1", "2"];

const Component = () => {
  // "key" обязателен!
  return data.map(value => <Input key={value} />)
}

Вывод этого компонента теперь нам понятен: просто список объектов с полем type

[
  { type: Input }, // "1" - это данные
  { type: Input } // "2" - это данные
]

Но проблема с динамическими списками в их динамичности. Мы можем их сортировать, добавлять элементы в начало или конец, и проводить какие-то другие манипуляции.

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

[
  { type: Input }, // теперь "2" - это данные, но React это не знает
  { type: Input } // теперь "1" - это данные, но React это не знает
]

Как убедиться, что переиспользуется тот компонент, который нам нужен? Потому что, раз мы полагаемся на порядок элементов в массиве, то будем использовать экземпляр из первого элемента для второго. И наоборот. Если каждый компонент имеет свой стейт, то это приведёт к странному поведению: если вы введёте текст в первый инпут, а затем переместите его на вторую позицию, то текст останется в первом инпуте.

Вот почему нам нужен проп "key", это своего рода идентификатор элемента внутри React. Используя его в списке дочерних элементов именно он используется при повторном рендере. Если у элемента есть проп key вместе с type, то при повторном рендеринге, React будет использовать все элементы с соотвествующим стейтом и DOM, в независимости от их положения внутри массива, если key и type совпадают "до" и "после".

До пересортировки:

[
  { type: Input, key: "1" }, // "1" это данные
  { type: Input, key: "2" } // "2" это данные
]

После пересортировки:

[
  { type: Input, key: "2" }, // теперь "2" это данные и React знает об этом, потому что есть "key"
  { type: Input, key: "1" } // теперь "1" это данные и React знает об этом, потому что есть "key"
]

Теперь, когда мы указали key, после ре-рендера React знает, что нужно использовать уже существующий в DOM элемент. Поэтому просто поменяет их местами, и текст введённый в первый компонент Input переместится вместе с ним на вторую позицию.

Дополнительные примеры поведения

Ссылка на пример Code Sandbox

Почему это всё важно и какое отношение это имеет к нашей ошибке? Дело в том, что key не ограничивается только динамическими массивами. Это просто атрибут компонента и в любой другой ситуации он будет вести себя ровно так же. И теперь, чтобы починить наш компонент Form нам нужно лишь сказать React, что эти два компонента Input разные и не должны переиспользовать одну и ту же ссылку. И если мы добавим свойство key мы добьёмся именно этого:

{isCompany ? (
  <Input id="company-tax-id-number" key="company-tax-id-number" ... />
) : (
  <Input id="person-tax-id-number" key="person-tax-id-number" ... />
)}

Теперь список дочерних компонентов будет выглядеть так:

До, isCompany имеет значение true

[
  { type: Checkbox },
  { type: Input, key: "person-tax-id-number" }
]

После, когда значение изменилось на false

[
  { type: Checkbox },
  { type: Input, key: "company-tax-id-number" }
]

Вуаля! Теперь React размонтирует старый компонент и смонтирует новый. Состояние сбросится до пустого, при переключении чекбокса. Пример на Code Sandbox

Использование "key" для принудительного переиспользования компонента

Если бы нам действительно нужно было переиспользовать определённый компонент, то свойство key нам бы в этом помогло. Помните наш компонент, где мы пофиксили баг с помощью смены позиции элемента в массиве?

const Form = () => {
  const [isCompany, setIsCompany] = useState(false);

  return (
    <React.Fragment>
      <Checkbox onChange={() => setIsCompany(!isCompany)} />
      {isCompany ? <Input id="company-tax-id-number" ... /> : null}
      {!isCompany ? <Input id="person-tax-id-number" ... /> : null}
    </React.Fragment>
  )
}

Когда состояние isCompany меняется, компоненты Input будут размонтироваться и монтироваться в зависимости от их смены позиции в массиве. НО! Если мы добавим одинаковый key каждому из них, то эта магия исчезнет.

<React.Fragment>
  <Checkbox onChange={() => setIsCompany(!isCompany)} />
  {isCompany ? <Input id="company-tax-id-number" key="tax-input" ... /> : null}
  {!isCompany ? <Input id="person-tax-id-number" key="tax-input" ... /> : null}
</React.Fragment>

При изменении данных и повторной отрисовке, React будет сравнивать следующие объекты:

До, когда isCompany имеет значение true

[
  { type: Checkbox },
  null,
  { type: Input, key: "tax-input" }
]

После, когда изменили значение на false

[
  { type: Checkbox },
  { type: Input, key: "tax-input" }
  null
]

React видит список дочерних элементов и видит, что до и после ре-рендера в списке есть объект с типом Input и с одним и тем же значением свойства key. Поэтому он подразумевает, что компонент просто поменял свою позицию и поэтому переиспользует тот экземпляр Input, что уже был создан ранее. Если мы что-то введём в поле ввода, а затем прожмём чекбокс, то состояние сохранится, несмотря на то, что технически это разные компоненты.

Конкретно для данного примера, это лишь забавное поведение и вряд ли имеет какую-то ценность на практике. Но я могу предположить, что это можно использовать для перфоманс оптимизации таких компонентов, как аккордеон, содержимое вкладок, или некоторых галлерей. Пример на Code Sandbox

Почему нам не нужен "key" за пределами массивов?

Теперь, когда алгоритм реконсиляции более-менее ясен, давайте разберёмся с ещё несколькими небольшими вопросами. Например, вы замечали, что React никогда не просит вас использовать key, если только вы не перебираете массив?

Итак, определим один компонент:

const data = ["1", "2"];

const Component = () => {
  // "key" обязателен!
  return (
    <React.Fragment>
      {data.map(value => <Input key={value} />)}
    </React.Fragment>
  )
}

И ещё один:

const Component = () => {
  // никто про "key" не спрашивает
  return (
    <React.Fragment>
      <Input />
      <Input />
    </React.Fragment>
  )
}

С точки зрения вывода они одинаковы:

[
  { type: Input },
  { type: Input },
]

Так почему в первом случае обязательно требуется поставить key, а во втором нет?

Дело в том, что в первом случае используется динамический массив. React не может предугадать, что с ним произойдёт в дальнейшем: вы можете добавить элемент, удалить, сортировать, и так далее. Потому он и требует от вас указать key.

Ну и что в этом такого?, - спросите вы. А давайте попробуем сделать так, чтобы в массиве компонентов использовался один и тот же ключ, но указывался он по условию. Например:

const Component = () => {
  const [isReverse, setIsReverse] = useState(false);
  // здесь по-прежнему никто не просит от вас "key"
  return (
    <React.Fragment>
      <Input key={isReverse ? 'some-key' : null} />
      <Input key={!isReverse ? 'some-key' : null} />
    </React.Fragment>
  )
}

Подумайте, что случится если я введу данные в поля ввода, а затем буду переключать чекбокс) Это будет вашим домашним заданием.

Динамические массивы и обычные элементы вместе

Если вы всё это время внимательно читали статью, то сейчас появится вероятность небольшого сердечного приступа. Честно, у меня один был, в процессе исследования. Потому что...

  • Раз элементы в динамическом массиве преобразуются в массив элементов, которые ничем не отличается от обычных элементов склеенных вместе
  • И если я добавлю обычный элемент после динамического массива
  • А после добавлю/удалю один из элементов в массиве

означает ли это, что элементы после этого массива будут всегда пересоздаваться? Короче: это кошмар для перфоманса или нет?

const data = ['1', '2'];

const Component = () => {

  return (
    <React.Fragment>
      {data.map((i) => <Input key={i} id={i} />)}
      <!-- будет ли пересоздаваться этот компонент, если я добавлю/удалю что-то в массиве выше? -->
      <Input id="3" />
    </React.Fragment>
  )
}

Потому что, если это массив из трёх элементов, два из которых динамические, а третий нет, то да, должен пересоздаваться. Посмотрим на объект:

[
  { type: Input, key: 1 }, // динамический элемент из массива
  { type: Input, key: 2 }, // динамический элемент из массива
  { type: Input  }, // обычный статический элемент
]

И если мы добавим новый элемент в массив data, то место обычного элемента, займёт динамический с key=3. Обычный переместится на новую позицию, что с точки зрения React является новым элементом, значит он должен заново смонтироваться?

К счастью нет, это не так. React умнее этого и позаботился о нас.

Когда мы смешиваем динамический массив и статические элементы, React просто создаёт массив динамических элементов и ставит их отдельно от статических.

[
  // динамические элементы располагаются сверху
  [{ type: Input, key: 1 }, { type: Input, key: 2 }],
  {
    type: Input // а это статический элемент, который мы добавили вручную
  }
]

Как вы понимаете из кода выше, при изменении динамического массива, наш статический элемент никогда не будет пересоздаваться. Так что сердвечный приступ откладывается.

Почему мы не можем объявлять компоненты внутри других компонентов?

Здесь нужно упомянуть ещё один важный момент касаемый реконсиляции: мы наконец можем дать ответ на вопрос "почему объявление компонентов внутри другого компонента является антипаттерном?".

Итак, когда мы делаем так:

const Component = () => {
  const Input = () => <input />;

  return <Input />
}

боги ре-рендеров будут крайне недовольны. Когда Component по какой-то причине будет ре-рендерится, то компонент Input будет пересоздаваться. Кошмар для перфоманса, куча потенциальных багов.

Если мы посмотрим на этот код с точки зрения реконсиляции, то увидим следующую структуру:

{
  type: Input,
}

Просто объект со ссылкой на функцию Input. Однако функция создана внутри нашего Component. Она локальная для данного компонента, и поэтому, как итог, будет пересоздаваться при каждом рендере. Далее, когда алгоритм реконсиляции дойдёт до сравнения двух объектов, он будет сравнивать две функции Input, которая была до ререндера, и которая создалась после. А теперь вспоминаем JavaScript:

const a = () => {};
const b = () => {};

console.log(a === b) // всегда false, потому что несмотря на то, что функции одинаковые, на самом деле они смотрят на разные объекты в памяти.

Как результат type у объектов будет иметь разные ссылки, а значит React пересоздаст компонент.

На этом сегодня всё.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment