React 18 にアップグレードしてみた

こんにちは、広野です。

2022年3月29日に、React のバージョン 18 がリリースされました。

react/CHANGELOG.md at main · facebook/react
The library for web and native user interfaces. Contribute to facebook/react development by creating an account on GitHu...

本記事を書いている時点では 18.1.0 がリリースされています。本記事では既存アプリ (v17) からアップグレードしたときに対応したことを紹介します。アップグレードにより潜在的なバグが見つかったため、その修正経験も含めて紹介します。

アップグレード時の修正箇所

公式ドキュメントによる修正箇所

公式には、以下のアップグレードガイドに記載されていることを対処することになります。

React 18 アップグレードガイド – React Blog
リリース告知の記事でお伝えしたとおり、React 18 には新たな並行レンダラを用いた機能が加わっており、既存のアプリケーションが段階的に採用できる方法も提供しています。この投稿では、React 18 にアップグレードするためのステップにつ...

実際に私が修正した箇所

実際のところ、一般 React 開発者の場合、公式ドキュメントや公式ブログに書かれていること全てを対応することはないと考えます。React 上級者ですと、当然修正該当箇所は多くなると思いますが。

私の場合、大きく以下 3 点がありました。

  1. モジュールのアップグレード(公式ドキュメント通り、ただし依存性チェックの対応は必要)
  2. index.js の修正(公式ドキュメント通り)
  3. コードの潜在的なバグ修正(想定外、当然公式ドキュメントは助けてくれない・・・)

1.モジュールのアップグレード

とりあえず公式ドキュメント通り。@latest をおまじない的に付けるぐらい。※私は npm を使用してます。

npm install --save react@latest react-dom@latest

ですが、ここで他のモジュールとの依存性チェックが走り、私の場合は以下のようなエラーが出ました。ここは各自の環境によって違いがあります。

npm ERR! code ERESOLVE
npm ERR! ERESOLVE unable to resolve dependency tree
npm ERR! 
npm ERR! While resolving: hirodemy-test6@0.1.0
npm ERR! Found: react@18.1.0
npm ERR! node_modules/react
npm ERR!   react@"^18.1.0" from the root project
npm ERR! 
npm ERR! Could not resolve dependency:
npm ERR! peer react@"^17.0.0" from @mui/styles@5.8.0
npm ERR! node_modules/@mui/styles
npm ERR!   @mui/styles@"*" from the root project
npm ERR! 
npm ERR! Fix the upstream dependency conflict, or retry
npm ERR! this command with --force, or --legacy-peer-deps
npm ERR! to accept an incorrect (and potentially broken) dependency resolution.
npm ERR! 
npm ERR! See /home/ec2-user/.npm/eresolve-report.txt for a full report.

npm ERR! A complete log of this run can be found in:
npm ERR!     /home/ec2-user/.npm/_logs/2022-05-28T05_00_15_329Z-debug-0.log

こうなるとアップグレードしない選択肢が出てくるわけですが(v17に戻す)、強行したかったので上記エラーメッセージ内にある npm のオプション、–legacy-peer-deps というのを付けて依存性問題のあるモジュールのインストールを実行します。

私の場合は、試行錯誤の末、以下のモジュールに依存性問題があることがわかり、依存性問題を回避させて再インストールしました。

npm install --save @mui/styles material-table@1.69.3 @material-ui/core --legacy-peer-deps

こうするとエラー無しに(回避しただけ)インストールが進みます。

ちなみに MUI 関連のモジュールです。まだ MUI は React 18 対応版がリリースされていないようで、ネット上の他の技術情報フォーラムサイトでも同様に回避している方がいらっしゃいました。正式対応され次第、モジュールアップデートをしたいところです。

2.index.js の修正

こちらは基本公式ドキュメント通りです。開発物により、細かいコードの差異はあります。render の文法が変わっています。

  • React 17 仕様の index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(
  <App />,
  document.getElementById('root')
);
  • React 18 仕様の index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode> 
);

ちなみに本記事執筆時点では create-react-app が自動生成してくれる index.js は React 18 仕様になっていました。

