Интерпретация новых функций React18 и руководство по обновлению полной версии

Уведомление

React 18Мы отказались ie11от поддержки и 2022年6月15日прекратим ее поддержку ie. Если вам нужна совместимость, вам нужно откатиться на React 17версию.

React 18 中引入的新特性是使用现代浏览器的特性构建的,在IE中无法充分polyfill,比如micro-tasks 

обновление

  • Новый проект: используйте напрямую npmили yarnустановите последнюю версию зависимостей (если это js, вам не нужно устанавливать файл объявления типов)
npm i react react-dom --save
    
npm i @types/react @types/react-dom -D 
  • Старый проект: сначала измените номер версии в зависимости на самый последний, затем удалите node_modulesпапку и переустановите:
npm i 

новые возможности

1. API рендеринга

Для лучшего управления введен новый root节点, который также поддерживает (параллельный режим рендеринга), что позволяет войти в (параллельный режим).React 18root APIroot APInew concurrent rendererconcurrent mode

// React 17
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

const root = document.getElementById('root')!;

ReactDOM.render(<App />, root);

// React 18
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

const root = document.getElementById('root')!;

ReactDOM.createRoot(root).render(<App />); 

В то же время при удалении компонента нам также необходимо unmountComponentAtNodeобновиться до root.unmount:

// React 17
ReactDOM.unmountComponentAtNode(root);

// React 18
root.unmount(); 

Советы: Если мы React 18будем использовать старый render api, то после запуска проекта вы увидите предупреждение в консоли:

11.jpgЭто означает, что вы можете обновить свой проект непосредственно до React 18версии, не вызывая прямого попадания break change. Если вам необходимо сохранить React 17функции версии, вы можете игнорировать эту ошибку, поскольку она 18совместима между версиями.

В дополнение к этому, он был удален React 18из метода , поскольку его использование обычно не дает ожидаемых результатов.render回调函数Suspense

В новой версии, если вам нужно renderиспользовать функцию обратного вызова в методе, мы можем реализовать ее в компоненте через useEffect:

// React 17
const root = document.getElementById('root')!;
ReactDOM.render(<App />, root, () => {
  console.log('渲染完成');
});

// React 18
const AppWithCallback: React.FC = () => {
  useEffect(() => {
    console.log('渲染完成');
  }, []);
  return <App />;
};
const root = document.getElementById('root')!;
ReactDOM.createRoot(root).render(<AppWithCallback />); 

Наконец, если ваш проект использует ssrрендеринг на стороне сервера, вам необходимо hydrationперейти на hydrateRoot:

// React 17
import ReactDOM from 'react-dom';
const root = document.getElementById('root');
ReactDOM.hydrate(<App />, root);

// React 18
import ReactDOM from 'react-dom/client';
const root = document.getElementById('root')!;
ReactDOM.hydrateRoot(root, <App />); 

Кроме того, вам также необходимо обновить TypeScriptопределение типа. Если ваш проект его использует TypeScript, самое примечательное изменение заключается в том, что теперь при определении propsтипа, если вам нужно получить подкомпонент children, то вам нужно 显式的定义它, например:

// React 17
interface MyButtonProps {
  color: string;
}

const MyButton: React.FC<MyButtonProps> = ({ children }) => {
  // 在 React 17 的 FC 中,默认携带了 children 属性
  return <div>{children}</div>;
};

export default MyButton;

// React 18
interface MyButtonProps {
  color: string;
  children?: React.ReactNode;
}

const MyButton: React.FC<MyButtonProps> = ({ children }) => {
  // 在 React 18 的 FC 中,不存在 children 属性,需要手动申明
  return <div>{children}</div>;
};

export default MyButton; 

2. автоматическая пакетная обработка setState

React 18Улучшения производительности реализованы «из коробки» за счет выполнения пакетной обработки по умолчанию.

Пакетная обработка означает, что для повышения производительности 多个状态更新пакетная обработка объединяется в пакеты на уровне данных 一次更新多个渲染объединяется в пакеты на уровне представления 一次渲染).

1. До React 18:

Теперь React 18 之前мы React 事件处理函数выполняем только пакетные обновления в формате . По умолчанию обновления в promise, setTimeout, 原生事件处理函数в или 任何其它事件内не группируются:

Сценарий 1: функция обработчика событий React

import React, { useState } from 'react';

// React 18 之前
const App: React.FC = () => {
  console.log('App组件渲染了!');
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(0);
  return (
    <button
      onClick={() => {
        setCount1(count => count + 1);
        setCount2(count => count + 1);
        // 在React事件中被批处理
      }}
    >
      {`count1 is ${count1}, count2 is ${count2}`}
    </button>
  );
};

