今天要來介紹一個 trpc
一個很特的功能 Links
。
Links
他是一個 trpc
中 client
端跟 server
端資料流的方式,link
會被定義在 client
端中,每一個 link
只會做一件事情,他可以是你呼叫 trpc
中對於 websocket
的連線,或是一些 logging
內容。
Link
他在 client
端中會議定義一個 array
叫做 link chain
,在代表著每次 trpc client
都會根據 array
順序依序呼叫 link function
。
這樣說可能讀者會不清楚沒關係我們簡單 demo 一下~
讀者可以以昨天範例當例子,trpc
很貼心的有預設提供一些 link
,例如 loggerLink
用來記錄每次 useQuery
或是 useMutation
的操作。
// ~src/utils/api.ts
import { httpBatchLink, httpLink, loggerLink } from '@trpc/client';
export const api = createTRPCNext<AppRouter>({
config(opts) {
return {
links: [
// loggerLink 在 array 順序中不能大於 httpBatchLink ,下方會說明
loggerLink(),
httpBatchLink({
/**
* If you want to use SSR, you need to use the server's full URL
* @link https://trpc.io/docs/ssr
**/
url: `${getBaseUrl()}/api/trpc`,
// maxURLLength: 2083, // 限制 413 Payload Too Large、414 URI Too Long和404 Not Found
// You can pass any HTTP headers you wish here
async headers() {
return {
// authorization: getAuthCookie(),
};
},
}),
],
queryClient
};
}
});
這時我們新增一比 title5
的資料
然後看 console.log
你會發現 loggerLink
做的事情就是紀錄我們 useQuery
跟 useMutate
的操作,這樣開發就可以知道他 input
內容是什麼,共用 context
有哪些非常方便。
如果你不希望在 prod
中看到 log
你可以透過 enabled
去判斷。
import { createTRPCProxyClient, httpBatchLink, loggerLink } from '@trpc/client';
import type { AppRouter } from '../server';
const client = createTRPCProxyClient<AppRouter>({
links: [
/**
* The function passed to enabled is an example in case you want to the link to
* log to your console in development and only log errors in production
*/
loggerLink({
enabled: (opts) =>
(process.env.NODE_ENV === 'development' &&
typeof window !== 'undefined') ||
(opts.direction === 'down' && opts.result instanceof Error),
}),
httpBatchLink({
url: 'http://localhost:3000',
}),
],
});
這時眼睛很大的小夥伴一定會發現在 link 中有一個預設的 httpBatchLink
這個又是什麼?別及~讓我慢慢介紹~在 link 中有一個特別的 link type 叫做 terminating link
。
terminating link :
terminating link
。terminating link
其次是 httpLink
和wsLink
街遮我們來看 httpBatchLink
到底做什麼事情,httpBatchLink
主要是批次處理所有 trpc client
請求整理成一次的 api request
,有點像是 promise.all
在 client
端一次接收,而 httpBatchLink
則是相反,我們來看例子。
在 Home page
中我們在請求其他的 query
// ~src/pages/index
export default function Home() {
const utils = api.useContext()
const { data: posts, isLoading, isError, error } = api.posts.getPosts.useQuery()
const { data: greetingdata } = api.greeting.useQuery({ name: 'Danny' })
return (
//..
)
}
你會發現 request
只會有一筆
response
結果也幫你整理成 array
了
批次處理的好處是我們可以減少 request
的請求,減少 server
的浪費的浪費,這樣你的 request
就不會有 block
的問題。
蠻有趣的是你可以透過 Promise.all
選擇哪些 request 需要 batch
一起。
``
const somePosts = await Promise.all([
trpc.post.byId.query(1),
trpc.post.byId.query(2),
trpc.post.byId.query(3),
]);
你可能會有疑惑如果我用了 httpBatchLink
那如果我在同一個頁面中假如呼叫 10 個 api 其中一個發生 error
會不會造成全部 api
都抓不到資料?答案是不會~筆者可以簡單給你看 demo
。
我們去找一個不存在的 post
,這時我們看一下 network
// ~src/pages/index
export default function Home() {
const utils = api.useContext()
const { data: posts, isLoading, isError, error } = api.posts.getPosts.useQuery()
const { data: greetingdata } = api.greeting.useQuery({ name: 'Danny' })
const { data: postData } = api.posts.getPost.useQuery({ post_id: 'not_found_id' })
return (
//..
)
}
你會發現只會有特定的 result
有 error
,其他的 query
或是 mutate
都不會有影響。
當然是可以,你可以在 api handler
取消 enable
export default createNextApiHandler({
router: appRouter,
createContext: createTRPCContext,
batching: {
enabled: false,
},
});
或是把 httpBatchLink
改成 httpLink
import type { AppRouter } from '@/server/routers/app';
import { httpLink } from '@trpc/client';
import { createTRPCNext } from '@trpc/next';
export const trpc = createTRPCNext<AppRouter>({
config() {
return {
links: [
httpLink({
url: '/api/trpc',
}),
],
};
},
});
甚至把 httpBatchLink
改成 httpLink
import { httpBatchLink, httpLink, loggerLink } from '@trpc/client';
const client = createTRPCProxyClient<AppRouter>({
links: [
httpLink({
url: 'http://localhost:3000',
}),
],
});
這樣每個 request 就獨立摟~
如果 batch 得大小太大會造成 413 Payload Too Large
、414 URI Too Long
和 404 Not Found
這些 error code 問題,httpBatchLink
中有提供第二個參數 maxURLLength
幫你限制每次 batch
數量已減少 error code 發生。
import { createTRPCProxyClient, httpBatchLink } from '@trpc/client';
import type { AppRouter } from '../server';
const client = createTRPCProxyClient<AppRouter>({
links: [
httpBatchLink({
url: 'http://localhost:3000',
maxURLLength: 2083, // a suitable size
}),
],
});
或是你可以針對某一個 query 不是參與 batch
,這時你可以用,splitLink
根據每個 request context
決定要用 httpBatchLink
或是 httpLink
如果 splitLink
中 return true
則執行 true
的 callback functions
,反之則是 false
import {
createTRPCProxyClient,
httpBatchLink,
httpLink,
splitLink,
} from '@trpc/client';
import type { AppRouter } from '../server';
const url = `http://localhost:3000`;
const client = createTRPCProxyClient<AppRouter>({
links: [
loggerLink({
enabled: (opts) =>
(process.env.NODE_ENV === 'development' &&
typeof window !== 'undefined') ||
(opts.direction === 'down' && opts.result instanceof Error),
}),
splitLink({
condition(op) {
// check for context property `skipBatch`
return op.context.skipBatch === true;
},
// when condition is true, use normal request
true: httpLink({
/**
* If you want to use SSR, you need to use the server's full URL
* @link https://trpc.io/docs/ssr
**/
url: `${getBaseUrl()}/api/trpc`,
// You can pass any HTTP headers you wish here
async headers() {
return {
// authorization: getAuthCookie(),
};
}
}),
// when condition is false, use batching
false: httpBatchLink({
/**
* If you want to use SSR, you need to use the server's full URL
* @link https://trpc.io/docs/ssr
**/
url: `${getBaseUrl()}/api/trpc`,
async headers() {
return {
// authorization: getAuthCookie(),
};
},
// maxURLLength: 2083, // 限制 413 Payload Too Large、414 URI Too Long和404 Not Found
maxURLLength: 2083
}),
}),
],
});
這樣在 client
端修改 context
內容後就可以選擇哪些 request
不需要做 batch
了。
const { data: posts, isLoading, isError, error } = api.posts.getPosts.useQuery(undefined, {
trpc: {
context: {
skipBatch: true,
}
}
})
那如果讀者想自己客製化 link
的話可以去 doc 看看,這邊就不多做說明。
import { TRPCLink } from '@trpc/client';
import { observable } from '@trpc/server/observable';
import type { AppRouter } from 'server/routers/_app';
export const customLink: TRPCLink<AppRouter> = () => {
// here we just got initialized in the app - this happens once per app
// useful for storing cache for instance
return ({ next, op }) => {
// this is when passing the result to the next link
// each link needs to return an observable which propagates results
return observable((observer) => {
console.log('performing operation:', op);
const unsubscribe = next(op).subscribe({
next(value) {
console.log('we received value', value);
observer.next(value);
},
error(err) {
console.log('we received error', err);
observer.error(err);
},
complete() {
observer.complete();
},
});
return unsubscribe;
});
};
};
https://github.com/Danny101201/next_demo/tree/main
✅ 前端社群 :
https://lihi3.cc/kBe0Y