React18の新機能解説&完全バージョンアップガイド

知らせ

React 18ie11のサポートを放棄し、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 18新しいものが導入されましたroot API。新しいものでは、(同時モード レンダリング)root APIもサポートされており、 (同時モード)new concurrent rendererに入ることができます。concurrent 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.jpgReact 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 事件处理函数バッチ更新のみを行っています。デフォルトではpromisesetTimeout、 、原生事件处理函数、 の更新は任何其它事件内バッチ処理されません。

シナリオ 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

ご覧のとおり、レンダリング数と更新数は同じであり、2 つの状態を更新しても、コンポーネントは更新ごとに 1 回しかレンダリングされません。

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

ご覧のとおり、クリックして 2 つの状態を更新するたびに、コンポーネントは 2 回レンダリングされ、バッチ更新は実行されません。

シナリオ 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 番目の状況と同じであることがわかります。クリックして 2 つの状態を更新するたびに、コンポーネントは 2 回レンダリングされ、バッチ更新は実行されません。

2. React 18 の場合:

上記の 3 つの例では、すべての更新が自動的にバッチ処理されるため、React 18これは 1 回だけ発生します。renderこれにより、アプリケーションの全体的なパフォーマンスが大幅に向上することは間違いありません。

ただし、次の例では、 でReact 18render が 2 回実行されます

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以前ではreactイベント処理機能のみバッチ処理が自動で実行され、それ以外の場合は複数回更新されていました。
  • 18 以降は、いかなる場合でもバッチ処理が自動的に実行され、複数の更新は常に 1 つにマージされます。

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更新は引き続きバッチで更新されるため、不要なバッチ更新を正確に制御できます。

批处理と の詳細については、React の公式の自動バッチ処理のflushSync詳細を参照してください

4. コンポーネントのアンインストール時の更新ステータスの警告

開発中に、次のようなエラーが発生することがあります。

QQ のスクリーンショット 20220503200708.jpg

このエラーは次のことを意味します:アンマウントされた (アンマウントされた) コンポーネントのステータス更新を実行できません。これは無効な操作であり、コード内のメモリ リークを示しています。

実際、このエラーはまれであり、以前のバージョンでは、この警告は広く誤解され、やや誤解を招くものでした。

このエラーの本来の目的は、次のようないくつかの特殊なシナリオをターゲットとすることでした你在useEffect里面设置了定时器,或者订阅了某个事件,从而在组件内部产生了副作用,而且忘记return一个函数清除副作用,则会发生内存泄漏……

しかし、実際の開発では、さらに多くのシナリオが存在します我们在 useEffect 里面发送了一个异步请求,在异步函数还没有被 resolve 或者被 reject 的时候,我们就卸载了组件このシナリオでは、警告もトリガーされます。ただし、この場合、非同期関数がガベージ コレクションされているため、コンポーネント内でメモリ リークは発生せず、この時点での警告は誤解を招くものです

この点については、React にも公式の説明があります。

222.jpg

上記の理由をまとめると、React 18このエラーは で正式に削除されました。

このエラーの詳細については、React の公式手順を参照してください。ここをクリックして参照してください。

5. Reactコンポーネントの戻り値について

  • Reactでは、React 17必要な場合に空组件のみ返すことができますnull明示的に返すとundefined、コンソールは実行時にエラーをスローします。
  • ではReact 18undefined戻ることによるクラッシュはチェックされなくなりました。returnnullと return の両方が可能ですundefined(ただし、のファイルReact 18dts引き続きチェックされ、 return のみが許可されますnull。このタイプのエラーは無視できます)。

コンポーネントの戻り値に関する公式説明: github.com/reactwg/rea…

6.ストリクトモード

コンソール ログは抑制されなくなりました。

React は、使用するたびに严格模式各コンポーネントと連携して動作し两次渲染、予期しない結果を観察できるようになります。ではReact 17其中一次渲染ログを読みやすくするためにコンソール ロギングが抑制されています。

この問題に関するコミュニティの混乱に対処するために、 ではReact 18、この制限は正式に解除されました。インストールされている場合React DevTools、2 回目のレンダリングのログ メッセージはグレー表示され、コンソールに穏やかに表示されます。