export default App; 

Нажмите кнопку, чтобы распечатать console.log:

анимация2.gif

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

Однако, если мы поместим обновление статуса внутри promiseили setTimeout:

Случай 2: setTimeout

import React, { useState } from 'react';

// React 18 之前
const App: React.FC = () => {
  console.log('App组件渲染了!');
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(0);
  return (
    <div
      onClick={() => {
        setTimeout(() => {
          setCount1(count => count + 1);
          setCount2(count => count + 1);
        });
        // 在 setTimeout 中不会进行批处理
      }}
    >
      <div>count1: {count1}</div>
      <div>count2: {count2}</div>
    </div>
  );
};

export default App; 

Нажмите кнопку, чтобы перепечатать console.log:

Анимация 3.gif

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

Сценарий 3: Собственные события js

import React, { useEffect, useState } from 'react';

// React 18 之前
const App: React.FC = () => {
  console.log('App组件渲染了!');
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(0);
  useEffect(() => {
    document.body.addEventListener('click', () => {
      setCount1(count => count + 1);
      setCount2(count => count + 1);
    });
    // 在原生js事件中不会进行批处理
  }, []);
  return (
    <>
      <div>count1: {count1}</div>
      <div>count2: {count2}</div>
    </>
  );
};

export default App; 

Нажмите кнопку, чтобы перепечатать console.log:

Анимация 3.gif

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

2. В Реакте 18:

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

Однако в следующем примере React 18рендеринг будет выполнен дважды :

import React, { useState } from 'react';

// React 18
const App: React.FC = () => {
  console.log('App组件渲染了!');
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(0);
  return (
    <div
      onClick={async () => {
        await setCount1(count => count + 1);
        setCount2(count => count + 1);
      }}
    >
      <div>count1: {count1}</div>
      <div>count2: {count2}</div>
    </div>
  );
};

export default App; 

Подведем итог:

  • До версии 18 пакетная обработка автоматически выполнялась только в функции обработки событий реагирования, а в других случаях она обновлялась несколько раз.
  • После 18 пакетная обработка в любом случае будет выполняться автоматически, и несколько обновлений всегда будут объединены в одно.

3. флешсинк

Пакетная обработка — это функция 破坏性改动, если вы хотите выйти из пакетного обновления, вы можете использовать flushSync:

import React, { useState } from 'react';
import { flushSync } from 'react-dom';

const App: React.FC = () => {
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(0);
  return (
    <div
      onClick={() => {
        flushSync(() => {
          setCount1(count => count + 1);
        });
        // 第一次更新
        flushSync(() => {
          setCount2(count => count + 1);
        });
        // 第二次更新
      }}
    >
      <div>count1: {count1}</div>
      <div>count2: {count2}</div>
    </div>
  );
};

export default App; 

Примечание. flushSyncНесколько функций внутри функции setStateпо-прежнему обновляются пакетно , что позволяет точно контролировать ненужные пакетные обновления.

Для получения дополнительной информации о 批处理и вы можете обратиться к официальному подробному обзоруflushSync React по автоматическому пакетному пакетированию .

4. Предупреждение о статусе обновления при удалении компонентов.

При разработке мы периодически сталкиваемся со следующими ошибками:

Скриншот QQ 20220503200708.jpg

Эта ошибка означает: невозможно выполнить обновление статуса неподключенного (несмонтированного) компонента. Это недопустимая операция, указывающая на утечку памяти в нашем коде.

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

Первоначальное намерение этой ошибки заключалось в том, чтобы предназначаться для некоторых особых сценариев, таких 你在useEffect里面设置了定时器,或者订阅了某个事件,从而在组件内部产生了副作用,而且忘记return一个函数清除副作用,则会发生内存泄漏……как

Но в реальном развитии существует больше сценариев 我们在 useEffect 里面发送了一个异步请求,在异步函数还没有被 resolve 或者被 reject 的时候,我们就卸载了组件. В этом случае предупреждение также будет активировано. Однако в этом случае утечки памяти внутри компонента нет, поскольку асинхронная функция была подвергнута сборке мусора, и на этом этапе предупреждение вводит в заблуждение .

По этому поводу у React также есть официальное объяснение:

222.jpg

Подводя итог вышеизложенным причинам, в 2007 году React 18эта ошибка была официально удалена.

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

