본문 바로가기
버전: 11.x

useSubscription()

비공식 베타 번역

이 페이지는 PageTurner AI로 번역되었습니다(베타). 프로젝트 공식 승인을 받지 않았습니다. 오류를 발견하셨나요? 문제 신고 →

useSubscription 훅은 서버의 구독(subscription) 프로시저를 구독하는 데 사용할 수 있습니다.

시그니처

옵션

  • 옵션 설정이 필요하지만 입력값을 전달하지 않으려면 undefined를 전달할 수 있습니다.
  • @tanstack/react-queryskipToken을 전달하면 구독이 일시 중지됩니다.
  • 구독 사용법의 완전한 예시는 SSE 예제를 참고하세요
tsx
interface UseTRPCSubscriptionOptions<TOutput, TError> {
/**
* Called when the subscription is started.
*/
onStarted?: () => void;
/**
* Called when new data is received from the subscription.
*/
onData?: (data: TOutput) => void;
/**
* Called when an **unrecoverable error** occurs and the subscription is stopped.
*/
onError?: (error: TError) => void;
/**
* Called when the subscription is completed on the server.
* The state will transition to `'idle'` with `data: undefined`.
*/
onComplete?: () => void;
/**
* @deprecated Use a `skipToken` from `@tanstack/react-query` instead.
* This will be removed in v12.
*/
enabled?: boolean;
}
tsx
interface UseTRPCSubscriptionOptions<TOutput, TError> {
/**
* Called when the subscription is started.
*/
onStarted?: () => void;
/**
* Called when new data is received from the subscription.
*/
onData?: (data: TOutput) => void;
/**
* Called when an **unrecoverable error** occurs and the subscription is stopped.
*/
onError?: (error: TError) => void;
/**
* Called when the subscription is completed on the server.
* The state will transition to `'idle'` with `data: undefined`.
*/
onComplete?: () => void;
/**
* @deprecated Use a `skipToken` from `@tanstack/react-query` instead.
* This will be removed in v12.
*/
enabled?: boolean;
}

반환 타입

반환 타입은 status를 판별 기준으로 하는 discriminated union입니다:

ts
type TRPCSubscriptionResult<TOutput, TError> =
| TRPCSubscriptionIdleResult<TOutput>
| TRPCSubscriptionConnectingResult<TOutput, TError>
| TRPCSubscriptionPendingResult<TOutput>
| TRPCSubscriptionErrorResult<TOutput, TError>;
 
interface TRPCSubscriptionIdleResult<TOutput> {
/** Subscription is disabled or has ended */
status: 'idle';
data: undefined;
error: null;
reset: () => void;
}
 
interface TRPCSubscriptionConnectingResult<TOutput, TError> {
/** Trying to establish a connection (may have a previous error from a reconnection attempt) */
status: 'connecting';
data: TOutput | undefined;
error: TError | null;
reset: () => void;
}
 
interface TRPCSubscriptionPendingResult<TOutput> {
/** Connected to the server, receiving data */
status: 'pending';
data: TOutput | undefined;
error: null;
reset: () => void;
}
 
interface TRPCSubscriptionErrorResult<TOutput, TError> {
/** An unrecoverable error occurred and the subscription is stopped */
status: 'error';
data: TOutput | undefined;
error: TError;
reset: () => void;
}
ts
type TRPCSubscriptionResult<TOutput, TError> =
| TRPCSubscriptionIdleResult<TOutput>
| TRPCSubscriptionConnectingResult<TOutput, TError>
| TRPCSubscriptionPendingResult<TOutput>
| TRPCSubscriptionErrorResult<TOutput, TError>;
 
interface TRPCSubscriptionIdleResult<TOutput> {
/** Subscription is disabled or has ended */
status: 'idle';
data: undefined;
error: null;
reset: () => void;
}
 
interface TRPCSubscriptionConnectingResult<TOutput, TError> {
/** Trying to establish a connection (may have a previous error from a reconnection attempt) */
status: 'connecting';
data: TOutput | undefined;
error: TError | null;
reset: () => void;
}
 
interface TRPCSubscriptionPendingResult<TOutput> {
/** Connected to the server, receiving data */
status: 'pending';
data: TOutput | undefined;
error: null;
reset: () => void;
}
 
interface TRPCSubscriptionErrorResult<TOutput, TError> {
/** An unrecoverable error occurred and the subscription is stopped */
status: 'error';
data: TOutput | undefined;
error: TError;
reset: () => void;
}

예제 프로시저

server/routers/_app.ts
tsx
import EventEmitter, { on } from 'events';
import { initTRPC } from '@trpc/server';
 
export const t = initTRPC.create();
 
type Post = { id: string; title: string };
const ee = new EventEmitter();
 
export const appRouter = t.router({
onPostAdd: t.procedure.subscription(async function* (opts) {
for await (const [data] of on(ee, 'add', {
signal: opts.signal,
})) {
const post = data as Post;
yield post;
}
}),
});
 
export type AppRouter = typeof appRouter;
server/routers/_app.ts
tsx
import EventEmitter, { on } from 'events';
import { initTRPC } from '@trpc/server';
 
export const t = initTRPC.create();
 
type Post = { id: string; title: string };
const ee = new EventEmitter();
 
export const appRouter = t.router({
onPostAdd: t.procedure.subscription(async function* (opts) {
for await (const [data] of on(ee, 'add', {
signal: opts.signal,
})) {
const post = data as Post;
yield post;
}
}),
});
 
export type AppRouter = typeof appRouter;

예제 React 컴포넌트

components/PostFeed.tsx
tsx
import { trpc } from '../utils/trpc';
 
type Post = { id: string; title: string };
 
export function PostFeed() {
const [posts, setPosts] = React.useState<Post[]>([]);
 
const subscription = trpc.onPostAdd.useSubscription(undefined, {
onData: (post) => {
setPosts((prev) => [...prev, post]);
},
});
 
return (
<div>
<h1>Live Feed</h1>
{subscription.status === 'connecting' && <p>Connecting...</p>}
{subscription.status === 'error' && (
<div>
<p>Error: {subscription.error.message}</p>
<button onClick={() => subscription.reset()}>Reconnect</button>
</div>
)}
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
);
}
components/PostFeed.tsx
tsx
import { trpc } from '../utils/trpc';
 
type Post = { id: string; title: string };
 
export function PostFeed() {
const [posts, setPosts] = React.useState<Post[]>([]);
 
const subscription = trpc.onPostAdd.useSubscription(undefined, {
onData: (post) => {
setPosts((prev) => [...prev, post]);
},
});
 
return (
<div>
<h1>Live Feed</h1>
{subscription.status === 'connecting' && <p>Connecting...</p>}
{subscription.status === 'error' && (
<div>
<p>Error: {subscription.error.message}</p>
<button onClick={() => subscription.reset()}>Reconnect</button>
</div>
)}
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
);
}