React 18 環境で React 17 仕様の index.js は動作しましたので、不安な方はまずはコードをそのまま置き換えることから始めるのも有りだと思います。

3.コードの潜在的なバグ修正

上記 1. 2. を実施してビルド、デプロイして実際にアプリの動作を試したところ、以下のようなエラーが発生する箇所がいくつかありました。

TypeError: Cannot read property 'XXXXX' of undefined
TypeError: Cannot read property 'XXXXX' of null

いずれも、あるコードが参照する変数等の中に値が入っていない、という状態を示しています。

これまで動作していたのに何故???

おそらく、React 18 になって render 処理が変わり、React 17 ではたまたま発動しなかったバグが表面化しただけだと後から気付きました。

私の場合、以下の 2 パターンの修正をしました。

パターン 1 :画面に表示する state に値が格納されているかチェックを入れる
  • 修正前
{(quizOptions.A) && (
  <ThemeProvider theme={optionStatus.A.theme}>
    <Button variant={optionStatus.A.variant} onClick={() => checkAnswer("A")}>
      {quizOptions.A}
    </Button>
  </ThemeProvider>
)}
  • 修正後
{(quizOptions.A && optionStatus.A) && (
  <ThemeProvider theme={optionStatus.A.theme}>
    <Button variant={optionStatus.A.variant} onClick={() => checkAnswer("A")}>
      {quizOptions.A}
    </Button>
  </ThemeProvider>
)}

修正箇所は先頭行だけです。

これまで、quizOptions という state に値が入っているかだけをチェックして表示有無を判断させていたのですが、実際には表示内容の処理には optionStatus という state も含まれます。

React 18 では、optionStatus に値が格納されていない旨のエラーが発生してしまいました。見た目上は state に値が格納され次第、再レンダーされるので実害はないのですが、初回レンダー時にはエラーが発生してしまいます。React 17 では、レンダータイミングの違いなのか、エラーは発生していなかったです。

quizOptions.A と optionStatus.A の両方に値が入っているときだけボタンを表示させる、というコードに修正しました。

パターン 2 :明示的に前処理完了後に後処理を実行させる

言わんとしていることは以下の図の通りです。

元々、私が書いたコードに問題があったのですが、React 17 ではうまく動いているように見えていました。(実際、期待していた通りの動きをしていました)

React 18 になって、誤ったコードの通り忠実に処理され、問題が顕在化しました。

処理順序を守らせるために、useEffect を使用して state 更新後に後続処理を発動させるように修正しました。

ちなみに React 18 へのアップグレード後環境で React 17 仕様の index.js を使用したところ、問題のあるコードでエラー無く動きました。やはり render の内部処理が変わったために動きに違いが出ているものと思います。

まとめ

いかがでしたでしょうか?

このような対応を経て、無事? React アプリが正常に動きました。

まだ React 18 の情報は少なく、本記事は一例ではありますが、似たようなことでお困りの方にとってお役に立てましたら幸いです。

コーディングミスがなければ、React 18 へのアップグレードは問題なく進むと思います。

著者について
広野 祐司

AWS サーバーレスアーキテクチャを駆使して社内クラウド人材育成アプリとコンテンツづくりに勤しんでいます。React で SPA を書き始めたら快適すぎて、他の言語には戻れなくなりました。サーバーレス & React 仲間を増やしたいです。AWSは好きですが、それよりもAWSすげー!って気持ちの方が強いです。
取得資格:AWS 認定は12資格、ITサービスマネージャ、ITIL v3 Expert 等
2020 - 2023 Japan AWS Top Engineer 受賞
2022 - 2023 Japan AWS Ambassador 受賞
2023 当社初代フルスタックエンジニア認定
好きなAWSサービス:AWS Amplify / AWS AppSync / Amazon Cognito / AWS Step Functions / AWS CloudFormation

広野 祐司をフォローする
クラウドに強いによるエンジニアブログです。
SCSKは専門性と豊富な実績を活かしたクラウドサービス USiZE(ユーサイズ)を提供しています。
USiZEサービスサイトでは、お客様のDX推進をワンストップで支援するサービスの詳細や導入事例を紹介しています。
アプリケーション開発ソリューション
シェアする
タイトルとURLをコピーしました