いまさらのReact v18


React Confの動画を見て感動したので社内の10分勉強会で共有しました。
その時に使用したまとめ資料です。

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を解消できる

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

  • ハンドラーの中にあるsetStateの実行後のレンダリングをまとめて1回にしてくれるやつ
    • これまでは状態更新関数ごとに再レンダリングしていた
// この処理による再レンダリングが1回になる
function handleClick() {
  setIsFetching(false)
  setIsError(null)
  setStatus("Success")
}

useDeferredValue

  • めっちゃ重い処理を明示的に後回しにするhook
    • フィルターされるアイテムリストとか
  • React.memoを使えないところで活躍する
    • memoされていても、親コンポーネントがレンダリングされる時とか
const deferredValue = useDeferredValue(//重い処理)

React Server Components

  • コンポーネントをサーバーサイドでレンダリングする
    • Stateを持たない、再レンダリングされない
      • useStateやuseEffectなどを使えない
  • バンドルサイズが0
    • レンダーした結果のJSXをクライアントに送るだけ = ただのAPI通信
  • Suspenseを使える

SSRとの違い

  • SSRは早く画面をユーザーに表示させることをメインにしてる
  • RSCはバンドルサイズを減らすことをメインにしてる

まとめ

  • Reactはアプリケーションのユーザー体験と、Reactのユーザー(開発者)の体験をよくすることにめちゃくちゃフォーカスしてる
    • 動画の中でもUXって言葉がめちゃくちゃ出てきた

参考資料

Automatic batching

https://github.com/reactwg/react-18/discussions/21

useDeferredValue

https://github.com/reactwg/react-18/discussions/129

RFC

https://github.com/reactjs/rfcs/pull/188