LIFF アプリを React 18 (Vite) で書いてみよう
前置き
2022 年 3 月 3 日に LINE Developer コミュニティ 主催の下 React (Vite) × LIFF ハンズオンを実施いたしました。
教材は Zenn book を利用して書いています。
ハンズオンではビルドツール Vite を利用して LIFF アプリを製作しました。
今回は、正式リリースされた React 18 を下に、先日製作した LIFF アプリを React 18 上でも動作させるために、何に対して注意するべきか書かせていただきます。
React 18 リリース
3 月 30 日に React 本体が、遅れて 4 月 7 日に型定義ファイルもリリースされています。
先日製作した LIFF アプリは Zenn book の 教材 と合わせ、基本的に React 17 を想定しています。
ですが React 18 でも問題なく動作することを確認しております。
React 18 で注意するべきこと
React 18 に更新する際、その変更差分は大きくないものの、いくつか気を付けたいことがあります。
-
createRoot
への仕様変更 - 暗黙的なコンポーネントにおける Children の扱い
-
useEffect
hook 周辺の挙動に留意 - React 18 の新機能
- Concurrency モード
- 自動バッチ処理
- トランジション (Transition)
- サーバサイドにおける React Suspense
なお、新機能については別途ブログに書かせていただいております。
createRoot
への仕様変更
Root DOM 作成 API に変更がありました。
- React 17 までは
ReactDOM.render
を使っていた - React 18 では
ReactDOM.createRoot
を使う
具体的な差分は コミットログ をご確認いただければ幸いです。
Web アプリケーションのルートで src/main.tsx
を読み込んでいるでしょうけれど、この変更点は大きいので注意していただければ幸いです。
暗黙的なコンポーネントにおける Children の扱い
Children を実装しているものの、下記暗黙の宣言に依存しているコンポーネントについて、削除される破壊的変更があり注意しなければいけません。
React.FunctionComponent
React.Component.Function
interface Props {
children?: React.ReactNode
}
class SomeClassComponents React.Component<Props> {
render() {
return <div>{this.props.children}</div>
}
}
const SomeFunctionComponent: React.FunctionComponent<Props> = props => <div>{props.children}</div>
この通り場合によっては、これまでの型付けで動かなくなっているケースがあるため、こちらにも注意していただければ幸いです。
なお、自動化された 移行スクリプト があるので、この利用も検討すべきです。
useEffect
hook 周辺の挙動に留意
StrictMode を取り入れる動機が、コンポーネントをアンマウントする代わりに、状態を保持することを可能にするため。
この目的を達成するため、コンポーネントをアンマウントするときと同じライフサイクルフックを呼び出しますが、コンポーネントと DOM 要素の両方の状態を保持することになります。
それは、すなわちコンポーネントのマウントとアンマウントを複数回繰り返すことを意味しており、複数回呼び出される可能性のある useEffect
で init()
しない方が良いでしょう。
import React from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App'
import('@line/liff').then((liff: any) => {
liff
.init({ liffId: import.meta.env.VITE_APP_LIFF_ID })
.then(() => {
const container = document.getElementById('root')
if (!container) throw new Error('Failed to find the root element')
createRoot(container).render(
<React.StrictMode>
<App />
</React.StrictMode>
)
})
.catch((err: any) => {
console.error({ err })
})
})
また React 18 の新機能のひとつ Concurrency モードでは、レンダリング作業を分割し、ブラウザのブロックを回避するために作業を一時停止および再開します。
このようにレンダリングの遅い場合が存在することも意味しています。
ライフサイクル周りの挙動に変更が存在する以上、改めてこの辺りを中心に見直しておきたいとも考えている次第です。
最後に
LIFF アプリとして動かすにあたり、上記以外の点について特に問題は見受けられないと考えております。
なお、以下リポジトリで React 18 に対応しています。
Zenn book の 教材 と合わせ、いま一度ご確認いただきますと良いものと考えています。
Discussion