QQ のスクリーンショット 20220503205553.jpg

Strict Modeに関する公式説明: github.com/reactwg/rea…

7. サスペンスをキャプチャするためにフォールバックが必要なくなりました

React 18のコンポーネントではSuspense、担当者が空的fallback属性の処理方法を変更しました。缺失值または值为nullfallback境界をスキップしなくなりましたSuspense代わりに、境界がキャプチャされて外側が表示され、見つからない場合はfallbackとしてレンダリングされますnull

更新前:

以前は、コンポーネントがプロパティをSuspense提供しない場合、React はそれを黙ってスキップし、次の境界まで検索を続けていました。fallback

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

export default App; 

React チームは、これが混乱を招き、デバッグが困難な状況を引き起こす可能性があることを発見しました。fallbackたとえば、問題をデバッグしているときに、 propertyのないコンポーネントに境界をスローして問題をテストするとSuspense、予期しない結果が生じ、プロパティがある不会警告と表示される可能性があります。没有fallback

更新しました:

現在、React は、現在のコンポーネントの値がまたは であるSuspense場合でも、現在のコンポーネントを境界として使用しますSuspensenullundefined

// 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.使用ID

const id = useId(); 

非互換性を回避するために、クライアントとサーバー上で同じ一意の ID を生成する同じコンポーネントをサポートしますhydration。これにより、以前のバージョンでの既存の問題が解決されReact 17ます17サーバーはレンダリング時にHTMLYesを返すため无序的useId原則として、それぞれがidコンポーネント ツリー内のコンポーネントの階層構造を表します。

useId の詳細については、ワーキング グループの useId 投稿を参照してください。

二、Sync外部ストアを使用する

useSyncExternalStoreから変更された新しい API でuseMutableSource、主に外部データのティアリングの問題を解決するために使用されます。

useSyncExternalStore を使用すると、データの同期更新を強制することで、React コンポーネントが CM の下で外部データ ソースを安全かつ効果的に読み取ることができます。同時モードでは、React レンダリングがスライス (ファイバー単位) で実行され、優先度の高い更新が途中で散在する可能性があります。パブリック データ (redux 内のデータなど) が優先度の高い更新で変更された場合、以前の優先度の低いレンダリングを再開する必要があります。そうしないと、状態が不整合になります。

useSyncExternalStoreこれは通常、サードパーティの状態管理ライブラリによって使用されるため、日常業務では注意を払う必要はありません。Reactそれは、同時実行機能の下での問題をuseStateネイティブに解決しているためです。主にフレームワーク開発者向けです。たとえば、状態を制御するときに直接使用することはできません。代わりに、データ更新を実装するために外部オブジェクトを維持します。を管理しないとティアリング問題を自動的に解決できません。そこで、このようなAPIを外部に提供する。tear(撕裂)useSyncExternalStorereduxReactstatestore发布订阅模式ReactReactReact

現在はをベースに実装されてReact-Redux 8.0います。useSyncExternalStore

useSyncExternalStore の詳細については、useSyncExternalStore の概要の投稿およびuseSyncExternalStore API の詳細 を参照してください。

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>参照してください。

同時モード

Concurrent Mode (以下、CM) は並行モードと訳されます。私たちはこの概念を何度も聞いたことがあるかもしれません。実際、この概念は昨年非常に成熟し、 のいくつかの API を通じて有効にReact 17することができます试验性CM

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

同時モードは、アプリの応答性を維持し、ユーザーのデバイス機能とネットワーク速度に基づいて適切に調整するのに役立ち、レンダリングを中断可能にすることで制限を修正します阻塞渲染モードではConcurrentReact複数の状態を同時に更新できます。

複雑すぎて言うことができないかもしれませんが、一言でまとめると次のようになります。

React 17との違いは次のReact 18とおりです。 から同步不可中断更新に変更されました异步可中断更新

ここからが重要な点です。次の部分を飛ばさないでください。

記事の冒頭で述べたように、「 ではReact 18、新しいものが提供されています。アップグレードするroot api必要があるのは、同時モードを有効にするためだけです。」rendercreateRoot(root).render(<App />)