5. О возвращаемом значении компонентов React

  • В React можно вернуть только React 17один, если вам это нужно . Если вы вернете явно , консоль выдаст ошибку во время выполнения.空组件nullundefined
  • В React 18версии undefinedбольше не проверяются сбои, вызванные возвратом. Он может как возвращать null, так и возвращать undefined(но React 18файл dtsвсе равно будет проверяться и разрешен только возврат null. Эту ошибку типа можно игнорировать).

Официальное объяснение возвращаемого значения компонента: github.com/reactwg/rea…

6. Строгий режим

Журналы консоли больше не подавляются:

严格模式React работает с каждым компонентом по мере его использования 两次渲染, позволяя вам наблюдать некоторые неожиданные результаты. В версии React 17ведение 其中一次渲染журнала консоли отключено, чтобы его было легче читать.

Чтобы устранить замешательство сообщества по этому вопросу, в 2017 году React 18это ограничение было официально снято. Если он у вас установлен React DevTools, сообщения журнала для второго рендеринга будут выделены серым цветом и отображаются в консоли в мягкой форме.

Скриншот QQ 20220503205553.jpg

Официальное объяснение строгого режима: github.com/reactwg/rea…

7. Саспенс больше не требует отступления для захвата.

В React 18компоненте Suspenseчиновники 空的fallbackвнесли изменения в способ обработки атрибутов: больше нельзя пропускать границы 缺失值или . Вместо этого граница фиксируется и просматривается снаружи, и если она не найдена, она отображается как .值为nullfallbackSuspensefallbacknull

До обновления:

Раньше, если ваш Suspenseкомпонент не предоставлял fallbackсвойство, React молча пропускал его и продолжал поиск до следующей границы:

// React 17
const App = () => {
  return (
    <Suspense fallback={<Loading />}> // <--- 这个边界被使用,显示 Loading 组件
      <Suspense>                      // <--- 这个边界被跳过,没有 fallback 属性
        <Page />
      </Suspense>
    </Suspense>
  );
};

export default App; 

Команда React обнаружила, что это может привести к запутанным и трудным для отладки ситуациям. Например, если вы отлаживаете проблему и тестируете ее, создавая привязку к компоненту без свойства fallback, Suspenseэто может привести к неожиданным результатам и 不会警告сказать, что у него 没有fallbackесть свойство.

Обновлено:

Теперь React будет использовать текущий компонент Suspenseв качестве границ, даже если Suspenseзначение текущего компонента nullравно или undefined:

// React 18
const App = () => {
  return (
    <Suspense fallback={<Loading />}> // <--- 不使用
      <Suspense>                      // <--- 这个边界被使用,将 fallback 渲染为 null
        <Page />
      </Suspense>
    </Suspense>
  );
};

export default App; 

Это обновление означает нас 不再跨越边界组件. Вместо этого мы захватим границу и отрисуем ее fallbackтак, как если бы вы предоставили nullкомпонент, который возвращает объект . Это означает, что приостановленный Suspenseкомпонент будет вести себя так, как ожидалось, и fallbackне возникнет проблем, если вы забудете указать атрибут.

Официальное объяснение о Suspense: github.com/reactwg/rea…

Новый API

1. идентификатор использования

const id = useId(); 

Поддерживает один и тот же компонент для генерации одного и того же уникального идентификатора на клиенте и сервере во избежание hydrationнесовместимости. Это решает существующие проблемы в версиях React 17и ниже. 17Поскольку наш сервер предоставляет HTMLYes при рендеринге 无序的, useIdпринцип заключается в том, что каждый из них idпредставляет иерархическую структуру компонента в дереве компонентов.

Подробнее об useId смотрите в посте useId в рабочей группе .

Например, useSyncExternalStore.

useSyncExternalStoreЭто новый API, который претерпел изменения и был useMutableSourceизменен. Он в основном используется для решения проблемы разрыва внешних данных.

useSyncExternalStore позволяет компонентам React безопасно и эффективно читать внешние источники данных под CM, вызывая синхронное обновление данных. В параллельном режиме рендеринг React будет выполняться слайсами (в единицах волокна), а обновления с более высоким приоритетом могут быть перемежены посередине. Если общедоступные данные (например, данные в Redux) изменяются при обновлении с высоким приоритетом, необходимо перезапустить предыдущий рендеринг с низким приоритетом, иначе состояние будет противоречивым.

