De-yu's Note
  • Deyu Notebook
  • Side Project
    • Stock
    • ChatBox
    • SnowCraft
    • ScrollBar
  • 架構問題
    • 解決 RTK Query data 為 undefined 的實務做法
    • 透過 xstate 解決UI 狀態問題
  • 技術觀點
    • 為什麼需要 store
    • Router 作用
    • react 和 next.js 差異
    • monoRepo vs Multiple Repo
  • Performance
    • React 優化
  • JS Coding
    • Curry
    • Debounce
    • Throttle
  • map
  • memo()
  • Promise 實作
  • Promise Function
  • Testing
    • 使用 Jest 、 React Testing Library 、MSW 建立測試環境
  • Miscellaneous
    • Event Loop
    • Browser
    • Code Review
    • Storage
  • AMD 、 CommonJS 、 ES modules
  • JWT
  • Next.js
  • 用過的 module
  • Internet
    • UDP
  • TCP/IP
  • SSL TLS
  • HTTP
  • AI 工作流
    • Page 1
Powered by GitBook
On this page
  • 介紹 RTK Query
  • 用法
  • 問題
  • 解法:使用 select() 搭配 createSelector 統一資料來源
  • 延伸: 建立衍生 selector
  1. 架構問題

解決 RTK Query data 為 undefined 的實務做法

介紹 RTK Query

RTK Query 是由 Redux Toolkit 提供的一個強大資料請求工具,專門設計來簡化 React 應用中與 API 溝通的流程。它內建了:

  • 快取與重新請求

  • 自動處理 loading / error 狀態

  • 快取失效(cache invalidation)

  • 整合 Redux 的狀態管理流程

大幅減少樣板程式碼(boilerplate),讓你能專注在資料如何呈現與處理。

用法

以下是一個簡單的 RTK Query 使用範例,從 API 抓取文章資料並顯示:

import React from 'react';
import { useGetPostsQuery } from './services/api';

const Posts = () => {
  const { data, error, isLoading } = useGetPostsQuery();

  if (isLoading) return <p>Loading...</p>;
  if (error) return <p>Error!</p>;

  return (
    <ul>
      {data?.map((post: any) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
};

注意:在初次載入時,data 會是 undefined,因為請求尚未完成。 取得 data 後進行渲染 並進行快取 並且重新 render 時 若快取存在 則不重新打 API

問題

使用 RTK Query 最常遇到的問題之一就是:

「為什麼我從 hook 拿到的 data 是 undefined?」

這是因為 hook 是非同步執行的,初始渲染時請求尚未完成,自然拿不到資料。這導致幾個實務問題:

  1. 如果未處理 undefined,會導致渲染錯誤或例外。

  2. 資料整理邏輯常與元件混寫,導致維護困難。

  3. 若遵循 Redux 模型,應該將資料選取邏輯抽離至 selector。

解法:使用 select() 搭配 createSelector 統一資料來源

RTK Query 產生的每個 endpoint 都提供 .select() 方法,可以讓你直接從 Redux state 中「選取快取的 API 結果」。

搭配 createSelector 可以:

  • 整合預設資料與 API 資料

  • 把資料轉換邏輯抽出,重複利用

  • 讓元件保持簡潔,專注在 UI 呈現

import { createSelector } from '@reduxjs/toolkit';
import { api } from './services/api';
import type { RootState } from './store';

// 假設有一份 fallback 預設資料(也可以是空陣列)
const selectFallbackPosts = (state: RootState) => state.fallbackPosts;

export const selectPosts = createSelector(
  [
    selectFallbackPosts,
    (state: RootState) => api.endpoints.getPosts.select()(state)?.data,
  ],
  (fallback, apiData) => apiData ?? fallback
);

延伸: 建立衍生 selector

透過這樣的 base selector,其他衍生 selector 就可以重用它,不需要重新取得資料或複製邏輯:

export const selectPostTitles = createSelector(
  selectPosts,
  (posts) => posts.map((p) => p.title)
);

如果 API 有 getPostById 這類接受參數的 endpoint,也可以這樣使用:

(state: RootState) => api.endpoints.getPostById.select(id)(state)?.data 這種寫法有幾個明顯好處:

  • 元件更輕量:只需 useSelector(selectPosts),不處理轉換邏輯

  • 測試更方便:selector 是純函式,可以單元測試

  • 切換資料來源更簡單:只需改 selector 裡的來源,不動元件

  • 易於組織大型應用資料結構

PreviousScrollBarNext透過 xstate 解決UI 狀態問題

Last updated 17 days ago