Techtouch Developers Blog

テックタッチ株式会社の開発チームによるテックブログです

v5 で何が変わる? 非同期状態管理ライブラリ TanStack Query の新機能と最適化

v5 で何が変わる? 非同期状態管理ライブラリ TanStack Query の新機能と最適化

はじめに

こんにちは!テックタッチでフロントエンドエンジニアをしている tsune です。野球好きの自分は、この時期になるとプロ野球開幕が待ち遠しくなってきます⚾ 開幕戦のチケットも確保したので、当日は会社のメンバーと一緒に神宮球場に行く予定です🥳

この記事では 2023 年 10 月に正式リリースされた TanStack Query の v5 へのアップデート内容を紹介します! また、その中でも Optimistic Update (楽観的更新)にまつわる内容を深堀りしていこうと思います🧐

三行まとめ

  • hooks のインターフェースが統一された!
  • Suspense の型レベルでのサポートが追加された!
  • Optimistic Update がよりシンプルに実装できるようになった!

TanStack Query とは?

TanStack Query (旧 React Query) とは、Web 関連の OSS を開発している TanStack から提供されているライブラリの一つです。

名前に Query と付くこともあってかデータフェッチ用のライブラリと思われることがあるようですが、実際は TanStack Query 自体がデータフェッチをすることはなく、非同期で取得したデータの状態管理を効率化するのがこのライブラリの目的です。

テックタッチでは、API や chrome storage などの非同期処理で取得したデータの管理にこのライブラリを利用しています。使用バージョンはまだ v4 なので、頃合いを見て v5 にアップデートしたいと思っています💪

v5 アップデート内容の紹介

アップデート内容から、個人的に気になった内容をかいつまんで紹介します。詳細なアップデート内容については公式アナウンスをご確認ください!

hooks のインターフェースの統一

useQuery や useMutation 等 hooks の引数が object に統一されました。

v4 以前では複数の書き方が可能でしたが、これによって多くのオーバーロード関数が存在していて、メンテナンスコストを増大させていたそうです。

// 引数は queryKey, queryFn, options の 3 つ
// v4: ✅ OK
// v5: 🚨 NG
const useTodo = useQuery(
  ['todo'],
  () => {
    // 何かしらのデータフェッチ処理
  },
  {
    staleTime: 30000,
  }
)

// 引数は queryKey, options の 2 つ
// v4: ✅ OK
// v5: 🚨 NG
const useTodo = useQuery(['todo'], {
  queryFn: () => {
    // 何かしらのデータフェッチ処理
  },
  staleTime: 30000,
})

// 引数は options のみ
// v4: ✅ OK
// v5: ✅ OK
const useTodo = useQuery({
  queryKey: ['todo'],
  queryFn: () => {
    // 何かしらのデータフェッチ処理
  },
  staleTime: 30000,
})

TypeScript 4.7 のアップデートによってインターフェースの統一が可能になり、これらのオーバーロード関数を削除することが出来たとのこと🎉 この Breaking Change に関しては codemod が提供されていました。

また、テックタッチでは公式の ESLint ルールを利用することで、インターフェースの統一を先んじて実施しています。

Suspense の正式サポート

v4 でも Suspense はサポートされていましたが、型レベルで Suspense の恩恵を受けられるようになりました🎉

v4

// options で suspense を有効化出来る
const useTodo = useQuery({
  queryKey: ['todo'],
  queryFn: () => {
    // 何かしらのデータフェッチ処理
  },
  {
    suspense: true
  }
})

// Suspense はサポートされるが、
// data の型は undefined も取るのでコンポーネント側でチェックが必要😭
const SomehingComponent = () => {
  const { data } = useTodo()
  //      ^ Data | undefined

  if (!data) {
    <p>loading...</p>
  }

  return (
    ...
  )
}

v5

// Suspense 専用の hook が登場
const useTodo = useSuspenseQuery({
  queryKey: ['todo'],
  queryFn: () => {
    // 何かしらのデータフェッチ処理
  },
})

// data の型から undefined が消えるのでコンポーネント側でのチェックが不要🥳
const SomehingComponent = () => {
  const { data } = useTodo()
  //      ^ Data

  return (
    ...
  )
}

Optimistic Update をシンプルに

useMutation から variables という変数が返されるようになり、これを使うことで Optimistic Update のロジックをシンプルに実装できるようになります🎉

これについては次のセクションで詳しく見ていきます!

そもそも Optimistic Update とは?

Optimistic Update とは、非同期処理(主に API 呼び出し)が成功することを前提に、操作を即座に UI に反映する手法です。API のレスポンスを待たないことにより、ユーザーへのフィードバック速度を高めることが出来ます。

よく例に挙げられるのは X や Instagram のいいねボタンで、いいねボタンを押したときのハートのインタラクションは、API のレスポンスを待たずに実行されています。

デモアプリ

Optimistic Update を使う場合と使わない場合でユーザーの体験がどのように変わるのか、以下にデモを作成しました。ラジオボタンを変更するとその更新のための API リクエストが飛び、1 秒後にレスポンスが返ってくるという設定です。成功時と失敗時の違いをわかりやすくするためにランダムで失敗するようにしています。

実際の API リクエストは基本的に成功することを踏まえると、Optimistic Update を利用したほうがユーザーの操作が即座に UI に反映されて、違和感がないことがわかるかと思います。

(わかりやすいように次のリクエストが成功するか失敗するかをあらかじめ表示しています。)

stackblitz.com

Optimistic Update の実装例

上記のサンプルを実装する際に、v4 以前と v5 でどのような変化があるか見てみます。

直接関わるロジック部分を抜粋して載せています。

v4

const Component = () => {
  // 更新中の値を保持するための state
  const [newValue, setNewValue] = useState('')
  
  // API の実行後に、成否にかかわらず更新中の値をリセットする
  const onSettled = () => {
    setNewValue('')
  }

  // useMutation をラップしている hook
  const { mutate: changeRadio, isError } = useChangeRadio({
    onSettled,
  })
  
  // useQuery をラップしている hook
  const { data: valueFromAPi } = useRadio()

  // 実際に画面に表示する値
  // エラーが発生したか更新中の値がない場合は API から取得した値を表示
  const displayedValue = isError || !newValue ? valueFromApi : newValue

  return (
    // render component
  )
}

v5

v5 では、更新対象の値として useMutation から variables を受け取れるようになりました。

const Component = () => {
  // useMutation をラップしている hook
  // v5 から追加された variables が更新中の値を保持している
  const { mutate: changeRadio, variables, isError } = useChangeRadio({})
  
  // useQuery をラップしている hook
  const { data: valueFromApi } = useRadio()

  // 実際に画面に表示する値
  // エラーが発生したか更新中の値がない場合は API から取得した値を表示
  const displayedValue = (isError || !variables) ? valueFromApi : variables

  return (
    // render component
  )
}

v4 と比較してみると、v5 では更新中の値の state や手続き的なコードが消えて、ロジックがスッキリしたことが見て取れます!

まとめ

以上、TanStack Query v5 の新機能とその中でも Optimistic Update に関わる変更を詳しく見ていきました。

今回は詳しく取り上げませんでしたが、useQuery からの callback 削除とそれについての開発者の見解も面白い内容だったので、ぜひご一読ください👀

僕からは以上!

参考資料