【備忘録】React：Suspense と RSC（React Server Components）関連の簡易メモ

## はじめに
webサイトやちょっとしたwebアプリ・サービスをReactで作ったりして楽しんだり、うまくいかずに苦しんだりしている benjuwan と申します。好きこそ物の上手なれ精神で手を動かしたり、情報を日々集めたりしているのですが、そうした中で React の`Suspense`と`RSC(React Server Components)`が個人的に小難しくて混乱したので勉強・参考になったサイト様の情報をもとに備忘録を残そうと思います。

:::note warn
2023/12/07 追記
結論として、**素の React で SSR を行うのは難易度が高い**。
SSR したければ `Next.js` や `Remix` などのフレームワークの使用が一般的。

参照：[りあクト！ TypeScriptで始めるつらくないReact開発 第4版【③ React応用編】](https://booth.pm/ja/items/2367992)
:::


## 参照元
https://eh-career.com/engineerhub/entry/2023/07/14/093000

https://qiita.com/wataru86/items/3affe191d324cc7389d6

https://zenn.dev/uhyo/books/react-concurrent-handson/viewer/data-fetching-1

## Suspense
### `Suspense`を使えば **コンポーネント自体にState（コンポーネント読込前後の状態）** を付与できる。
> Suspenseは通常の状態では何もせず、子として渡されたコンテンツ（children）がレンダリングされます。しかし、中身のコンポーネントがサスペンドした場合、中身がレンダリングされる代わりにfallbackとして渡されたコンテンツがレンダリングされます。
**サスペンドというのはSuspense用語で、簡単に言えばコンポーネントが「まだレンダリングできない」という状態**に陥ることを指します。より簡単に言えば、**コンポーネントが「ローディング中」である場合にサスペンド**します。
例えば、サーバーからデータを読み込んで表示する責務を持つコンポーネントの場合、データの読み込みが完了するまでは「ローディング中」とするのが妥当でしょう。そのため、Suspenseコンポーネントのfallbackには、内部のコンテンツがローディング中の場合に表示する内容を与えるのが普通です。
```
import { Suspense } from 'react';
// ...
<Suspense fallback={<div>Loading...</div>}>
  <SomeComponent />
</Suspense>
```
> なお、Suspenseの内部のいずれか1つのコンポーネントがサスペンドすればfallbackに切り替わります。上の例で言えば、SomeComponentだけでなくさらにその内部のコンポーネントがサスペンドした場合にもfallbackに切り替わります。

### コンポーネントのレンダリングが成功しないとフック用の記憶領域は用意されない
> （...中略）そもそも**コンポーネントのレンダリングが成功していないので、ステートの記憶領域が用意されていない**のです。つまり、**コンポーネントがサスペンドした場合には、（そのコンポーネントから投げられたPromiseを除いては）そのコンポーネントのレンダリングを試みたという記録が歴史から抹消されます**。よく「Reactコンポーネントのレンダリング中に副作用を起こしてはいけない」と言われますが、その実際的な理由の一端がこれです。レンダリングは無かったことにされたのに副作用だけ残ってしまう恐れがあるので、やってはいけないのです。

> （...中略）`useState`だけでなく`useRef`を使ってもコンポーネント内にデータを保持することはできません。フック用の記憶領域はすべてレンダリングが完了しないと用意されないのです。

> **コンポーネントのレンダリングがサスペンドした場合、レンダリングを試みたという事実が歴史から抹消される**のでした。これはすでにレンダリングされたコンポーネントが**再レンダリング時にサスペンドした場合も例外ではありません**。この場合、その再レンダリング中にコンポーネントの記憶領域に書き込まれたものがロールバックされます。

### コンポーネントの読込前後の状態に応じてレンダリング（処理）を分離できる
｜---読込前：fallbackに記述した内容（`fallback={<div>Loading...</div>}`）をレンダリング
｜---読込後：ネストしているコンポーネント（上記では`<SomeComponent />`）をレンダリング

### `Suspense`を使うには非同期処理をさせておく必要がある
> 上記の仕組み上、Suspenseを活用するためには、内部のコンポーネントをサスペンドさせる必要があります。これには基本的に、サスペンド機能を内蔵したデータフェッチングライブラリを使用します

ライブラリを使わずとも使える？？

https://qiita.com/wataru86/items/3affe191d324cc7389d6
> 「非同期処理の実行中」や「非同期処理の完了」は Promise を使って検知します。
この新しい Suspense の使用には、Promise を throw するコンポーネントを作る必要があります。
最初は Promise を throw し、その Promse が解決したら 結果の値を返す fetchPosts を用意します。

::: note info
~~上記記事の該当コード部分を CodeSandBox で試したのですが、私のスキル不足も相まってか機能しませんでした……（泣）~~
2023/12/10 追記
`useSWR`を使用して解決。`suspense`機能は一般的に対応するフレームワークまたはライブラリ（SWRのような）を用いて実装する。
:::

https://codesandbox.io/embed/suspense-wwr8hs?fontsize=14&hidenavigation=1&theme=dark

## RSC(React Server Components)
### `RSC(React Server Components)`は「React Canary」という開発途上な機能なので、フレームワークを活用して使用する
> RSCは本記事執筆時点でまだ安定版としてリリースされていませんが、React Canaryでは使用することができます。このように、React CanaryはReactの新機能を先行して使うことができるチャンネルです。その代わり、頻繁に破壊的変更が入る可能性があります。
破壊的変更への対応は大変になるため、React Canaryを皆さんが直接利用することは普通ありません。フレームワークを介して利用し、破壊的変更はフレームワークのレイヤーで吸収することが想定されています。例えばNext.js 13.4でApp Routerという機能がstable扱いになりましたが、これもReact Canaryの機能であるRSCを利用しています。まだCanaryな機能であっても、フレームワークが頑張ることで一般ユーザーに安定した機能として提供されるのです。

> 今後、安定版を利用可能になったとしても、自力でRSCを使うためにはそれに対応したサーバー実装を用意する必要があるため、やはりフレームワークを使用した方がお手軽です。

### クライアント用コンポーネントとサーバー用コンポーネントで使い分ける
> RSCではサーバー用とクライアント用の2種類にコンポーネントを分類する必要がありますが、それぞれで利用できる機能が異なります。
サーバー用のコンポーネントでは、従来のReactでは使用できなかったasync関数コンポーネントが可能です。
一方、`useState`や`useEffect`といった動的な挙動を含むフックはサーバー用コンポーネントからは使用できず、クライアント用のコンポーネントでのみ利用できます。

#### クライアント用コンポーネントには冒頭に`use client`という記述が必要
> クライアント用コンポーネントが書かれたファイルは冒頭に"use client";という宣言が必要です。

#### サーバー用コンポーネントからクライアント用コンポーネントは呼び出せるが、逆（クライアント →　サーバー）はできない。けれども、children として props で渡せば使える
> クライアント用コンポーネントからサーバー用コンポーネントを呼び出すことはできません。これは「クライアント用コンポーネントをブラウザ上で実行してみたら実はサーバー用コンポーネントが必要だったとわかった」という場合でも、サーバーに問い合わせるわけにいかないからです。
ただし、**クライアント用コンポーネントの`children`や他の`props`にサーバー用コンポーネントを渡すことはできます**。これは、クライアント用コンポーネントの内部で必要となるサーバーコンポーネント部分をあらかじめレンダリングして渡しておくイメージです。

#### サーバー用コンポーネントのレンダリング結果（HTML）がクライアント用コンポーネントに渡ってきて、クライアント用コンポーネントのレンダリング結果（HTML）はそのままブラウザで表示される
> サーバー用コンポーネントであるOtherServerComponentは**サーバー側でレンダリングが完了し、ただのHTMLとなってブラウザに送られます**。つまり、仮にOtherServerComponentsが内部で100キロバイトあるライブラリを使用していたとしても、ブラウザにそのライブラリをダウンロードする必要がありません。

> クライアント用コンポーネントの場合**そのレンダリングはクライアント側で行われるので、ClientComponentのソースコードがブラウザに送られます**。

## さいごに
今後も分かり次第、随時反映していきたいと思います。