Похоже, далеко не все понимают как работает React, в частности: контекст, рендер, как всплывают события и почему происходят ремаунты, казалось бы в тех местах, где их не должно быть. Я конечно не претендую на реакт-эксперта, но хотел бы прояснить кое-какие моменты, которые не описаны в доке, или упоминаются как-то вскользь. Будет много ссылок на демки где можно поиграться, посмотреть логи и подробнее разобраться.
- Начнем с самого простого - контекста. Контекст нужен что бы доставить данные вглубь React дерева, при это избегая props-drillingа, пробрасывания данных через кучу родителей к дочерним. В целом в доке все отлично расписано, но многие не обращают внимание на один важный момент: все потребители контекста, неважно, это
Context.Consumer или React.useContext(Context) , будут перерисовываться (т.е. вызван render метод, а не обновлен DOM) каждый раз когда значение контекста меняется. Это значит что использовать объекты, без предварительного кэширования, в контексте - плохая идея, т.к. все потребители будут перерисовываться на каждый рендер провайдера, даже если данные в этом объекте никогда не меняются.
<Context.Provider value={{ value: 20 }}>{children}</Context.Provider>
https://codesandbox.io/s/object-in-the-context-nh1ud?file=/src/App.tsx в этом примере на каждый рендер провайдера создается новый объект, что вызывает перерасчет всех потребителей, в больших приложениях это может привести к большому даунгрейду перфа. Фикситься достаточно просто, использованием
useMemo или useRef, в зависимости от того, меняются ли значения в объекте.- Как я понял, многие предполагают что компонент монтируются (или по крайней мере его рендер вызывается) в момент создание элемента
const child = <Child /> , даже если элемент не был использован в ретурне рендера родителя. На самом деле это не так, компонент не монтируется и даже его рендер не вызывается, если элемента этого компонента нет в React дереве, другими словами если компонент не был использован в ретурне рендера родителя. Отсюда вытекает еще одна неочевидность: компонент монтируются столько раз, сколько раз его элемент появился в React дереве.
const child = <Child name="renderChild" />;
const unusedChild = <Child name="unusedChild" />;
return (
<>
{child}
{child}
{child}
{false && unusedChild}
</>
)
https://codesandbox.io/s/react-render-8zm5d?file=/src/App.tsx:1059-1070 в примере выше
Child с названием renderChild монтируются 3 раза, т.к. 3 элемента используется в дереве, а вот unusedChild вообще не монтируются да и рендер для него тоже не вызовется, поскольку его нет в дереве. Все тоже касается и последующих ререндеров, но здесь есть 2 НО, когда ререндера может не произойти: 1) компонент использует
React.memo - пропсы не поменялись, ререндер не произошел2) новый элемент не был создан в момент ререндера родителя - это можно сделать различными способами, в примере по ссылке это делается через создание элементов за пределами рендер метода. Но того же эффекта можно добиться положив элемент в
useRef или useMemo . Чет мне кажется, что работает оно так-же как и React.memo только без дополнительных проверок пропсов в момент рендера, а просто сравнивается сслыка элемента - я не проверял в сорцах, просто предположение.Продолжение в первом коменте...
Читать в ноушен: https://bit.ly/3jwrFYY
#react