【React v18】リリース情報で気になった機能とcreate-react-app使用時の注意点

こんにちは、次世代デジタル基盤開発事業部の渡邊です。
今回は今年の3月末にリリースされたReact v18(※1)の中身が気になったので、それに関する備忘録と create-react-appでプロジェクト作成する際の諸注意(?)についてご紹介していきます。

※1:この記事はv18.0.0のリリース情報を参考に書いています。

[目次]

環境

今回の取り組みで使っている環境は以下の通りです。

  • WSL2
  • Ubuntu 20.04.1 LTS
  • Node.js 16.13.0
  • npm 8.1.0
  • React 18.0.0

新機能

今回のv18リリースによって新しい機能が複数登場しています。その中で、今回は新機能として挙げられている[useTransition]というフックが気になったので実際にcreate-react-appを使って実装しながら動きを確認してみました。

《useTransition》

このフックの役割としては、ざっくり申し上げると「特定の処理を保留状態にして、ユーザー体験(UX)を向上させる」ことだと言えます。

おそらく文章だとイメージが伝わりにくいと思うので、useTransitionを取り入れたコードと動作例を画像でご紹介します。 今回の例では大きく分けて4つのソースを用意しました。

[App.js(※2)]

import './List.css';
import React, { useState, useTransition } from 'react'
import { List } from './components'
function App() {
  const [isPending, startTransition] = useTransition();
  const [keyword, setKeyword] = useState("");
  const searchKey = (event) => {
    startTransition(() => {
      setKeyword(event.target.value)
    })
  }
  return (
    <>
      <div>
        <div className="Keyword">
          <input
            type='text'
            placeholder='検索キーワード入力欄'
            onChange={searchKey}
          />
          {isPending && (<p>検索中</p>)}
          <List inputData={keyword} />
        </div>
      </div>
    </>
  );
}
export default App; 

※2:こちらのソースはcreate-react-appでプロジェクトを作成した際に自動生成されるものです。中身は書き換えています。

[List.jsx]

import React, {useId} from 'react';
import { getData } from '../data/createData';
import '../List.css';

const data = getData()

const List = (props) => {
    const id = useId()
    return (
        <>
            <table border='1' className="Result">
                <tbody>
                    <tr>
                        <th>タイトル</th>
                        <th>カテゴリ</th>
                    </tr>
                    {data.map((obj, index) => {
                        const listKey = []
                        for (const [key] of Object.entries(obj)) {
                            listKey.push(key)
                        }
                        return (
                            listKey[index - index].includes(props.inputData) &&
                            <tr key={id + index}>
                                <td>{listKey[index - index]}</td>
                                <td>{obj[`タイトル${index + 1}`]}</td>
                            </tr>
                        )
                    })}
                </tbody>
            </table>
        </>
    )
}
export default List;

[List.css]

.Keyword {
    text-align: center;
}
.Result {
    margin: 0px auto;
}

[createData.js]

export const getCategory = () => {
    const category = [
        'アクション',
        'スポーツ',
        'ファンタジー'
    ]
    return category;
}
export const getTitle = () => {
    const title = []
    for (var i = 0; i < 3000; i++) {
        title.push(`タイトル${i + 1}`)
    }
    return title;
}

export const getData = () => {
    const titles = getTitle();
    const categories = getCategory();
    const data = []
    titles.forEach((test, index) => (
        data.push(
            { [titles[index]]: categories[Math.floor(Math.random() * categories.length)] }
        )
    ))
    return data
}

そして、これらを動かした際にブラウザに表示されるものが以下になります。

実行結果(初期表示時)

これは、createData.jsで作成したデータを検索できる機能となります。検索欄にタイトルを入力すると、それに一致するデータを表示するという動きになります。

では、この中でuseTransitionがどのような効果を発揮するかですが・・・。
以下の画像に表示されている[検索中]という文言が、結果として先ほど申し上げた「ユーザー体験(UX)の向上」に関係してきます。

実行結果(検索時)

コードで見た場合、App.jsx内の下記コードが関係してきます。

const [isPending, startTransition] = useTransition();
startTransition(() => {
  setKeyword(event.target.value)
})
{isPending && (<p>検索中</p>)}

ReactのuseStateで更新処理を行うと、その度に再レンダリングが行われてリアルタイムで更新されることになります。

このときに検索時の入力操作と再レンダリングによる更新処理が同期されるような動きを見せるので、処理によってはブラウザ表示が一瞬固まって見えてしまいます。

これは、ユーザー体験(UX)という観点から言えばあまり良いとは呼べませんよね・・・。 そこで、useTransitionが効果を発揮します。処理としては、

  • startTransitionという関数でラッピングされた関数の処理を、レンダリング時に保留させる対象としてReactに認識させる。これによって、今回だとuseStateによる更新処理が保留状態になる。
  • isPendingという変数がstartTransitionでラッピングされた処理の状態を示す役割となっており、保留となっている処理が存在するときにtrueとなる。そして、trueとなることで[検索中]というメッセージが表示される。

というようになります。

実際に実装してみて、今回のような「ブラウザ上に表示されている大量データを使って処理されていることをユーザーに知らせたい」ときなどに有効活用できそうだと感じました。

補足事項

こちらは今回の調査でcreate-react-appを使っていて気づいたことなので、補足事項としてご紹介します。

《create-react-appでプロジェクト作成する際の注意点》

Reactの開発を行う際にcreate-react-appを使ってプロジェクトを作成すると、React関連のパッケージがv18系にバージョンアップされています。

[package.json(抜粋)]

  "dependencies": {
    "@testing-library/jest-dom": "^5.16.4",
    "@testing-library/react": "^13.1.1",
    "@testing-library/user-event": "^13.5.0",
    "react": "^18.0.0",
    "react-dom": "^18.0.0",
    "react-scripts": "5.0.1",
    "web-vitals": "^2.1.4"
  }

まぁこれは想定通りという方も多いと思います。ただ、ここで注意したい点としてはReact開発時に使うパッケージによってv18系に対応されていない可能性があります。

今回の作業の中でReact開発時によく使うMaterial-UI関連のパッケージをnpmでインストールしようとしたのですが、まだv18系に対応されていないのか以下のようなメッセージが表示されました。

[Material-UIインストール時(抜粋)]

npm ERR!
npm ERR! Could not resolve dependency:
npm ERR! peer react@"^16.8.0 || ^17.0.0" from @material-ui/core@4.12.4
npm ERR! node_modules/@material-ui/core
npm ERR!   @material-ui/core@"*" from the root project

ただこちらについてはMaterial-UIのgithubを確認したところ、Reactのv18に対応しているのは[@material-ui/core]ではなく[@mui/material-ui]のようです。
リリース情報によると[@mui/material-ui]パッケージのv5.6.0からReactのv18に対応しているみたいです。

そのため、上記のようなケースも考慮してcreate-react-appを使ってプロジェクトを作成した際は追加でインストールするパッケージがv18に対応しているか確認しましょう。

まとめ

いかがだったでしょうか。
Reactのv18では新しいフックが追加されたりこれまで使われていた機能が廃止されたりと多くのアップデートが実施されました。

現時点だと解説記事はそこまで多くないと思いますが、動きを理解したい場合は他のところでも解説されている記事も参考にしながら実際に動かしてみてください。

今回の記事が少しでも参考になれば幸いです。最後まで読んでいただきありがとうございました!
www.tecotec.co.jp