見出し画像

React18で追加されたuseTransitionを試してみた

こんにちは。Showcase Gigのかいづかです。今回はReact18で導入されたuseTransitionというHooksを試しに使ってみたので、サンプルを交えながら紹介してきたいと思います。useTransitionの使い方を知りたい人の助けになれば幸いです。
本記事は、 Showcase Gig Advent Calendar 2022 の12日目の記事です。


useTransitionはどんな機能か

緊急性の高い更新と緊急性の低い更新をReactに伝えることができるものです。

緊急性の高い更新とは、テキストフィールド,ドロップダウンなどのユーザーの操作に対して、即座に反映されることが期待されるものです。テキストフィールドに値を入力してるのに、値が全然反映されないと使ってる人は不安になりますもんね。

逆に、テキストフィールドに値が入力された結果に対して一覧を絞り込むのは、テキストフィールドに値が入力されたと同時に結果を反映されなくてもユーザーはそこまで気にならないでしょう。また、一覧の絞り込み反映が終わらないうちにテキストフィールドやドロップダウンの絞り込みのための値が変更された場合は、気になるのは最終的な一覧のみです。

useTransitionの使い方としては、以下です。結構シンプルな印象です。

//実行中の状態(boolean)とトランジションを開始するための関数を返してくれます
const [isPending, startTransition] = useTransition();

//この関数の第一引数に渡した関数内の更新が緊急度の低いものとされる
startTransition(()=>{
  //ここにstateの更新など
})


参考

実際にサンプルを動かしてみる

それでは実際に、useTransitionを使った場合の挙動を確認してみましょう。サンプルのアプリは以下です。食べ物の一覧があって、左側のテキスト検索と右側のドロップダウンで絞り込むことができます。
違いがわかりやすいように一覧のデータは、10,000件にしてます。


サンプルアプリのスクショ

サンプルアプリのコードは以下です。余談ですが、viteで環境構築したのですが、こういうサクッと検証したい時に便利だなぁと思いました。
https://github.com/kai815/try-react18/tree/try-useTransition


useTransitionを使ってない時

コードは以下です。

//foodListのデータの生成のコードなどは割愛してます

export const FoodLists = () => {
  const [filteredFoodList, setFilteredFoodList] = useState<Food[]>(foodList)
  const [searchText, setSearchText] = useState<string>('')
  const [selectedGenre, setSelectedGenre] = useState<string>('')
  
  const onInputChange = (event:React.ChangeEvent<HTMLInputElement>) => {
    setSearchText(event.target.value)
    setFilteredFoodList(filterFoodList(event.target.value, selectedGenre))
  }

  const onSelectChange = (event:React.ChangeEvent<HTMLSelectElement>) => {
    setSelectedGenre(event.target.value)
    setFilteredFoodList(filterFoodList(searchText, event.target.value))
  }

  return (
    <div>
      <p>FoodList</p>
      <div style={{display:'flex',justifyContent:'space-between'}}>
        <input onChange={onInputChange} value={searchText}/>
        <select value={selectedGenre} onChange={onSelectChange}>
          <option value={''}></option>
          <option value={genreList.japanese}>
            {genreList.japanese}
          </option>
          <option value={genreList.italian}>
            {genreList.italian}
          </option>
          <option value={genreList.curry}>
            {genreList.curry}
          </option>
          <option value={genreList.chinese}>
            {genreList.chinese}
          </option>
        </select>
      </div>
      {filteredFoodList.map((food)=>{
        return (
          <div key={food.id} style={{width:'500px',background:food.color}}>
            <p>{food.name}</p>
            <p>ジャンル:{food.genre}</p>
          </div>
        )
      })}
    </div>
  )
}

挙動は以下の通りになります。
ドロップダウンの選択時やテキストの入力時、ドロップダウンやテキストの入力の変更はすぐに反映されずに一覧のフィルターが終わったと同時に反映されることがわかるかと思います。

useTransitionを使っていない

useTransitionを使った時

コードは以下です。

//foodListのデータの生成のコードなどは割愛してます

export const FoodLists = () => {
  //実行中の状態(boolean)とトランジションを開始するための関数
  const [isPending, startTransition] = useTransition()
  const [filteredFoodList, setFilteredFoodList] = useState<Food[]>(foodList)
  const [searchText, setSearchText] = useState<string>('')
  const [selectedGenre, setSelectedGenre] = useState<string>('')

  const onInputChange = (event:React.ChangeEvent<HTMLInputElement>) => {
    setSearchText(event.target.value)
    //緊急度の低い一覧の更新の関数をstartTransitionに渡します
    startTransition(()=>{
      setFilteredFoodList(filterFoodList(event.target.value, selectedGenre))
    })
  }

  const onSelectChange = (event:React.ChangeEvent<HTMLSelectElement>) => {
    setSelectedGenre(event.target.value)
    //緊急度の低い一覧の更新の関数をstartTransitionに渡します
    startTransition(()=>{
      setFilteredFoodList(filterFoodList(searchText, event.target.value))
    })
  }

  return (
    <div>
      <p>FoodList</p>
      <div style={{display:'flex',justifyContent:'space-between'}}>
        <input onChange={onInputChange} value={searchText}/>
        <select value={selectedGenre} onChange={onSelectChange}>
          <option value={''}></option>
          <option value={genreList.japanese}>
            {genreList.japanese}
          </option>
          <option value={genreList.italian}>
            {genreList.italian}
          </option>
          <option value={genreList.curry}>
            {genreList.curry}
          </option>
          <option value={genreList.chinese}>
            {genreList.chinese}
          </option>
        </select>
      </div>
      {filteredFoodList.map((food)=>{
        return (
          <div key={food.id} style={{width:'500px',background:food.color}}>
            <p>{food.name}</p>
            <p>ジャンル:{food.genre}</p>
          </div>
        )
      })}
    </div>
  )
}

挙動は以下の通りになります。
ドロップダウンの選択時やテキストの入力時、ドロップダウンやテキストの入力の更新はすぐに反映されて、一覧の更新はそれらの変更に比べて少し後になっていることがわかります。

useTransitionを使っている

さらに、useTransitionは実行中の状態(boolean)を返してくれるので、それを用いることで以下のように、ユーザーに一覧を更新中であることを知らせることができます。

isPendingを使って更新中・・を表示

補足

もしサンプルをローカルで動かしても違いがわかりにくかった方は、Chromeの検証ツールのperformanceのCPUの設定を変えるといいかもしれません。

検証ツール


最後に

いかがだったでしょうか。新たに追加されたuseTransitionを使いこなすことができれば、よりユーザーにとって使いやすいサービスを使いこなすことができるかもしれませんね。

この記事が気に入ったらサポートをしてみませんか?