PR TIMES フロントエンドの React 18 バージョンアップの取り組み

はじめまして。2022新卒で PR TIMES に入社し、フロントエンドエンジニアをしている岩元 (@yoiwamoto) です。

先日、PR TIMES の全ての React プロジェクトで React 18 へのバージョンアップを行ったので、この記事ではその経緯や学べたこと等を共有します。

目次

モチベーション

今回のバージョンアップのモチベーションは一言で言うと、「まだコードベースが小さいうちに早めにやっておきたいね」です。

弊社のフロントエンドは、去年までほとんど全てバックエンドリポジトリ内でテンプレートエンジン + jQuery で構成されており、React が導入され始めたこと自体つい最近です。

あわせて読みたい
レガシーなフロントエンドを捨ててReact.jsでリプレイスした話
レガシーなフロントエンドを捨ててReact.jsでリプレイスした話こんにちは。PR TIMES の開発本部でフロントエンドエンジニアをしている鈴木雄大(@szkyudi)です。2021年10月に2020年新卒の僕と2021年新卒の2人の計3人で企業ページのフ...

導入時点で React のバージョンは 17、その他の依存パッケージについても概ね最新のものが採用されていました。また、React も全ての画面で使用されているわけではなく、現状は限られた数ページで利用されている状態なので、アプリケーションもそれほど大きいものではありません。

React 18 へのバージョンアップは、「難しいことはないがそれなりに大変」、「ただしいつかは必ずやらなければいけない」という類のものなので、これから React 実装のページが増えてコードベースが大きくなってしまう前に、サクッと済ませてしまったという感じです。

逆に、React 18 の目玉とされる transition のような機能は直近で実用予定はなく、そういう意味でポジティブなモチベーションはあまりなかったと言えますが、いずれ使いたくなった時にはすぐ手が出せて嬉しいですね。

やったこと

基本的には公式のマイグレーションガイドに従っており、具体的に行った作業は以下です。

  • react、react-dom、および React 周辺のパッケージを React 18 対応バージョンにバージョンアップ
  • ReactDOM の API 変更に対応
  • React.FC の props に暗黙的に children が含まれなくなったので、該当箇所で children を明示
  • @types/react、@types/react-dom のバージョンを固定

How to Upgrade to React 18 – React Blog

react、react-dom、および React 周辺のパッケージを React 18 対応バージョンにバージョンアップ

例として、以下のようなパッケージでメジャーバージョンアップが必要でした。

framer-motion4 → 6
react-dnd15 → 16
@testing-library/react12 → 13

React 18 未対応のパッケージがあると基本的にはバージョンアップが不可能なので、事前に調査します。公式ドキュメントに記載があったり、あるいは Issue が上がっていることが多いので、この辺りを調べるとよさそうです。

また、対応バージョンに上げるために、破壊的変更を含むバージョンアップが必要という場合もあるので、その場合は React とは別で事前に上げておくのがよさそうです。(弊社でも1件そのような対応をしました。)

ReactDOM の API 変更に対応。

ReactDOM.render が deprecated になるので、これを createRoot に置き換えます。

import ReactDOM from 'react-dom';

ReactDOM.render(<App />, document.getElementById('root'));
import { createRoot } from 'react-dom/client';

const root = createRoot(document.getElementById('root') as HTMLElement);
root.render(<App />);

React.FC の props に暗黙的に children が含まれなくなったので、該当箇所で children を明示。

export const Component: FC = ({ children }) => {
  return <p>{children}</p>;
};
type Props = {
  children: ReactNode;
};

export const Component: FC<Props> = ({ children }) => {
  return <p>{children}</p>;
};

ちなみに弊社フロントエンドチームでは、今後は React.FC のアノテーションを使わない方針になっていますが、使用箇所が多いため既存コードは段階的に修正を進めています。

@types/react、@types/react-dom のバージョンを固定する。

これは React に限らずですが、パッケージのバージョンアップ時にはたまに、transitive dependency (依存パッケージの依存パッケージ) に dependency と別のバージョンのものがインストールされてしまうことがあるため、これを固定します。lock ファイルを手で編集する方法でも解決できますが、弊社のフロントエンドではパッケージマネージャーに yarn を使用しているので、selective dependency resolutions を使用するため、package.json に resolutions を追記します。

{
  ...,
  "resolutions": {
    "@types/react": "^18.0.15",
    "@types/react-dom": "^18.0.6"
  }
}

注意点として、yarn workspace を使用している場合、各 workspace の package.json ではなく、root の package.json に記載しないと resolutions が機能しないようです (ちょっとハマりました)。

普通は各依存パッケージの package.json を見て上手にバージョン解決してくれるわけですが、今回 @types/react-dom の package.json > dependencies に記載の @types/react のバージョンが 18 系ではなく "*" になっており、誤って 17 系が参照されてしまい、ReactNode などの型が衝突してエラーが起きてしまいました。

バージョン解決の仕組みをよく知らないので何とも言えませんが、他に React 17 依存のパッケージがない場合は “*” と言えど 17 をインストールする必要がないので、おそらく yarn.lock に既に React 17 の記載がある + バージョン指定が "*" である、などの条件で起こりそうです。

とはいえ、yarn の機能で一旦解決できるので、使いましょう。

まとめ

初めに書いたように、今回はフロントエンドのコードベースが比較的小さいうちにバージョンアップを行ったため、変更量はある程度多いもののレビューできないほどではなく、事前に行った依存パッケージのバージョンアップ等を除き、PR は1件で済みました。

ただし、今後弊社のフロントエンドも規模が大きくなっていくと、サクッと済ませるというわけにもいかなくなります。定期的にあるこうした作業のために、Renovate などで依存関係のアップデートを自動化したりして、なるべく依存を最新に保つための仕組みが作れるといいなと思いました。

この記事を書いた人

2022新卒で PR TIMES に入社し、開発チームでフロントエンドエンジニアをしています。

目次
閉じる