TanStack Router 暴露的大多数类型都是内部的,可能会发生破坏性变更,并且不总是易于使用。这就是为什么 TanStack Router 有一个专注于易用性的暴露类型子集,旨在供外部使用。这些类型在类型级别提供与 TanStack Router 运行时概念相同的类型安全体验,并且在提供类型检查的位置上具有灵活性
ValidateLinkOptions 对对象字面量类型进行类型检查,以确保它们在推断位置符合 Link 选项。例如,您可能有一个通用的 HeadingLink 组件,它接受 title 属性以及 linkOptions,这个组件的想法是可以重复用于任何导航。
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} />
</>
)
}
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 应该始终为面向公众的签名指定。而 TOptions 应该始终在像 HeadingLink 这样的推断位置使用,以推断 linkOptions 来正确缩小 params 和 search
这样做的结果是,以下代码中的 linkOptions 是完全类型安全的
<HeadingLink title="Posts" linkOptions={{ to: '/posts' }} />
<HeadingLink title="Post" linkOptions={{ to: '/posts/$postId', params: {postId: 'postId'} }} />
<HeadingLink title="Posts" linkOptions={{ to: '/posts' }} />
<HeadingLink title="Post" linkOptions={{ to: '/posts/$postId', params: {postId: 'postId'} }} />
所有导航类型工具都有数组变体。ValidateLinkOptionsArray 启用对 Link 选项数组的类型检查。例如,您可能有一个通用的 Menu 组件,其中每个项目都是一个 Link。
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>
{props.items.map((item) => (
<li>
<Link {...item} />
</li>
))}
</ul>
)
}
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>
{props.items.map((item) => (
<li>
<Link {...item} />
</li>
))}
</ul>
)
}
这当然使得以下 items 属性完全类型安全
<Menu
items={[
{ to: '/posts' },
{ to: '/posts/$postId', params: { postId: 'postId' } },
]}
/>
<Menu
items={[
{ to: '/posts' },
{ to: '/posts/$postId', params: { postId: 'postId' } },
]}
/>
也可以为数组中的每个 Link 选项固定 from。这将允许所有 Menu 项目相对于 from 进行导航。ValidateFromPath 工具可以提供对 from 的额外类型检查
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>
{props.items.map((item) => (
<li>
<Link {...item} from={props.from} />
</li>
))}
</ul>
)
}
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>
{props.items.map((item) => (
<li>
<Link {...item} from={props.from} />
</li>
))}
</ul>
)
}
ValidateLinkOptionsArray 允许您通过提供额外的类型参数来固定 from。结果是一个类型安全的 Link 选项数组,提供相对于 from 的导航
<Menu
from="/posts"
items={[{ to: '.' }, { to: './$postId', params: { postId: 'postId' } }]}
/>
<Menu
from="/posts"
items={[{ to: '.' }, { to: './$postId', params: { postId: 'postId' } }]}
/>
ValidateRedirectOptions 对对象字面量类型进行类型检查,以确保它们在推断位置符合重定向选项。例如,您可能需要一个通用的 fetchOrRedirect 函数,它接受 url 以及 redirectOptions,这个函数的想法是在 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()
}
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' })
fetchOrRedirect('http://example.com/', { to: '/login' })
ValidateNavigateOptions 对对象字面量类型进行类型检查,以确保它们在推断位置符合导航选项。例如,您可能想要编写一个自定义钩子来启用/禁用导航。
export interface UseConditionalNavigateResult {
enable: () => void
disable: () => void
navigate: () => void
}
export function useConditionalNavigate<
TRouter extends RegisteredRouter,
TOptions,
>(
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)
}
},
}
}
export interface UseConditionalNavigateResult {
enable: () => void
disable: () => void
navigate: () => void
}
export function useConditionalNavigate<
TRouter extends RegisteredRouter,
TOptions,
>(
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' },
})
const { enable, disable, navigate } = useConditionalNavigate({
to: '/posts/$postId',
params: { postId: 'postId' },
})