useSyncExternalStoreОбычно он используется сторонними библиотеками управления состоянием, и нам не нужно обращать на него внимание в повседневной работе. Потому что Reactон useStateизначально решил tear(撕裂)проблемы, связанные с функцией параллелизма. useSyncExternalStoreВ основном для разработчиков фреймворков, например , его нельзя использовать reduxнапрямую при управлении состоянием. Вместо этого он поддерживает внешний объект для реализации обновлений данных. Без управления он не может автоматически решить проблему разрыва. Поэтому такой API предоставляется внешнему миру.Reactstatestore发布订阅模式ReactReactReact

В настоящее время React-Redux 8.0он реализован useSyncExternalStoreна основе .

Дополнительные сведения об useSyncExternalStore см. в обзорной публикации useSyncExternalStore и сведения об API useSyncExternalStore .

3. использоватьInsertionEffect

const useCSS = rule => {
  useInsertionEffect(() => {
    if (!isInserted.has(rule)) {
      isInserted.add(rule);
      document.head.appendChild(getStyleForRule(rule));
    }
  });
  return rule;
};

const App: React.FC = () => {
  const className = useCSS(rule);
  return <div className={className} />;
};

export default App; 

Эти хуки рекомендуется css-in-jsиспользовать только библиотекам. Время выполнения этого хука — DOMпосле и до генерации useLayoutEffect. Его принцип работы примерно такой же, useLayoutEffectкак и у , за исключением того, что DOMссылка на узел в это время недоступна. Обычно он используется для <style>предварительного внедрения скриптов.

Дополнительные сведения об useInsertionEffect см. в Руководстве по обновлению библиотеки для<style> .

Параллельный режим

Параллельный режим (далее — CM) переводится как параллельный режим. Возможно, мы слышали эту концепцию много раз. Фактически, эта концепция стала очень зрелой в прошлом году и React 17может быть 试验性включена через некоторые API в CM.

CM 本身并不是一个功能,而是一个底层设计 

Параллельный режим помогает приложениям оперативно реагировать и корректировать их соответствующим образом в зависимости от возможностей устройства пользователя и скорости сети, устраняя ограничения за счет прерывания рендеринга 阻塞渲染. В Concurrentрежиме Reactнесколько состояний могут обновляться одновременно.

Возможно, это слишком сложно сказать, но это можно суммировать в одном предложении:

React 17Разница между и React 18заключается в следующем: от 同步不可中断更新изменено до 异步可中断更新.

Здесь наступает важный момент, пожалуйста, не пропускайте следующую часть:

Мы упоминали в начале статьи: В React 18, предусмотрены новые root api, нужно только renderобновиться createRoot(root).render(<App />), чтобы включить параллельный режим.

Итак, в этот момент некоторые ученики могут спросить: означает ли включение 并发模式включение 并发更新?

НЕТ! В React 17некоторых экспериментальных функциях в , включение 并发模式означает включение 并发更新, но React 18после выпуска официальной версии, из-за официальных корректировок политики, React больше не полагается на 并发模式включение 并发更新.

Другими словами: то, что он включен, 并发模式не обязательно означает, что он включен 并发更新!

Краткое содержание одним предложением: В 18, больше нет нескольких моделей, но они основаны 是否使用并发特性на 是否开启并发更新действиях .

v18Сколько версий имеется на рынке от самой старой версии до текущей React?

Его можно резюмировать с архитектурной точки зрения: в настоящее время существует две архитектуры:

  • 递归Обновление бесперебойное Stack Reconciler(старая архитектура)
  • 遍历Обновление в прерываемом режиме Fiber Reconciler(новая архитектура)

Вы можете выбрать, включать ли новую архитектуру , поэтому 并发更新для всех версий, имеющихся в настоящее время на рынке, Reactсуществуют четыре ситуации :

  1. Старая архитектура (v15 и предыдущие версии)
  2. Новая архитектура, одновременные обновления не включены, поведение соответствует случаю 1 (по умолчанию к этому случаю относятся версии 16 и 17).
  3. Новая архитектура, одновременные обновления не включены, но включены одновременный режим и некоторые новые функции (например Automatic Batching, в v18 так по умолчанию).
  4. Новая архитектура, включение параллельного режима, включение одновременных обновлений

并发特性Относится к 并发更新функциям, которые можно использовать только после включения, например:

  • useDeferredValue
  • useTransition

диаграмма отношений:

stateDiagram-v2
[*] --> React18
React18 --> ReactDOM.render
React18 --> ReactDOM.createRoot
ReactDOM.render --> 未开启并发模式
ReactDOM.createRoot --> 开启并发模式
未开启并发模式 --> 未开启自动批处理
开启并发模式 --> 开启自动批处理
未开启自动批处理 --> 未开启并发更新
开启自动批处理 --> 未使用并发特性
开启自动批处理 --> 使用并发特性
未使用并发特性 --> 未启并发更新
使用并发特性 --> 开启并发更新 

