Open7

RTK Query

snamiki1212snamiki1212

RTK Query についてドキュメントや実際に手を使って動かしたりして得た知見をまとめておく

snamiki1212snamiki1212

挙動

  • 初回のfetchではpending => fulfilledなactionとなり、同一query・cache に対してデータ取得を行うとactionはrejected となる(データはcacheから取得できている)
snamiki1212snamiki1212

selector

  • selectorはAPI発火が発生しない
  • そのため事前にAPIからデータ取得をしていないときに、データ取得が空振るるので注意(これでハマったので)
  • 特に、RTK => RTK Query へのリプレイスで単純にselectorへ差し替えていくと発火点が消えてしまうことがあるので。
snamiki1212snamiki1212

cache

  • selector でデータ整形をしたいときにどうやるのがベストなのかがわからん

  • transformでデータを変えてしまうほうがよいかもしれない?

transformResponse(res => {
  const list = res;
  const dict = keyBy(list, "id"); // lodash
  return {list, dict}
})
  • ただ、他のselectorと併合したcreateSelectorが出来ない

  • selectFromResultもあることに気づいた

  • transformResponse vs selectFromResult

  • https://redux.js.org/tutorials/essentials/part-8-rtk-query-advanced#comparing-transformation-approaches

  • transformResponse = すべてのコンポーネントで利用されるようなコアなデータ

  • selectFromResult = 一部のコンポーネントで利用されるようなデータ = アプリケーションレベルのもの

  • reduxのselectorはstateからのselectだが、RTKQではquery resultからのselectにすること

    • つまり、useQuery + selectFromResult
  • useSelectorを使って、stateからのselectも可能ではあるがAPIの発火が行われないので基本的に非推奨

selectFromResult

snamiki1212snamiki1212

RTKでのselector

// selector.ts
const selectCurrentUser = createSelector(
  selectCurrentUserId,
  selectUserDict,
  (id, dict) => dict[id]
);  

// component.ts
const Component = () => {
  const user = useSelector(selectCurrentUser);
  return <div>{user?.name}</div>
}

RTK Query でのselector(hooks)

/**
 * types.ts
 */
type GetUserQueryResponse = { users: User[], dict: Record<string, User> } // useQuery の返り値
type GetUserApiResponse = { users: User[] }; // API の返り値

/**
 * api.ts
 */
export const api = createApi({
  endpoints: (builder) => ({
    getUsers: builder.query<GetUserQueryResponse>({
      // データ整形はtransformResponse にて行っておく
      transformResponse: (res: GetUserApiResponse) => {
        const { users } = res ;
        const dict = keyBy(users, "id") // lodash https://lodash.com/docs/4.17.15#keyBy
        return { users, dict };
      },
    }),
  }),
});
export const { useGetUsers } = api;

/**
 * selector.ts
 * 
 * redux のstateからデータをselectするのではなくて、
 * queryの結果からデータをselectするselectorを作成する
 */
const selectCurrentUserByIdFromData = createSelector(
  (data: GetUserQueryResponse) => data?.dict ?? {},
  (_: unknown, userId: string) => userId,
  (dict, userId) => dict[userId],
);

/**
 * hooks.ts
 */
export const useSelectUser = () => {
  const userId = useSelector(selectCurrenctUserId);
  const { currentUser } = useGetUsersQuery(undefined, {
    selectFromResult: (result) => {
      const currentUser = selectCurrentUserByIdFromData(result.data, userId);
      return { ...result, currentUser };
    },
  });
  return currentUser;
};

RTK Query でのselector(without hooks)

// selector.ts
const selectUserDict = (state: RootState) => api.endpoints.getUsers.select(undefined)(state);
const selectCurrentUser = createSelector(
  selectCurrentUserId,
  selectUserDict,
  (id, dict) => dict[id]
);

// component.ts
const Component = () => {
  const user = useSelector(selectCurrentUser);
  return <div>{user?.name}</div>
}
  • 注意点として、APIは呼ばれないので少なくとも1回はどこかでuseGetUsers()を呼びだしておく必要がある