2021 年、特に下半期で様々な動きがあった。
- React
- React 18
- Suspense
- Server Components
- React Forget (Compiler for auto memorization)
- Automatic Batching in event handlers
- Support on Web Components (Experimental)
- Plugins
- React Router v6.0
- React Location v1.0
- MUI v5.0 (リブランディング)
- Create-React-App (CRA)
- CRA 5
- Webpack 5 support
- CRA 5
- Remix (React framework for SSR)
- Next.js
- Next.js 11
- script を読み込むコンポーネント
next/script
next/image
で画像サイズを自動判定- Webpack 5 support
- Migration from CRA
- script を読み込むコンポーネント
- Next.js 12
- Rust compiler
swc
- Middleware (beta)
- React 18 support (Experimental)
- Rust compiler
- Next.js 11
- Gatsby.js
- Gatsby.js 3
- GraphQL 15 support
- React 17 support
- Webpack 5 support
- Gatsby.js 4
- Deferred Static Generation (DSG)
- Server Side Rendering (SSR)
- Gatsby.js 3
特につい最近開催された React Conf 2021 は React 公式より 記事 が出ている。
CSS フレームワーク界隈でも Material-UI のリブランディングもあった。
スタイリングソリューション再考の一環で emotion または styled-components から選択可能です。
# Material-UI v4.0-
npm i @material-ui/core
yarn add @material-ui/core
# MUI v5.0+
# emotion
npm i @mui/material @emotion/react @emotion/styled
yarn add @mui/material @emotion/react @emotion/styled
# styled-components
npm i @mui/material @mui/styled-engine-sc styled-components
yarn add @mui/material @mui/styled-engine-sc styled-components
v5.0 では makeStyles()
が廃止される代わりに styled()
を利用することによってカスタマイズできる点は大きな変更点のひとつ。
なおこの後当ブログでは Suspense と React Location に焦点を絞って書かせていただく。
参照するソースコードについて
これより参照するソースコードは今年、自身がジョインするプロジェクトとその周りにいるエンジニアを対象として行った React ハンズオンを下に説明させていただく。
その際に一般利用可能な Pokemon API を Web アプリ内で使って進めた。
この度の目標は Hooks API をベースとした Web アプリケーションを作ることとした。またターゲットを考慮して、フレームワーク Next.js を一切使わない選択をとった。
その目標を達成するため複数の技術的なマイルストーンを合わせ準備している。
react-query
またはswr
を利用した API のフェッチuseState
を利用した状態管理useMemo
を利用した再描画の仕組み
Suspense
React 18 の Concurrent Rendering という機能で、コンポーネントそのものが「ローディング中でまだレンダリングできない」状態になる。
import { useQuery } from 'react-query'
import './App.css'
import './assets/gallery.css'
import { CardList } from './components/CardList'
function App() {
const { data, error } = useQuery('items', async () => {
return await fetch(`${process.env.REACT_APP_POKEMON_API}/pokemon?limit=200&offset=200`).then(
(res) => res.json(),
)
})
if (!data) return <div>Loading..</div>
if (error) return <div>Failed</div>
return (
<div className="App">
<div className="gallery">
<CardList data={data.results} search={searchText} />
</div>
</div>
)
}
Suspense を使う場合は CardList の責務が簡略化される。具体的にローディング中の処理は CardList の責務で無くなる。
import { useQuery } from 'react-query'
import './App.css'
import './assets/gallery.css'
import { CardList } from './components/CardList'
function App() {
const { data, error } = useQuery('items', async () => {
return await fetch(`${process.env.REACT_APP_POKEMON_API}/pokemon?limit=200&offset=200`).then(
(res) => res.json(),
)
})
if (error) return <div>Failed</div>
return (
<div className="App">
<div className="gallery">
<React.Suspense fallback={<>Loading..</>}>
<CardList data={data?.results} search={searchText} />
</React.Suspense>
</div>
</div>
)
}
また非同期コンポーネントを複数面倒見れる点も嬉しい。
<React.Suspense fallback={<>Loading..</>}>
<Search text={searchText} setText={handleInputClick} />
<CardList data={data?.results} search={searchText} />
</React.Suspense>
React 18 の SSR ストリーミングは Suspense を前提に、初期状態を表す HTML を最速で送ってその後データが揃ったらその HTML を置き換える。
これを活用するために非同期処理は Suspense を用いて書く必要がある。
またそのレンダリングがサーバ側で非同期的に行われる特徴を Server Components にも活かせる。
:::message is-primary Server Components とは。
Client Components は props を渡すと仮想 DOM が返る。一方で絶対 React コンポーネントを async にできない props を受け取ったら同期的に仮想 DOM を返さないといけない。
なお Server Comonents とそれが import しているあらゆるものはバンドルに一切含まれない。
これこそ Zero-bundle-size と言われている所以です。
:::
React Location
React Location は React Query の作者でもある @tannerlinsley が開発した React 用の新しい Router ライブラリです。
同氏は他にも React Table や React Chart などのライブラリも公開しています。
React Router が Remix との組み合わせを想定しているのに対し、兼ねてより SPA として利用するため CSR 下での非同期処理やキャッシュなどが盛り込まれている。
React Router と比較して、別途ルーティングリストとして切れるようになったのは大きい。
export const routes = [
{
path: '/',
element: <Index />,
},
{
path: '/about',
element: <About />,
},
]
src/App.tsx
で Router コンポーネントでラップする。
import { routes } from './routes.constants'
const location = new ReactLocation()
function App() {
return (
<Router location={location} routes={routes}>
<div>
<Outlet />
</div>
</Router>
)
}
基本的にはこれだけ実装すれば OK です。
ですが Route Loader を使う選択肢も存在、下記目的も合わせて確認いただきたい。
- 基本的に並列実行される
- キャッシュの最大経過時間を設定できる
- loader の実行中または実行した後に、特定の箇所を強制的に更新する
const routes = [
{
path: '/',
loader: async (_match: any, { dispatch }: any) => {
const teamsRes = await fetch(
`${process.env.REACT_APP_POKEMON_API}/pokemon?limit=200&offset=200`,
)
dispatch({
type: 'maxAge',
maxAge: Number(teamsRes.headers.get('max-age')),
})
return {
data: await teamsRes.json(),
}
},
element: <Index />,
},
{
path: '/about',
element: <About />,
},
]
useMatch
フックを利用して loader データを使う。
import { useMatch } from 'react-location'
function Index() {
const {
data: { data },
} = useMatch()
}
激重 API などを対象に上手く Route Loader も使えたら、パフォーマンス最適化に繋がりそうだ。
API 更新
React 18 で各種 API の更新が入るようです。
なお ルート要素へのレンダリング API も更新されるようです。
import ReactDOM from 'react-dom'
import App from 'App'
const container = document.getElementById('root')
// React 17-
ReactDOM.render(<App />, container)
// React 18+
const root = ReactDOM.createRoot(container)
root.render(<App />)
最後に
簡単な Web アプリを製作することで React 18 の片鱗に触れてきた。
詳しくは各種ブランチをチェックいただければ幸いです。
- React Router の基礎を feature/react-router ブランチで確認する
- React Router で Suspense の利用を feature/react-query_suspense ブランチで確認する
- React Location の基礎を feature/react-location ブランチで確認する
- React Location で Route Loader オブジェクトの利用を enhance/react-location_route-loader ブランチで確認する