После четкого понимания их взаимоотношений мы можем продолжить изучение 并发更新:

Возможности параллелизма:

1. начатьпереход

Запустите следующий код в версии 18:

import React, { useState, useEffect, useTransition } from 'react';

const App: React.FC = () => {
  const [list, setList] = useState<any[]>([]);
  const [isPending, startTransition] = useTransition();
  useEffect(() => {
    // 使用了并发特性,开启并发更新
    startTransition(() => {
      setList(new Array(10000).fill(null));
    });
  }, []);
  return (
    <>
      {list.map((_, i) => (
        <div key={i}>{i}</div>
      ))}
    </>
  );
};

export default App; 

Поскольку он выполняется (используется ) в функции обратного вызова, он setListсработает .startTransition并发特性setList并发更新

startTransition, в основном для обеспечения отзывчивости пользовательского интерфейса при выполнении большого количества задач. Этот новый API может “过渡”значительно улучшить взаимодействие с пользователем, помечая определенные обновления startTransitionкак setStateсрочные 紧急渲染.

二、useDeferredValue

Возврат значения отложенного ответа позволяет stateзадержке вступить в силу. Значение станет последним значением только в том случае, если нет срочного обновления. useDeferredValueВроде startTransition, оба помечены как несрочное обновление.

С точки зрения введения useDeferredValue, useTransitionочень ли это похоже на ?

  • То же: useDeferredValueпо сути то же самое, что и внутренняя реализация useTransition, она помечена как 延迟更新задача.
  • Разница useTransitionв том, что задача обновления превращается в задачу отложенного обновления и useDeferredValueгенерируется новое значение, которое используется в качестве статуса отложенного. (Один используется для переноса методов, другой — для переноса значений)

Поэтому startTransitionмы также можем использовать приведенный выше пример useDeferredValueдля реализации:

import React, { useState, useEffect, useDeferredValue } from 'react';

const App: React.FC = () => {
  const [list, setList] = useState<any[]>([]);
  useEffect(() => {
    setList(new Array(10000).fill(null));
  }, []);
  // 使用了并发特性,开启并发更新
  const deferredList = useDeferredValue(list);
  return (
    <>
      {deferredList.map((_, i) => (
        <div key={i}>{i}</div>
      ))}
    </>
  );
};

export default App; 

Затем запустите проект и проверьте распечатанную диаграмму стека выполнения:

Скриншот QQ 20220505072516.jpg

В настоящее время наши задачи разделены на разные кадры для каждого кадра task, а JS脚本время выполнения составляет примерно около 10 % 5ms. Таким образом, у браузера остается время для выполнения макета стиля и рисования стилей , что снижает вероятность пропуска кадров.

3. Обычные ситуации

Мы можем отключить функцию параллелизма и запустить проект в обычной среде:

import React, { useState, useEffect } from 'react';

const App: React.FC = () => {
  const [list, setList] = useState<any[]>([]);
  useEffect(() => {
    setList(new Array(10000).fill(null));
  }, []);
  return (
    <>
      {list.map((_, i) => (
        <div key={i}>{i}</div>
      ))}
    </>
  );
};

export default App; 

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

999.jpg

Из напечатанной диаграммы стека выполнения видно, что из-за большого количества компонентов (10 000) время выполнения JS равно, что 500msозначает, что без функции параллелизма: когда одновременно отрисовываются 10 000 тегов, страница будет заблокирована. некоторое время 0.5秒, вызывая задержку, но если включить одновременные обновления, такой проблемы не будет.

 这种将长任务分拆到每一帧中,像蚂蚁搬家一样一次执行一小段任务的操作,被称为时间切片(time slice) 

в заключение

  • Смысл одновременных обновлений в 交替执行разных задачах.Когда зарезервированного времени недостаточно, Reactуправление потоком возвращается браузеру, ожидая прихода следующего времени кадра, а затем продолжая прерванную работу.
  • 并发模式Это 并发更新основная предпосылка для реализации
  • 时间切片Это 并发更新специфическое средство реализации

Заключение

Выше приведено общее содержание этого Reactобновления. Если есть какие-либо ошибки, исправьте их.

Acho que você gosta

Origin blog.csdn.net/web2022050901/article/details/125215382#comments_26623274
Recomendado
Clasificación