したがって、この時点で、学生の中には「オンにするということは、電源を入れる并发模式という意味ですか并发更新?」と尋ねる人もいるかもしれません。

いいえ!の一部の実験的機能でReact 17は、オンにすることは を并发模式オンにすることを意味し并发更新ますが、React 18正式バージョンのリリース後は、公式ポリシーの調整により、React はオンにすることに依存しなくなりまし并发模式并发更新

言い換えれば、オンになっているからといって、并发模式必ずしもオンになっているわけではありません并发更新

一文の要約:では18、複数のモデルはなくなり、アクション是否使用并发特性是否开启并发更新基づいています。

最も古いバージョンから現在のバージョンまでv18、いくつのバージョンが市場に出回っていますReactか?

アーキテクチャの観点から要約すると、現在 2 つのアーキテクチャがあります。

  • 中断のない方法で递归更新Stack Reconciler(古いアーキテクチャ)
  • 中断可能な方法で遍历更新Fiber Reconciler(新しいアーキテクチャ)

新しいアーキテクチャを有効にするかどうかを選択できるため、并发更新現在市場に出ているすべてのバージョンにはReact次の 4 つの状況があります。

  1. 古いアーキテクチャ (v15 以前のバージョン)
  2. 新しいアーキテクチャでは、同時更新は有効になっておらず、動作はケース 1 と一致しています (v16 および v17 はデフォルトでこのケースに該当します)。
  3. 新しいアーキテクチャでは、同時更新は有効になっていませんが、同時モードといくつかの新機能が有効になっています (たとえばAutomatic Batching、v18 ではデフォルトでこれが当てはまります)。
  4. 新しいアーキテクチャ、同時モードの有効化、同時更新の有効化

并发特性并发更新オンにしないと使用できない機能を指します。たとえば、次のとおりです。

  • useDeferredValue
  • useTransition

関係図:

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

それらの関係を明確に理解した後、次のことを調査し続けることができます并发更新

同時実行機能:

1.startTransition

v18 で次のコードを実行します。

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、主に、多数のタスクの下で UI の応答性を維持するためです。この新しい API では、“过渡”特定の更新を緊急としてマークすることで、ユーザー インタラクションを大幅に改善できます。簡単に言うと、startTransitionコールバックによってトリガーされたレンダリングはsetState非緊急レンダリングとしてマークされ、これらのレンダリングは他のレンダリングによってプリエンプトされる可能性があります紧急渲染

二、useDeferredValue

遅延応答値を返すとstate遅延が有効となり、緊急の更新がない場合のみ最新値となります。useDeferredValueと同様にstartTransition、両方とも非緊急アップデートとしてマークされています。

導入の観点から見るとuseDeferredValueuseTransitionと非常によく似ていますか?

  • 同じ:useDeferredValue内部実装と本質的に同じであり、タスクuseTransitionとしてマークされます。延迟更新
  • 違いはuseTransition、更新タスクが遅延更新タスクに変わり、useDeferredValue新しい値が生成され、それが遅延ステータスとして使用されることです。(1 つはメソッドのラップに使用され、もう 1 つは値のラップに使用されます)

したがって、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

現時点では、タスクはフレームごとに異なるフレームに分割されておりtaskJS脚本実行時間はおよそ 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 の実行時間がかかることがわかります。これは、同時実行機能がないと、10,000 個のタグが一度にレンダリングされると、500msページがブロックされてしまうことを意味します。しばらくの間0.5秒、遅延が発生しますが、同時更新をオンにするとそのような問題は発生しません。

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

結論は

  • 同時更新の意味は交替执行別のタスクであり、予約時間が十分でない場合は、Reactスレッド制御がブラウザに返され、次のフレーム時間が到着するのを待って、中断された作業が続行されます。
  • 并发模式并发更新それを実現するための大前提となるのが、
  • 时间切片并发更新実現するための具体的な手段です

結論

以上が今回のReactバージョンアップの大まかな内容ですが、誤りがあれば修正してください。

おすすめ

転載: blog.csdn.net/web2022050901/article/details/125215382#comments_26623274