[react] React 使用 RTK Query 來獲取 API 資料
安裝
npm install react-redux @reduxjs/toolkit
資料夾與檔案結構
src
| App.js
| index.js
| ...
|
└─── components
│ │ Post.js
| | TestAPI.js
│
└─── store
│ │ index.js
│ │
│ └─── api
│ │ │ apiSlice.js
說明
在 這篇文章 的最後有提到,如果要使用 Redux Toolkit 的 slice 實作 async await 去取得 API 資料的話,會需要自訂一個 action creator,在 action creator 裡面還需要使用到 dispatch,這是一件不太方便的事情。
所以 Redux Toolkit 有提供一套解決方法給我們使用,叫做 Redux Toolkit Query,以下簡稱 RTK Query。
RTK Query 是專門用來取得 API 資料的一個套件,它提供了以下幾個優點:
- 能夠追蹤 API 的載入狀態,以便顯示 Loading 畫面。
- 要是兩個以上的 Component 呼叫了同樣的 API,並不會發送兩次 Request 出去,只會送一次 Request。
- 在使用者與畫面互動時,能夠將取得到的 API 資料使用 cache 暫存,以此保持良好的使用者體驗。
ApiProvider
先介紹第一種 RTK Query 的使用方法,要使用 RTK Query 一樣要引入 Provider 包住我們的整個 React Application,而 RTK 有提供另外一個 Provider 給 RTK Query 使用,叫做 ApiProvider。
ApiProvder 不能夠與 store 的 Provider 一同使用,會有問題,官方也有特別說明這點。
Using ApiProvider together with an existing Redux store will cause them to conflict with each other.
import "./App.css";
import { Provider } from "react-redux";
import store from "./store";
import { ApiProvider } from "@reduxjs/toolkit/dist/query/react";
function App() {
return (
//錯誤的程式碼,ApiProvider 不能夠與 Provide r一同使用。
<ApiProvider>
<Provider store={store}>
<div className="App">...</div>
</Provider>
</ApiProvider>
);
}
export default App;
但這是有解決辦法的,我們待會介紹到第二種使用方法的時候會說明。
現在來使用 RTK Query 撰寫抓取 API 資料的程式碼吧,範例程式碼會使用 JSONPlaceholder 提供的 posts API 來實作 RTK Query。
https://jsonplaceholder.typicode.com/posts
先使用 createApi 來建立我們的 API 服務,在 createApi 內需要填入 3 個參數:
- reducerPath : reducer 的 path 名稱,通常會設定跟 API 服務一樣的名稱。
- baseQuery : 使用 fetchBaseQuery 後,可以設定 API 的 baseUrl。
- endPoints : 定義要呼叫的 API Function 名稱,而 RTK Query 會自動將裡面定義的 Function 處理成 Hook 讓我們去使用。
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
export const postsApi = createApi({
reducerPath: "postsApi",
baseQuery: fetchBaseQuery({
baseUrl: "https://jsonplaceholder.typicode.com/",
}),
endpoints: (builder) => ({
getAllPosts: builder.query({
/* 因為有設定baseUrl的關係,不用填寫完整的 API 網址。
當呼叫該Function時,是去 https://jsonplaceholder.typicode.com/posts 取得資料 */
query: () => "posts", // https://jsonplaceholder.typicode.com/posts
}),
}),
});
// RTK Query 會自動將 endPoints 內定義的 Function 輸出成 Hook。
// 格式為 use + 定義的 Function 名稱 + Query
export const { useGetAllPostsQuery } = postsApi;
使用 query 發送的是 GET Request,如果要發送 Post 的話要更改為 Mutation。
接著在 App.js 去引入 ApiProvider,而 ApiProvider 還需要提供一個 prop,叫做 api,這邊就直接將我們剛剛定義的 API 服務傳入即可。
Post 和 TestAPI 為待會測試 RTK Query 是否能夠成功執行的 Component。
import "./App.css";
import Post from "./components/Post";
import TestAPI from "./components/TestAPI";
import { ApiProvider } from "@reduxjs/toolkit/dist/query/react";
import { postsApi } from "./store/api/apiSlice";
function App() {
return (
//錯誤的程式碼,ApiProvider不能夠與Provider一同使用。
<ApiProvider api={postsApi}>
<div className="App">
<Post />
<TestAPI />
</div>
</ApiProvider>
);
}
export default App;
現在來 Post Component 內測試是否能使用 RTK Query 去獲取 JSONPlaceholder 的 API 資料。
只要將 RTK Query 提供給我們的 Hook 引入並直接使用即可。
import React from "react";
import { useGetAllPostsQuery } from "../store/api/apiSlice";
const Post = () => {
//將資料解構出來
const { data, isLoading } = useGetAllPostsQuery();
//判斷是否正在載入API資料
if (isLoading) {
return <h1>Loading...</h1>;
}
console.log("Posts", data);
return <h1>Post</h1>;
};
export default Post;
接著打開 console,確認一下是否有取得到 API 資料。
可以看到我們成功使用 RTK Query 取得到 API 資料了。
在 TestAPI Component 也去使用 useGetAllPostsQuery Hook。
import React from "react";
import { useGetAllPostsQuery } from "../store/api/apiSlice";
const TestAPI = () => {
//將資料解構出來
const { data, isLoading } = useGetAllPostsQuery();
//判斷是否正在載入API資料
if (isLoading) {
return <h1>Loading...</h1>;
}
console.log("TestAPI", data);
return <h1>TestAPI</h1>;
};
export default TestAPI;
在正常的情況下,我們在兩個 Component 都去呼叫了 JSONPlaceholder 的 API,所以應該會發送兩次的 Request 出去。
但實際上我們只發送了一次 Request 就取得了資料,這也是前面提到的 RTK Query 的優點,要是多個 Component 發送請求給同一個 API,RTK Query 只會發送一次 Request。
Provider
而前面有提到,store 使用的 Provider 並不能與 ApiProvider 一同使用,這邊官方有提供相對應的解決方法,只要在 configureStore 內去定義 API 服務的 reducerPath 和 reducer 即可。
import { configureStore } from "@reduxjs/toolkit";
import { postsApi } from "./api/apiSlice";
const store = configureStore({
reducer: {
[postsApi.reducerPath]: postsApi.reducer,
// ...其他slice
},
//使用 api middleware 可以啟用 caching、invalidation、polling 功能。
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(postsApi.middleware),
});
export default store;
import "./App.css";
import Post from "./components/Post";
import TestAPI from "./components/TestAPI";
import { Provider } from "react-redux";
import store from "./store";
// import { ApiProvider } from "@reduxjs/toolkit/dist/query/react";
// import { postsApi } from "./store/api/apiSlice";
function App() {
return (
<Provider store={store}>
<div className="App">
<Post />
<TestAPI />
</div>
</Provider>
);
}
export default App;
現在再次回到網頁重新整理,一樣可以看到我們的 API 資料有成功的取回。
補充
如果 API 需要帶入參數來取得資料的話,可以使用以下的做法:
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
export const postsApi = createApi({
reducerPath: "postsApi",
baseQuery: fetchBaseQuery({
baseUrl: "https://jsonplaceholder.typicode.com/",
}),
endpoints: (builder) => ({
getAllPosts: builder.query({
query: () => "posts", // https://jsonplaceholder.typicode.com/posts
}),
getPostById: builder.query({
query: (id) => `posts?id=${id}`, // https://jsonplaceholder.typicode.com/posts?id=1
}),
}),
});
export const { useGetAllPostsQuery, useGetPostByIdQuery } = postsApi;
import React from "react";
import { useGetPostByIdQuery } from "../store/api/apiSlice";
const Post = () => {
//取得id為1的文章
const { data, isLoading } = useGetPostByIdQuery(1);
if (isLoading) {
return <h1>Loading...</h1>;
}
console.log("Posts", data);
return <h1>Post</h1>;
};
export default Post;