今天要來介紹一個 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