타입 유틸리티
TanStack Router가 노출하는 대부분의 타입은 내부용이며 호환성이 깨지는 변경의 대상이 될 수 있고 항상 사용하기 쉽지는 않습니다. 그래서 TanStack Router에는 외부에서 쉽게 사용할 수 있도록 노출된 타입의 일부가 있습니다. 이러한 타입은 타입 수준에서 TanStack Router의 런타임 개념과 동일한 타입 안전 경험을 제공하며, 타입 검사를 제공할 위치를 유연하게 선택할 수 있습니다.
ValidateLinkOptions로 Link 옵션 타입 검사
ValidateLinkOptions는 타입 추론 위치에서 객체 리터럴 타입이 Link 옵션을 준수하는지 검사합니다. 예를 들어 title prop과 linkOptions를 받는 제네릭 HeadingLink 컴포넌트를 만들면 이 컴포넌트를 모든 탐색에 재사용할 수 있습니다.
React
export interface HeaderLinkProps<
TRouter extends RegisteredRouter = RegisteredRouter,
TOptions = unknown,
> {
title: string
linkOptions: ValidateLinkOptions<TRouter, TOptions>
}
export function HeadingLink<TRouter extends RegisteredRouter, TOptions>(
props: HeaderLinkProps<TRouter, TOptions>,
): React.ReactNode
export function HeadingLink(props: HeaderLinkProps): React.ReactNode {
return (
<>
<h1>{props.title}</h1>
<Link {...props.linkOptions} />
</>
)
}
Solid
export interface HeaderLinkProps<
TRouter extends RegisteredRouter = RegisteredRouter,
TOptions = unknown,
> {
title: string
linkOptions: ValidateLinkOptions<TRouter, TOptions>
}
export function HeadingLink<TRouter extends RegisteredRouter, TOptions>(
props: HeaderLinkProps<TRouter, TOptions>,
): Solid.JSX.Element
export function HeadingLink(props: HeaderLinkProps): Solid.JSX.Element {
return (
<>
<h1>{props.title}</h1>
<Link {...props.linkOptions} />
</>
)
}
HeadingLink의 더 허용적인 오버로드를 사용하면 제네릭 시그니처에서 필요한 타입 단언을 피할 수 있습니다. 타입 매개변수가 없는 더 느슨한 시그니처를 사용하면 HeadingLink 구현에서 타입 단언을 쉽게 피할 수 있습니다.
유틸리티의 모든 타입 매개변수는 선택 사항이지만 최상의 TypeScript 성능을 위해 공개 시그니처에서는 항상 TRouter를 지정해야 합니다. 또한 params와 search를 올바르게 좁히도록 linkOptions를 추론하려면 HeadingLink 같은 타입 추론 위치에서 항상 TOptions를 사용해야 합니다.
그 결과 다음의 linkOptions가 완전히 타입 안전해집니다.
<HeadingLink title="Posts" linkOptions={{ to: '/posts' }} />
<HeadingLink title="Post" linkOptions={{ to: '/posts/$postId', params: {postId: 'postId'} }} />
ValidateLinkOptionsArray로 Link 옵션 배열 타입 검사
모든 탐색 타입 유틸리티에는 배열 변형이 있습니다. ValidateLinkOptionsArray를 사용하면 Link 옵션 배열을 타입 검사할 수 있습니다. 예를 들어 각 항목이 Link인 제네릭 Menu 컴포넌트를 만들 수 있습니다.
React
export interface MenuProps<
TRouter extends RegisteredRouter = RegisteredRouter,
TItems extends ReadonlyArray<unknown> = ReadonlyArray<unknown>,
> {
items: ValidateLinkOptionsArray<TRouter, TItems>
}
export function Menu<
TRouter extends RegisteredRouter = RegisteredRouter,
TItems extends ReadonlyArray<unknown>,
>(props: MenuProps<TRouter, TItems>): React.ReactNode
export function Menu(props: MenuProps): React.ReactNode {
return (
<ul>
{props.items.map((item) => (
<li>
<Link {...item} />
</li>
))}
</ul>
)
}
Solid
import { For } from 'solid-js'
export interface MenuProps<
TRouter extends RegisteredRouter = RegisteredRouter,
TItems extends ReadonlyArray<unknown> = ReadonlyArray<unknown>,
> {
items: ValidateLinkOptionsArray<TRouter, TItems>
}
export function Menu<
TRouter extends RegisteredRouter = RegisteredRouter,
TItems extends ReadonlyArray<unknown>,
>(props: MenuProps<TRouter, TItems>): Solid.JSX.Element
export function Menu(props: MenuProps): Solid.JSX.Element {
return (
<ul>
<For each={props.items}>
{(item) => (
<li>
<Link {...item} />
</li>
)}
</For>
</ul>
)
}
따라서 다음 items prop도 완전히 타입 안전해집니다.
<Menu
items={[
{ to: '/posts' },
{ to: '/posts/$postId', params: { postId: 'postId' } },
]}
/>
배열의 각 Link 옵션에 from을 고정할 수도 있습니다. 이렇게 하면 모든 Menu 항목이 from을 기준으로 상대 탐색을 수행합니다. ValidateFromPath 유틸리티를 사용하면 from을 추가로 타입 검사할 수 있습니다.
React
export interface MenuProps<
TRouter extends RegisteredRouter = RegisteredRouter,
TItems extends ReadonlyArray<unknown> = ReadonlyArray<unknown>,
TFrom extends string = string,
> {
from: ValidateFromPath<TRouter, TFrom>
items: ValidateLinkOptionsArray<TRouter, TItems, TFrom>
}
export function Menu<
TRouter extends RegisteredRouter = RegisteredRouter,
TItems extends ReadonlyArray<unknown>,
TFrom extends string = string,
>(props: MenuProps<TRouter, TItems, TFrom>): React.ReactNode
export function Menu(props: MenuProps): React.ReactNode {
return (
<ul>
{props.items.map((item) => (
<li>
<Link {...item} from={props.from} />
</li>
))}
</ul>
)
}
Solid
import { For } from 'solid-js'
export interface MenuProps<
TRouter extends RegisteredRouter = RegisteredRouter,
TItems extends ReadonlyArray<unknown> = ReadonlyArray<unknown>,
TFrom extends string = string,
> {
from: ValidateFromPath<TRouter, TFrom>
items: ValidateLinkOptionsArray<TRouter, TItems, TFrom>
}
export function Menu<
TRouter extends RegisteredRouter = RegisteredRouter,
TItems extends ReadonlyArray<unknown>,
TFrom extends string = string,
>(props: MenuProps<TRouter, TItems, TFrom>): Solid.JSX.Element
export function Menu(props: MenuProps): Solid.JSX.Element {
return (
<ul>
<For each={props.items}>
{(item) => (
<li>
<Link {...item} from={props.from} />
</li>
)}
</For>
</ul>
)
}
ValidateLinkOptionsArray는 추가 타입 매개변수를 제공해 from을 고정할 수 있게 합니다. 그 결과 from을 기준으로 탐색하는 타입 안전한 Link 옵션 배열을 얻습니다.
<Menu
from="/posts"
items={[{ to: '.' }, { to: './$postId', params: { postId: 'postId' } }]}
/>
ValidateRedirectOptions로 리디렉션 옵션 타입 검사
ValidateRedirectOptions는 타입 추론 위치에서 객체 리터럴 타입이 리디렉션 옵션을 준수하는지 검사합니다. 예를 들어 url과 redirectOptions를 받는 제네릭 fetchOrRedirect 함수가 필요할 수 있으며, 이 함수는 fetch가 실패하면 리디렉션합니다.
export async function fetchOrRedirect<
TRouter extends RegisteredRouter = RegisteredRouter,
TOptions,
>(
url: string,
redirectOptions: ValidateRedirectOptions<TRouter, TOptions>,
): Promise<unknown>
export async function fetchOrRedirect(
url: string,
redirectOptions: ValidateRedirectOptions,
): Promise<unknown> {
const response = await fetch(url)
if (!response.ok && response.status === 401) {
throw redirect(redirectOptions)
}
return await response.json()
}
그 결과 fetchOrRedirect에 전달하는 redirectOptions가 완전히 타입 안전해집니다.
fetchOrRedirect('http://example.com/', { to: '/login' })
ValidateNavigateOptions로 navigate 옵션 타입 검사
ValidateNavigateOptions는 타입 추론 위치에서 객체 리터럴 타입이 navigate 옵션을 준수하는지 검사합니다. 예를 들어 탐색을 활성화하거나 비활성화하는 사용자 지정 훅을 작성할 수 있습니다.
React
export interface UseConditionalNavigateResult {
enable: () => void
disable: () => void
navigate: () => void
}
export function useConditionalNavigate<
TRouter extends RegisteredRouter = RegisteredRouter,
TOptions,
>(
navigateOptions: ValidateNavigateOptions<TRouter, TOptions>,
): UseConditionalNavigateResult
export function useConditionalNavigate(
navigateOptions: ValidateNavigateOptions,
): UseConditionalNavigateResult {
const [enabled, setEnabled] = useState(false)
const navigate = useNavigate()
return {
enable: () => setEnabled(true),
disable: () => setEnabled(false),
navigate: () => {
if (enabled) {
navigate(navigateOptions)
}
},
}
}
Solid
import { createSignal } from 'solid-js'
export interface UseConditionalNavigateResult {
enable: () => void
disable: () => void
navigate: () => void
}
export function useConditionalNavigate<
TRouter extends RegisteredRouter = RegisteredRouter,
TOptions = unknown,
>(
navigateOptions: ValidateNavigateOptions<TRouter, TOptions>,
): UseConditionalNavigateResult
export function useConditionalNavigate(
navigateOptions: ValidateNavigateOptions,
): UseConditionalNavigateResult {
const [enabled, setEnabled] = createSignal(false)
const navigate = useNavigate()
return {
enable: () => setEnabled(true),
disable: () => setEnabled(false),
navigate: () => {
if (enabled()) {
navigate(navigateOptions)
}
},
}
}
그 결과 useConditionalNavigate에 전달하는 navigateOptions가 완전히 타입 안전해지고 React 상태에 따라 탐색을 활성화하거나 비활성화할 수 있습니다.
const { enable, disable, navigate } = useConditionalNavigate({
to: '/posts/$postId',
params: { postId: 'postId' },
})