Irbisa · Шпаргалка

Рендеринг и reconciliation

Что вызывает рендер, фазы render и commit, reconciliation и key, Fiber, конкурентный рендеринг, transitions, Suspense, гидратация

Senior-разработчик · 20 вопросов

  1. 01

    Что заставляет React-компонент перерендериться?

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

  2. 02

    Чем отличаются React-элемент, компонент и экземпляр компонента?

    Компонент представляет собой функцию, которую вы пишете; элемент является простым неизменяемым объектом (type, props, key), который создаёт JSX, чтобы описать, что рендерить; экземпляр означает то, что React хранит для элемента, смонтированного на позиции в дереве: fiber с его стейтом, хуками и эффектами.

    function Avatar({ name }: { name: string }) {  const [expanded, setExpanded] = useState(false);  return <button onClick={() => setExpanded(true)}>{expanded ? name : name[0]}</button>;} const element = <Avatar name="Ada" />;…
  3. 03

    Чем отличаются фаза рендера и фаза коммита в React?

    В фазе рендера React вызывает ваши компоненты и вычисляет, что изменилось; в фазе коммита он применяет эти изменения к DOM и запускает refs и эффекты.

  4. 04

    В каком порядке появятся эти логи при монтировании (без StrictMode) и почему?

    function Child() {  console.log('Child render');  useLayoutEffect(() => console.log('Child layout effect'), []);  useEffect(() => console.log('Child effect'), []);  return <p>child</p>;} function Parent() {  console.log('Parent render');  useLayoutEffect(() => console.log('Parent layout effect'), []);  useEffect(() => console.log('Parent effect'), []);  return <Child />;}

    Parent render, Child render, Child layout effect, Parent layout effect, Child effect, Parent effect.

  5. 05

    Что такое reconciliation и какие эвристики использует React при сравнении двух деревьев элементов?

    Reconciliation (согласование) сравнивает дерево элементов нового рендера с предыдущим и решает, какие DOM-узлы сохранить, обновить или заменить.

    function Page({ isAdmin }: { isAdmin: boolean }) {  // same type at the same position: Panel keeps its state when isAdmin flips  return isAdmin ? <Panel title="Admin" /> : <Panel title="User" />;} function Card({ boxed }: { boxed: boolean }) {…
  6. 06

    Сколько раз Expensive напишет в лог после трёх кликов по кнопке и почему?

    function Expensive() {  console.log('Expensive render');  return <p>Heavy chart</p>;} function Counter({ children }: { children: React.ReactNode }) {  const [count, setCount] = useState(0);  return (    <div>      <button onClick={() => setCount(count + 1)}>Clicked {count}</button>      {children}    </div>  );} export default function App() {  return <Counter><Expensive /></Counter>;}

    Один раз, при монтировании: Expensive создаётся в App и попадает в Counter как children, поэтому при ререндере Counter проп children остаётся тем же объектом, и React его пропускает.

  7. 07

    Что произойдёт, если вызвать сеттер стейта во время рендера, и когда это допустимый приём?

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

    function List({ items }: { items: Item[] }) {  const [selection, setSelection] = useState<Item | null>(null);  const [prevItems, setPrevItems] = useState(items);   if (items !== prevItems) {    // runs once per change; React re-runs List before rendering its children…
  8. 08

    Что такое конкурентный рендеринг и что изменилось в React 18?

    Конкурентный рендеринг позволяет React готовить новый UI в фоне, приостанавливать его ради чего-то более срочного и выбрасывать, если он больше не нужен.

  9. 09

    Чем рендер обновления внутри startTransition отличается от срочного обновления и что произойдёт, если пользователь печатает во время этого рендера?

    Срочное обновление рендерится синхронно и коммитится до следующего кадра; transition рендерится в фоне, может быть прерван и держит текущий UI, пока новый не готов.

    function SearchPage() {  const [text, setText] = useState('');  const [query, setQuery] = useState('');  const [isPending, startTransition] = useTransition();   return (…
  10. 10

    Что делает useDeferredValue и когда его стоит использовать вместо debounce?

    useDeferredValue(value) во время срочного рендера возвращает предыдущее значение, а затем в фоне ререндерит с новым, поэтому медленная часть UI отстаёт от быстрого ввода, не блокируя его.

    const SlowList = memo(function SlowList({ query }: { query: string }) {  const items = filterHugeList(query); // expensive  return <ul>{items.map((item) => <li key={item.id}>{item.name}</li>)}</ul>;}); function Search() {…
  11. 11

    Что происходит внутри React, когда компонент приостанавливается (suspend), и что изменилось в React 19?

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

    const Comments = lazy(() => import('./Comments')); function Post({ postPromise }: { postPromise: Promise<PostData> }) {  const post = use(postPromise); // suspends until resolved  return (    <article>…
  12. 12

    Что такое гидратация и что добавляет выборочная гидратация (selective hydration) в React 18 и новее?

    Гидратация означает, что React на клиенте берёт HTML, отрендеренный на сервере, строит своё дерево поверх существующего DOM и навешивает обработчики, а не создаёт DOM заново.

    // entry: attach to the server HTML instead of creating new DOMhydrateRoot(document.getElementById('root')!, <App />); function LocalTime() {  // wrong: the server and the client format different times  // return <span>{new Date().toLocaleTimeString()}</span>;…
  13. 13

    Что такое React Fiber и какую проблему он решил?

    Fiber представляет собой reconciler React начиная с 16-й версии: каждый экземпляр элемента становится fiber-узлом, единицей работы, которую React может приостановить, продолжить или выбросить.

  14. 14

    Как React согласует (reconcile) переупорядоченный список с ключами и какая перестановка для него самая дорогая?

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

  15. 15

    Как React внутри планирует обновления и расставляет приоритеты: что такое lanes и time slicing?

    Каждому обновлению назначается lane, бит в битовой маске, который кодирует его приоритет, и React всегда сначала рендерит самые срочные ожидающие lanes.

  16. 16

    Что такое сигналы в Solid, Preact или Angular, и почему React перерендеривает компоненты вместо точечных (fine-grained) обновлений?

    Сигнал представляет собой реактивное значение, которое запоминает, кто его читает: при изменении обновляются только эффекты и DOM-привязки, которые его читали, а функция компонента заново не запускается.

    type Effect = () => void;let running: Effect | null = null; function createSignal<T>(value: T) {  const subscribers = new Set<Effect>();  const read = () => {…
  17. 17

    Почему навигация, обёрнутая в transition, оставляет на экране старую страницу вместо fallback у Suspense и как управлять тем, какие fallback-и появляются?

    Во время transition React отказывается прятать уже видимый контент: если новый экран приостанавливается внутри границы, которая уже показывает контент, React держит старый UI (с isPending равным true), пока не придут данные.

    function App() {  const [page, setPage] = useState('/');  const [isPending, startTransition] = useTransition();   function navigate(url: string) {    // keeps the current page on screen while the next one loads…
  18. 18

    Найдите баг: переключение вкладки обёрнуто в startTransition, но после await старая вкладка исчезает и мелькает fallback от Suspense.

    function Tabs() {  const [tab, setTab] = useState<'about' | 'posts'>('about');  const [isPending, startTransition] = useTransition();   function selectTab(next: 'about' | 'posts') {    startTransition(async () => {      await saveLastTab(next);      setTab(next);    });  }   return (    <>      <TabBar active={tab} pending={isPending} onSelect={selectTab} />      <Suspense fallback={<Spinner />}>{tab === 'about' ? <About /> : <Posts />}</Suspense>    </>  );}

    setTab выполняется после await, вне синхронной области startTransition, поэтому React считает его обычным срочным обновлением.

  19. 19

    Что делает flushSync и когда он действительно нужен?

    flushSync(fn) из react-dom синхронно рендерит и коммитит обновления, сделанные внутри fn, поэтому после возврата DOM уже обновлён.

    import { flushSync } from 'react-dom'; function Chat() {  const [messages, setMessages] = useState<Message[]>([]);  const listRef = useRef<HTMLUListElement>(null);…
  20. 20

    Что происходит, когда компонент бросает ошибку во время рендера, и что такое onCaughtError, onUncaughtError и onRecoverableError в React 19?

    React поднимается до ближайшего error boundary и рендерит его fallback; если границы нет, он размонтирует весь корень, и страница становится пустой.

    import { createRoot } from 'react-dom/client'; const root = createRoot(document.getElementById('root')!, {  onCaughtError(error, errorInfo) {    report(error, { componentStack: errorInfo.componentStack, handled: true });  },…