知らせ
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 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
、プロジェクトの開始後にコンソールに警告が表示されます。
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 つの状態を更新しても、コンポーネントは更新ごとに 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 を再印刷します。
ご覧のとおり、クリックして 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 を再印刷します。
ネイティブ JS イベントでは、結果が 2 番目の状況と同じであることがわかります。クリックして 2 つの状態を更新するたびに、コンポーネントは 2 回レンダリングされ、バッチ更新は実行されません。
2. React 18 の場合:
上記の 3 つの例では、すべての更新が自動的にバッチ処理されるため、React 18
これは 1 回だけ発生します。render
これにより、アプリケーションの全体的なパフォーマンスが大幅に向上することは間違いありません。
ただし、次の例では、 でReact 18
render が 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. コンポーネントのアンインストール時の更新ステータスの警告
開発中に、次のようなエラーが発生することがあります。
このエラーは次のことを意味します:アンマウントされた (アンマウントされた) コンポーネントのステータス更新を実行できません。これは無効な操作であり、コード内のメモリ リークを示しています。
実際、このエラーはまれであり、以前のバージョンでは、この警告は広く誤解され、やや誤解を招くものでした。
このエラーの本来の目的は、次のようないくつかの特殊なシナリオをターゲットとすることでした你在useEffect里面设置了定时器,或者订阅了某个事件,从而在组件内部产生了副作用,而且忘记return一个函数清除副作用,则会发生内存泄漏……
。
しかし、実際の開発では、さらに多くのシナリオが存在します我们在 useEffect 里面发送了一个异步请求,在异步函数还没有被 resolve 或者被 reject 的时候,我们就卸载了组件
。このシナリオでは、警告もトリガーされます。ただし、この場合、非同期関数がガベージ コレクションされているため、コンポーネント内でメモリ リークは発生せず、この時点での警告は誤解を招くものです。
この点については、React にも公式の説明があります。
上記の理由をまとめると、React 18
このエラーは で正式に削除されました。
このエラーの詳細については、React の公式手順を参照してください。ここをクリックして参照してください。
5. Reactコンポーネントの戻り値について
- Reactでは、
React 17
必要な場合に空组件
のみ返すことができますnull
。明示的に返すとundefined
、コンソールは実行時にエラーをスローします。 - では
React 18
、undefined
戻ることによるクラッシュはチェックされなくなりました。returnnull
と return の両方が可能ですundefined
(ただし、のファイルReact 18
はdts
引き続きチェックされ、 return のみが許可されますnull
。このタイプのエラーは無視できます)。
コンポーネントの戻り値に関する公式説明: github.com/reactwg/rea…
6.ストリクトモード
コンソール ログは抑制されなくなりました。
React は、使用するたびに严格模式
各コンポーネントと連携して動作し两次渲染
、予期しない結果を観察できるようになります。ではReact 17
、其中一次渲染
ログを読みやすくするためにコンソール ロギングが抑制されています。
この問題に関するコミュニティの混乱に対処するために、 ではReact 18
、この制限は正式に解除されました。インストールされている場合React DevTools
、2 回目のレンダリングのログ メッセージはグレー表示され、コンソールに穏やかに表示されます。
Strict Modeに関する公式説明: github.com/reactwg/rea…
7. サスペンスをキャプチャするためにフォールバックが必要なくなりました
React 18
のコンポーネントではSuspense
、担当者が空的fallback
属性の処理方法を変更しました。缺失值
または值为null
のfallback
境界をスキップしなくなりました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
場合でも、現在のコンポーネントを境界として使用します。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.使用ID
const id = useId();
非互換性を回避するために、クライアントとサーバー上で同じ一意の ID を生成する同じコンポーネントをサポートしますhydration
。これにより、以前のバージョンでの既存の問題が解決されReact 17
ます17
。サーバーはレンダリング時にHTML
Yesを返すため无序的
、useId
原則として、それぞれがid
コンポーネント ツリー内のコンポーネントの階層構造を表します。
useId の詳細については、ワーキング グループの useId 投稿を参照してください。
二、Sync外部ストアを使用する
useSyncExternalStore
から変更された新しい API でuseMutableSource
、主に外部データのティアリングの問題を解決するために使用されます。
useSyncExternalStore を使用すると、データの同期更新を強制することで、React コンポーネントが CM の下で外部データ ソースを安全かつ効果的に読み取ることができます。同時モードでは、React レンダリングがスライス (ファイバー単位) で実行され、優先度の高い更新が途中で散在する可能性があります。パブリック データ (redux 内のデータなど) が優先度の高い更新で変更された場合、以前の優先度の低いレンダリングを再開する必要があります。そうしないと、状態が不整合になります。
useSyncExternalStore
これは通常、サードパーティの状態管理ライブラリによって使用されるため、日常業務では注意を払う必要はありません。React
それは、同時実行機能の下での問題をuseState
ネイティブに解決しているためです。主にフレームワーク開発者向けです。たとえば、状態を制御するときに直接使用することはできません。代わりに、データ更新を実装するために外部オブジェクトを維持します。を管理しないとティアリング問題を自動的に解決できません。そこで、このようなAPIを外部に提供する。tear(撕裂)
useSyncExternalStore
redux
React
state
store
发布订阅模式
React
React
React
現在はをベースに実装されて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 本身并不是一个功能,而是一个底层设计
同時モードは、アプリの応答性を維持し、ユーザーのデバイス機能とネットワーク速度に基づいて適切に調整するのに役立ち、レンダリングを中断可能にすることで制限を修正します阻塞渲染
。モードではConcurrent
、React
複数の状態を同時に更新できます。
複雑すぎて言うことができないかもしれませんが、一言でまとめると次のようになります。
React 17
との違いは次のReact 18
とおりです。 から同步不可中断更新
に変更されました异步可中断更新
。
ここからが重要な点です。次の部分を飛ばさないでください。
記事の冒頭で述べたように、「 ではReact 18
、新しいものが提供されています。アップグレードするroot api
必要があるのは、同時モードを有効にするためだけです。」render
createRoot(root).render(<App />)
したがって、この時点で、学生の中には「オンにするということは、電源を入れる并发模式
という意味ですか并发更新
?」と尋ねる人もいるかもしれません。
いいえ!の一部の実験的機能でReact 17
は、オンにすることは を并发模式
オンにすることを意味し并发更新
ますが、React 18
正式バージョンのリリース後は、公式ポリシーの調整により、React はオンにすることに依存しなくなりまし并发模式
た并发更新
。
言い換えれば、オンになっているからといって、并发模式
必ずしもオンになっているわけではありません并发更新
。
一文の要約:では18
、複数のモデルはなくなり、アクション是否使用并发特性
に是否开启并发更新
基づいています。
最も古いバージョンから現在のバージョンまでv18
、いくつのバージョンが市場に出回っていますReact
か?
アーキテクチャの観点から要約すると、現在 2 つのアーキテクチャがあります。
- 中断のない方法で
递归
更新Stack Reconciler
(古いアーキテクチャ) - 中断可能な方法で
遍历
更新Fiber Reconciler
(新しいアーキテクチャ)
新しいアーキテクチャを有効にするかどうかを選択できるため、并发更新
現在市場に出ているすべてのバージョンにはReact
次の 4 つの状況があります。
- 古いアーキテクチャ (v15 以前のバージョン)
- 新しいアーキテクチャでは、同時更新は有効になっておらず、動作はケース 1 と一致しています (v16 および v17 はデフォルトでこのケースに該当します)。
- 新しいアーキテクチャでは、同時更新は有効になっていませんが、同時モードといくつかの新機能が有効になっています (たとえば
Automatic Batching
、v18 ではデフォルトでこれが当てはまります)。 - 新しいアーキテクチャ、同時モードの有効化、同時更新の有効化
并发特性
并发更新
オンにしないと使用できない機能を指します。たとえば、次のとおりです。
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
非緊急レンダリングとしてマークされ、これらのレンダリングは他のレンダリングによってプリエンプトされる可能性があります紧急渲染
。
- startTransition の詳細については、「 startTransition のパターン」を参照してください。
二、useDeferredValue
遅延応答値を返すとstate
遅延が有効となり、緊急の更新がない場合のみ最新値となります。useDeferredValue
と同様にstartTransition
、両方とも非緊急アップデートとしてマークされています。
導入の観点から見るとuseDeferredValue
、useTransition
と非常によく似ていますか?
- 同じ:
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;
次に、プロジェクトを開始し、印刷された実行スタック図を確認します。
現時点では、タスクはフレームごとに異なるフレームに分割されておりtask
、JS脚本
実行時間はおよそ 10% です5ms
。これにより、ブラウザはスタイル レイアウトとスタイル描画を実行する時間が残り、フレーム落ちの可能性が減ります。
- useDeferredValue の詳細については、「18 の新機能: useDeferredValue」を参照してください。
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;
プロジェクトを開始し、印刷された実行スタック図を確認します。
印刷された実行スタック図から、コンポーネントの数 (10,000) が多いため、JS の実行時間がかかることがわかります。これは、同時実行機能がないと、10,000 個のタグが一度にレンダリングされると、500ms
ページがブロックされてしまうことを意味します。しばらくの間0.5秒
、遅延が発生しますが、同時更新をオンにするとそのような問題は発生しません。
这种将长任务分拆到每一帧中,像蚂蚁搬家一样一次执行一小段任务的操作,被称为时间切片(time slice)
結論は
- 同時更新の意味は
交替执行
別のタスクであり、予約時間が十分でない場合は、React
スレッド制御がブラウザに返され、次のフレーム時間が到着するのを待って、中断された作業が続行されます。 并发模式
并发更新
それを実現するための大前提となるのが、时间切片
并发更新
実現するための具体的な手段です
結論
以上が今回のReact
バージョンアップの大まかな内容ですが、誤りがあれば修正してください。