EY-Office ブログ

React 18への予習シリーズ:トランジション

React 18への予習シリーズ:Suspenseを復習しよう に続く、React18シリーズです。今回は今後のアプリ作りに影響しそうなトランジションを取り上げます。

トランジションはReact 16.13で実験的機能として実装されました、公式ページの並列的 UI パターン(実験的機能)に詳しい説明があります。しかし、React 18 alphaではAPI等が変更されているので注意してください。

transition

従来のReact

まずは、Suspenseを使った普通のReactアプリです。

このアプリは

  1. ロードされるとFirst PageとNextボタンが表示されます
  2. Nextボタンを押すと、画面が切り替わりLoading...と表示されます。同時にAPIサーバーからデータ取得が開始されます
  3. APIサーバーからデータが取得できると、Next Pageとサーバーから取得された文字列(Hello!)が表示されます。

以下は画面の動きをアニメーションGIFにしたものです。

コードは以下のようになります、Suspenceに付いてはReact 18への予習シリーズ:Suspenseを復習しようを読んでください。

import React, { useState, Suspense } from 'react'
import useSWR from 'swr'

const URL = "APIサーバーのURL"
type MessageType = {message: string}

export const App: React.FC = ()  => {
  const [pageNo, setPageNo] = useState(1)
  return (
    <Suspense fallback={<p>Loading...</p>}>
      {pageNo === 1 && <FirstPage pushed={() => setPageNo(2)} />}
      {pageNo === 2 && <NextPage />}
    </Suspense>
  )
}

type FirstPageProps = { pushed: () => void }
const FirstPage: React.FC<FirstPageProps> = ({pushed}) => {
  return (
    <>
      <h2>First Page</h2>
      <button onClick={pushed}>Next</button>
    </>
  )
}

const NextPage: React.FC = () => {
  const {data} = useSWR<MessageType>(URL, { suspense: true })
  return (
    <>
      <h2>Next Page</h2>
      <p>{data?.message}</p>
    </>
  )
}

トランジション

さて、次はReact 18で正式APIになるトランジション(useTransition)を使ったプログラムです。

このアプリは

  1. ロードされるとFirst PageとNextボタンが表示されます
  2. Nextボタンを押すと、画面はそのままでボタンの文字がNext...と表示されます。同時にAPIサーバーからデータ取得が開始されます
  3. APIサーバーからデータが取得できると、Next Pageとサーバーから取得された文字列(Hello!)が表示されます。

以下は画面の動きをアニメーションGIFにしたものです。

従来のアプリとの違いは、ボタンを押すと次の画面コンポーネントが実行されているのですが、画面表示は最初のページのな点です。ボタンを押しても次のページのコンテンツが準備できるまでは現在の画面が表示されていてLoading...のような無意味な画面は表示されない事です。また、コンテンツ取得中も最初のページのコンテンツが読めるのはUX的にも良いように思えます。

import React, { useState, useTransition, Suspense } from 'react'
import useSWR from 'swr'


const URL = "APIサーバーのURL"
type MessageType = {message: string}

export const App: React.FC = ()  => {
  const [pageNo, setPageNo] = useState(1)
  return (
    <Suspense fallback={<p>Loading...</p>}>
      {pageNo === 1 && <FirstPage pushed={() => setPageNo(2)} />}
      {pageNo === 2 && <NextPage />}
    </Suspense>
  )
}

type FirstPageProps = {
  pushed: () => void
}
const FirstPage: React.FC<FirstPageProps> = ({pushed}) => {
  const [isPending, startTransition] = useTransition() // ← ①
  return (
    <>
      <h2>First Page</h2>
      <button onClick={() => startTransition(() => pushed())}> // ← ②
        {isPending ? "Next..." : "Next"}  // ← ③
      </button>
    </>
  )
}

const NextPage: React.FC = () => {
  const {data} = useSWR<MessageType>(URL, { suspense: true })
  return (
    <>
      <h2>Next Page</h2>
      <p>{data?.message}</p>
    </>
  )
}

コードは上のようになります。「従来のReact」コードとの違いはごくわずかです。

  • ① トランジションを使うには、useTransitionホックを使います。戻り値はトランジション中(通信中など)を表す論理値isPendingとトランジション開始関数startTransitionです
  • ② ボタンを押したイベント処理でトランジションを開始するstartTransition関数を実行します、引数は、ページ遷移(トランジション)を起こすState変更処理を指定します。これによりサーバーからのデータ取得が終わるまでページは変化しなくなります。
  • ③ トランジション中を表すisPendingを使ってボタンの文字を変えています。サーバーからデータ取得中になっているかを示す事でユーザーに安心感を与えています。

これだけで、新しいスタイルのページ遷移ができます!

さて、なぜこれがConcurrent Mode(並列モード)によって可能になったのかは、次回解説したいと思っています。

おまけ : 2021-07-14日時点でのReact 18 alpha + TypeScriptの環境構築方法

React 18 alphaでTypeScriptが使える環境の構築の公式ドキュメントはありません。 今回はHow to use TypeScript with React 18 alphaを参考にしました。

Alphaバージョンなので今後変更されるかも知れませんので、試すときは最新のドキュメントも参考にしてください。

インストール

まず、create-react-appを使ってTypeScriptベースの開発環境を作ります。テンプレートcra-template-ey-officeは私が作ったものですがtypescriptでも良いと思います。

プロジェクト作成後、React18alphaを --forceオプションを付けてインストールします。

$ npx create-react-app react18alpha --template cra-template-ey-office
$ cd react18alpha
$ npm install react@alpha react-dom@alpha --force
設定ファイルの変更

TypeScriptの設定ファイルtsconfig.json以下のようにtypesオプションを追加します。

-    "jsx": "react-jsx"
+    "jsx": "react-jsx",
+    "types": ["react/next", "react-dom/next"]
React18のrootAPIへの変更

index.tsxにあるReactDOM.renderをReact18用のReactDOM.createRootに変更します。

-  ReactDOM.render(<App />, document.getElementById('root'))
+  ReactDOM.createRoot(
+    document.getElementById('root') as HTMLElement).render(<App />)
実行は今まで通りです
npm start

- about -

EY-Office代表取締役
・プログラマー
吉田裕美の
開発者向けブログ