🗂
いまさらのReact 18
FYI: React Confの動画
(個人的)React v18のメイン
- Suspense
- automatic batching
- useDefferedValue hook
- startTransition
今回はReact18と言うよりSuspenseの話になります
Suspenseによって何が変わるのか
- UX面
- ユーザーに対して表示できるところから段階的にビューを表示させることができる
- 重たい処理が必要なところは完了後に表示させる
- ユーザーに対して表示できるところから段階的にビューを表示させることができる
- コード面
- ビューの要素だけをJSXに書くことができる
- isLoading&&<Loader/>みたいな奴がいらなくなる
- Apollo,React Queryと組み合わせることでfetch on renderとかfetch then renderを解消できる
- ビューの要素だけをJSXに書くことができる
fetch on renderのコード
ref: https://ja.reactjs.org/docs/concurrent-mode-suspense.html#approach-1-fetch-on-render-not-using-suspense
function ProfilePage() {
const [user, setUser] = useState(null);
useEffect(() => {
fetchUser().then(u => setUser(u));
}, []);
if (user === null) {
return <p>Loading profile...</p>;
}
return (
<>
<h1>{user.name}</h1>
<ProfileTimeline />
</>
);
}
function ProfileTimeline() {
const [posts, setPosts] = useState(null);
useEffect(() => {
fetchPosts().then(p => setPosts(p));
}, []);
if (posts === null) {
return <h2>Loading posts...</h2>;
}
return (
<ul>
{posts.map(post => (
<li key={post.id}>{post.text}</li>
))}
</ul>
);
}
Suspense
- ひとことで: コンポーネントがPromiseを投げるようになったことでユーザー体験爆上がり
Server rendering with Suspense
SSR
- 何をするのか
- データの取得
- HTMLの描画
- JS読み込み
- ハイドレーション (後で説明します)
- Pros Cons
- HTMLを早くユーザーに表示させることができるので体験がいい
- JavaScriptがロードされていないので、アプリケーションはインタラクティブではない
- 単なるHTMLが表示されているだけだから
- アプリケーション全体のデータを読み込むまでは何もHTMLが表示されない
- fetch everything , before you can show anythingと言っている
CSR
- 何をするのか
- クライアントで画面の描画を行う
- Pros Cons
- サクサク
- JSのバンドルサイズが大きくなると初回表示に時間がかかる
- 何かを表示する前に全てを読み込んでおく必要がある
-> SSR,CSR両方とも読み込むのにアプリケーション全体をストップさせる必要があった
ハイドレーション
- HTMLをインタラクティブにするもの
- コンポーネントツリーを走査してイベントハンドラを割り当てる
- 全体に一度でハイドレートさせる必要がある
- アプリケーション全体のJSを全て読み込む必要がある = 時間がかかる
- ハイドレーションが完了するまではインタラクティブではないものをユーザーに表示している状態になる
- クリックしても何も反応しない
- アプリケーション全体のハイドレーションが完了するまで、アプリケーション全体はインタラクティブにならない
React18では分割してHTMLを表示し、それぞれを部分的にハイドレートできるようになった 🎉
→ これを実現しているのがStreaming HTML
Streaming HTML
- データを取得している間はfallback用のHTMLを描画して、データ取得が完了するとその要素を動的に置き換える → これによって画面全体のデータ取得を待つ必要がなくなる
- fallback用のHTML = ローディングのスピナーなど
- 置き換わる要素 = 取得に時間のかかった要素
- 下の画像で言うところの<Comments>
非同期的にハイドレートしているためHTMLを早く表示し、データ取得が完了したところからインタラクティブにすることができるようになる
実装
- Suspenseで囲むことで後回しにする処理を伝える
- これだけ
- エラーハンドリングはErrorBoundaryを使う
- イメージ的にはtry-catch
<Suspense fallback={<Spinner/>}>
<Comments />
</Suspense>
冒頭のコードはこうなる
// This is not a Promise. It's a special object from our Suspense integration.
const resource = fetchProfileData();
function ProfilePage() {
return (
<Suspense fallback={<h1>Loading profile...</h1>}>
<ProfileDetails />
<Suspense fallback={<h1>Loading posts...</h1>}>
<ProfileTimeline />
</Suspense>
</Suspense>
);
}
function ProfileDetails() {
// Try to read user info, although it might not have loaded yet
const user = resource.user.read();
return <h1>{user.name}</h1>;
}
function ProfileTimeline() {
// Try to read posts, although they might not have loaded yet
const posts = resource.posts.read();
return (
<ul>
{posts.map(post => (
<li key={post.id}>{post.text}</li>
))}
</ul>
);
}
ハイドレーションが完了したところからインタラクションできる
- 複数のSuspenseでラップされている場合、非同期でツリーの上から順にハイドレートされる
- Suspenseは複数のコンポーネントをラップできる
- 全体がSuspendされる
- Reactはユーザーが関心のある順にハイドレーションを行う → Selective Hydration
- 絶対にツリーの上から順にハイドレートされるわけではない
Selective Hydration
- ユーザーがクリックした箇所からハイドレーションを行える
automatic batching
- 非同期処理やイベントハンドラーのコールバック内にある状態更新関数実行後のレンダリングをまとめて1回にしてくれる
// これはReact17以前でもサポート済み
function handleClick() {
setIsFetching(false)
setIsError(null)
setStatus("Success")
}
setTimeout(() => {
setCount(v => v + 1);
setChecked(checked => !checked);
}, 1000);
useDeferredValue
- めっちゃ重い処理を明示的に後回しにするhook
- フィルターされるアイテムリストとか
- React.memoを使えないところで活躍する
- memoされていても、親コンポーネントがレンダリングされる時とか
const deferredValue = useDeferredValue(//重い処理)
React Server Components
- コンポーネントをサーバーサイドでレンダリングする
- Stateを持たない、再レンダリングされない
- useStateやuseEffectなどを使えない
- Stateを持たない、再レンダリングされない
-
バンドルサイズが0
- レンダーした結果のJSXをクライアントに送るだけ = ただのAPI通信
- Suspenseを使える
SSRとの違い
- SSRは早く画面をユーザーに表示させることをメインにしてる
- RSCはバンドルサイズを減らすことをメインにしてる
まとめ
- Reactはアプリケーションのユーザー体験と、Reactのユーザー(開発者)の体験をよくすることにめちゃくちゃフォーカスしてる
- 動画の中でもUXって言葉がめちゃくちゃ出てきた
参考資料
Automatic batching
useDeferredValue
Discussion