Still life

web系エンジニャー @yui_tang の備忘録等々

RTK query 1st impression

スターフェスティバル Advent Calendar 2020の19日目です。

びっくりするほど久しぶりの記事で自分でもびっくりちゃいました。

最近は久しぶりにお仕事でアプリケーションのコードを書いてることが多いのですが、主にTS + Next.jsを戦場にしています。


そんな中突然ですが、先日アルファ版がリリースされたRTK Queryについて、簡単に触ってみたのでご紹介したいと思います。

rtk-query-docs.netlify.app

RTK Queryとは

RTK = Redux Toolkitの略で、このライブラリは現在アルファ版ですが最終的にはRedux Toolkitへ組み込まれることを目標としています。
rtk-incubatorというOrganization下で管理されているのですが、まだ作られたばかりのOrganizationのようでRTK Queryに関するリポジトリ以外はまだありません。しかし、RTK Queryのmain authorがredux maintainerであることから、今後はRedux Toolkitのライブラリはここで管理されていくのかもしれません。


本題のRTK Queryですが、簡潔に言うとData fetching & Cachingです。内部的にはReduxが使われています。

React QueryVercelのSWRと今後比較されることになるであろうライブラリです。
RTK Query公式ドキュメントにはSWRが比較対象に含まれていない為、こちらの機能比較がわかりやすいです。
正直まだまだアルファ版のため不足している機能は目立ちますが、既にDocumentも充実していたりExampleを試す限り問題なく動いているレベルで、メジャーバージョンリリースもそう遠くは無いと感じる進捗です。
react-query.tanstack.com

触ってみる

手っ取り早くcreate-react-appしたアプリで、rtk-queryを体験してみます。
今回はFetchだけ。

services/pokemon.ts
import { createApi, fetchBaseQuery } from '@rtk-incubator/rtk-query';

export const pokemonApi = createApi({
  reducerPath: 'pokemonApi',
  baseQuery: fetchBaseQuery({ baseUrl: 'https://pokeapi.co/api/v2/' }),
  endpoints: (builder) => ({
    getPokemonByName: builder.query({
      query: (name: string) => `pokemon/${name}`,
    }),
  }),
});

export const { useGetPokemonByNameQuery } = pokemonApi;

まず、createApiでオプションを渡します。
RTK Queryの他と違う点(外でもこのように書けるかもですが)は、API全体の定義を一箇所に宣言的に定義します。
リクエストやキャッシュ設定を一箇所にまとめることで、動作を明確に出来ることが狙いです。
refs: pokeapi

store.ts
import { configureStore } from '@reduxjs/toolkit';
import { pokemonApi } from './services/pokemon';

export const store = configureStore({
  reducer: {
    [pokemonApi.reducerPath]: pokemonApi.reducer,
  },
  middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(pokemonApi.middleware),
});

createApiで作られたAPIは、Sliceとdata fetching / cachingを行うミドルウェアを生成するので、Storeに追加します。

Component/Pokemon.tsx
import React from "react";
import { useGetPokemonByNameQuery } from "../services/pokemon";

export const pokemons = [
  "bulbasaur",
  "pikachu",
  "ditto",
  "bulbasaur",
  "ditto",
  "pikachu",
] as const;
interface Props {
  key: number;
  name: typeof pokemons[number];
}
export const Pokemon: React.FC<Props> = ({ key, name }) => {
  const { data, error, isLoading } = useGetPokemonByNameQuery(name);
  return (
    <>
      {error ? (
        <>Error occurred!!</>
      ) : isLoading ? (
        <>Wait a moment...</>
      ) : data ? (
        <div key={key}>
          <h3>{data.species.name}</h3>
          <img src={data.sprites.front_shiny} alt={data.species.name} />
        </div>
      ) : null}
    </>
  );
};

serviceでcreateApiしたものをcomponentでhook的に呼ぶだけで、レスポンスをdata, error, isLoadingで受けることが出来ます。
APIリファレンスを読んでもとてもシンプルになっており、リクエストのステータスを簡単にcomponentに反映出来ます。

結果
f:id:yuitang:20201219011016p:plain
画面はこうなります

単純にFetch処理を書いてたら6回リクエストが飛ぶところ、よしなにRTK Queryが同一リクエストをキャッシュしてくれるため、APIリクエストが3回しか行われていないことがわかりますね。

コード:
GitHub - YuiSakamoto/rtk-query-sandbox

その他

簡単なコードで雰囲気をお伝えしましたが、コンセプトに挙げられている一通りの機能は実装済み(一部予定)なので、状況次第では実戦投入も視野に入れられるかも。

  • Mutations
  • Error Handling
  • Conditional Fetching
  • Pagination
  • Polling
  • Prefetching
  • Optimistic Updates
  • Code Splitting

また、冒頭で紹介したrtk-incubator orgでは、もう一つrtk-query-codegenというリポジトリがあります。
宣言的にAPIリクエストをまとめて設定出来る設計を活かし、Open API・Swaggerを介してAPIリクエストコードを生成するためのものですが、まだまだこちらは発展途上のようです。が、今後に期待!!

感想

正直なところ、特筆してこのライブラリが突出している、ということはなさそうという1st impressionでした。
が、選択肢の一つに入る十分な機能を既に備えているライブラリであることは間違いなく、特に既にRedux Toolkitを使ったアプリケーションで導入するとRedux-Thunkで書かれたコードが大分簡潔に書けるようになります。既にこのような状況で開発しているプロダクトは、導入を検討する価値は十分あるなと感じています。

  • Reduxを使ってない => React Query
  • Reduxを使ってる => RTK Query
  • Next.js => SWR

のように、今後使っているライブラリに応じて使い分けることになっていくのかな、と雑に感じています。
140文字以上の文章を書き慣れていないので、今回はここまで。またね😉