awesome-everything EN
↑ Обратно к восхождению

Браузер и фронтенд-рантайм

React fiber: чтение кода

Суть Читайте реальные сниппеты React — баг индексного ключа при reorder, побочный эффект в render-фазе, неправильное разделение приоритетов и сорванный bailout — и выбирайте сеньорский фикс.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на senior-высоте — в орбите
◷ 14 min

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

Цель

Отработайте цикл, который вы запускаете в каждом инциденте производительности или корректности React: прочитать компонент, предсказать, как reconciliation, разделение render/commit и планировщик lanes его обработают, и выбрать самый рычажный фикс.

Сниппет 1 — переупорядочиваемый список

function TodoList({ todos }) {
  // todos можно переупорядочивать перетаскиванием, у каждой строки
  // есть неуправляемый <input>, куда пользователь печатает заметки
  return (
    <ul>
      {todos.map((todo, i) => (
        <li key={i}>
          <span>{todo.title}</span>
          <input type="text" placeholder="notes…" />
        </li>
      ))}
    </ul>
  );
}
Викторина

Пользователь печатает заметки в строку 3, затем перетаскивает строку 1 в самый низ. Заметки как будто прыгают на неправильную строку. В чём баг и исправление?

Сниппет 2 — работа в теле функции

function Chart({ points }) {
  // строим производный датасет и логируем аналитическое событие
  const series = expensiveTransform(points);
  analytics.track('chart_rendered', { count: series.length });

  return <svg>{series.map(p => <Dot key={p.id} {...p} />)}</svg>;
}
Викторина

Какая строка — латентный баг при concurrent rendering в React 18 и почему?

Сниппет 3 — разделение приоритетов

function Search({ allItems }) {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState(allItems);
  const [isPending, startTransition] = useTransition();

  function onChange(e) {
    startTransition(() => {
      setQuery(e.target.value);                          // (A)
      setResults(filter(allItems, e.target.value));      // (B)
    });
  }
  return <><input value={query} onChange={onChange} />{isPending && <Spinner/>}<List items={results}/></>;
}
Викторина

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

Сниппет 4 — сорванный bailout

const Row = React.memo(function Row({ item, onSelect }) {
  return <li onClick={() => onSelect(item.id)}>{item.label}</li>;
});

function List({ items, onSelect }) {
  return (
    <ul>
      {items.map(item => (
        <Row key={item.id} item={item} onSelect={() => onSelect(item.id)} />
      ))}
    </ul>
  );
}
Викторина

Каждый Row перерендеривается всякий раз, когда перерендеривается List, хотя React.memo оборачивает Row и items не менялись. Почему и какой минимальный фикс?

Итог

Четыре чтения на уровне fiber: индексный ключ следует за позицией, поэтому неуправляемое состояние на fiber дрейфует к неправильному элементу при reorder — используйте стабильный id; побочный эффект в теле функции выполняется непредсказуемое число раз при повторе — перенесите его в useEffect или в событие; срочное значение input должно оставаться вне startTransition, а внутрь идёт только дорогое обновление; и inline-стрелка-проп — свежая ссылка каждый рендер, тихо срывающая React.memo. Прочитайте код, предскажите, как реконсилер его обработает, исправьте идентичность или ссылку прежде, чем тянуться к любому другому рычагу.

Продолжить восхождение ↑React fiber: усмирить шторм перерендеров
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.