本指南将帮助您了解 TanStack Start 的基本原理,无论您如何设置项目。
TanStack Start 由 Vite 和 TanStack Router 提供支持。
这是将决定 Start 中使用的 TanStack Router 行为的文件。在这里,您可以配置从默认的预加载功能到缓存陈旧性的所有内容。
// src/router.tsx
import { createRouter as createTanStackRouter } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen'
export function createRouter() {
const router = createTanStackRouter({
routeTree,
scrollRestoration: true,
})
return router
}
declare module '@tanstack/react-router' {
interface Register {
router: ReturnType<typeof createRouter>
}
}
// src/router.tsx
import { createRouter as createTanStackRouter } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen'
export function createRouter() {
const router = createTanStackRouter({
routeTree,
scrollRestoration: true,
})
return router
}
declare module '@tanstack/react-router' {
interface Register {
router: ReturnType<typeof createRouter>
}
}
当您第一次运行 TanStack Start(通过 npm run dev 或 npm run start)时,会生成 routeTree.gen.ts 文件。此文件包含生成的路由树和一些 TS 工具,使 TanStack Start 具有完整的类型安全。
注意
服务器入口点是开箱即用的可选功能。如果未提供,TanStack Start 将自动为您处理服务器入口点,并使用以下内容作为默认值。
这是通过 src/server.ts 文件完成的
// src/server.ts
import {
createStartHandler,
defaultStreamHandler,
} from '@tanstack/react-start/server'
import { createRouter } from './router'
export default createStartHandler({
createRouter,
})(defaultStreamHandler)
// src/server.ts
import {
createStartHandler,
defaultStreamHandler,
} from '@tanstack/react-start/server'
import { createRouter } from './router'
export default createStartHandler({
createRouter,
})(defaultStreamHandler)
无论我们是静态生成应用程序还是动态提供应用程序,server.ts 文件都是执行所有 SSR 相关工作的入口点。
注意
客户端入口点是开箱即用的可选功能。如果未提供,TanStack Start 将自动为您处理客户端入口点,并使用以下内容作为默认值。
将 HTML 发送到客户端只成功了一半。一旦到达那里,我们需要在路由解析到客户端后,对客户端 JavaScript 进行水合。我们通过使用 StartClient 组件对应用程序的根进行水合来实现这一点
// src/client.tsx
import { StartClient } from '@tanstack/react-start'
import { StrictMode } from 'react'
import { hydrateRoot } from 'react-dom/client'
import { createRouter } from './router'
const router = createRouter()
hydrateRoot(
document,
<StrictMode>
<StartClient router={router} />
</StrictMode>,
)
// src/client.tsx
import { StartClient } from '@tanstack/react-start'
import { StrictMode } from 'react'
import { hydrateRoot } from 'react-dom/client'
import { createRouter } from './router'
const router = createRouter()
hydrateRoot(
document,
<StrictMode>
<StartClient router={router} />
</StrictMode>,
)
这使我们能够在用户的初始服务器请求完成后启动客户端路由。
除了客户端入口点(默认可选)之外,应用程序的 __root 路由是您应用程序的入口点。此文件中的代码将包装应用程序中的所有其他路由,包括您的主页。它的行为就像您的整个应用程序的无路径布局路由。
因为它总是被渲染,所以它是构建应用程序外壳并处理任何全局逻辑的理想场所。
// src/routes/__root.tsx
import {
Outlet,
createRootRoute,
HeadContent,
Scripts,
} from '@tanstack/react-router'
import type { ReactNode } from 'react'
export const Route = createRootRoute({
head: () => ({
meta: [
{
charSet: 'utf-8',
},
{
name: 'viewport',
content: 'width=device-width, initial-scale=1',
},
{
title: 'TanStack Start Starter',
},
],
}),
component: RootComponent,
})
function RootComponent() {
return (
<RootDocument>
<Outlet />
</RootDocument>
)
}
function RootDocument({ children }: Readonly<{ children: ReactNode }>) {
return (
<html>
<head>
<HeadContent />
</head>
<body>
{children}
<Scripts />
</body>
</html>
)
}
// src/routes/__root.tsx
import {
Outlet,
createRootRoute,
HeadContent,
Scripts,
} from '@tanstack/react-router'
import type { ReactNode } from 'react'
export const Route = createRootRoute({
head: () => ({
meta: [
{
charSet: 'utf-8',
},
{
name: 'viewport',
content: 'width=device-width, initial-scale=1',
},
{
title: 'TanStack Start Starter',
},
],
}),
component: RootComponent,
})
function RootComponent() {
return (
<RootDocument>
<Outlet />
</RootDocument>
)
}
function RootDocument({ children }: Readonly<{ children: ReactNode }>) {
return (
<html>
<head>
<HeadContent />
</head>
<body>
{children}
<Scripts />
</body>
</html>
)
}
路由是 TanStack Router 的一项重要功能,在路由指南中已详细介绍。总结如下:
// src/routes/index.tsx
import * as fs from 'node:fs'
import { createFileRoute, useRouter } from '@tanstack/react-router'
import { createServerFn } from '@tanstack/react-start'
const filePath = 'count.txt'
async function readCount() {
return parseInt(
await fs.promises.readFile(filePath, 'utf-8').catch(() => '0'),
)
}
const getCount = createServerFn({
method: 'GET',
}).handler(() => {
return readCount()
})
const updateCount = createServerFn({ method: 'POST' })
.validator((d: number) => d)
.handler(async ({ data }) => {
const count = await readCount()
await fs.promises.writeFile(filePath, `${count + data}`)
})
export const Route = createFileRoute('/')({
component: Home,
loader: async () => await getCount(),
})
function Home() {
const router = useRouter()
const state = Route.useLoaderData()
return (
<button
type="button"
onClick={() => {
updateCount({ data: 1 }).then(() => {
router.invalidate()
})
}}
>
Add 1 to {state}?
</button>
)
}
// src/routes/index.tsx
import * as fs from 'node:fs'
import { createFileRoute, useRouter } from '@tanstack/react-router'
import { createServerFn } from '@tanstack/react-start'
const filePath = 'count.txt'
async function readCount() {
return parseInt(
await fs.promises.readFile(filePath, 'utf-8').catch(() => '0'),
)
}
const getCount = createServerFn({
method: 'GET',
}).handler(() => {
return readCount()
})
const updateCount = createServerFn({ method: 'POST' })
.validator((d: number) => d)
.handler(async ({ data }) => {
const count = await readCount()
await fs.promises.writeFile(filePath, `${count + data}`)
})
export const Route = createFileRoute('/')({
component: Home,
loader: async () => await getCount(),
})
function Home() {
const router = useRouter()
const state = Route.useLoaderData()
return (
<button
type="button"
onClick={() => {
updateCount({ data: 1 }).then(() => {
router.invalidate()
})
}}
>
Add 1 to {state}?
</button>
)
}
TanStack Start 完全基于 TanStack Router 构建,因此 TanStack Router 的所有导航功能都可供您使用。总结如下:
这是一个快速示例,演示如何使用 Link 组件导航到新路由
import { Link } from '@tanstack/react-router'
function Home() {
return <Link to="/about">About</Link>
}
import { Link } from '@tanstack/react-router'
function Home() {
return <Link to="/about">About</Link>
}
有关导航的更深入信息,请查阅导航指南。
您可能已经注意到我们上面使用 createServerFn 创建的服务器函数。这是 TanStack 最强大的功能之一,它允许您创建可以从服务器(SSR 期间)和客户端调用的服务器端函数!
以下是服务器函数工作原理的快速概述
这是一个快速示例,演示如何使用服务器函数从服务器获取并返回数据
import { createServerFn } from '@tanstack/react-start'
import * as fs from 'node:fs'
import { z } from 'zod'
const getUserById = createServerFn({ method: 'GET' })
// Always validate data sent to the function, here we use Zod
.validator(z.string())
// The handler function is where you perform the server-side logic
.handler(async ({ data }) => {
return db.query.users.findFirst({ where: eq(users.id, data) })
})
// Somewhere else in your application
const user = await getUserById({ data: '1' })
import { createServerFn } from '@tanstack/react-start'
import * as fs from 'node:fs'
import { z } from 'zod'
const getUserById = createServerFn({ method: 'GET' })
// Always validate data sent to the function, here we use Zod
.validator(z.string())
// The handler function is where you perform the server-side logic
.handler(async ({ data }) => {
return db.query.users.findFirst({ where: eq(users.id, data) })
})
// Somewhere else in your application
const user = await getUserById({ data: '1' })
要了解有关服务器函数的更多信息,请查阅服务器函数指南。
服务器函数也可以用于在服务器上执行突变。这也使用相同的 createServerFn 函数完成,但有一个额外的要求,即您需要使客户端上受突变影响的任何数据失效。
这是一个快速示例,演示如何使用服务器函数在服务器上执行突变并使客户端上的数据失效
import { createServerFn } from '@tanstack/react-start'
import { z } from 'zod'
import { dbUpdateUser } from '...'
const UserSchema = z.object({
id: z.string(),
name: z.string(),
})
export type User = z.infer<typeof UserSchema>
export const updateUser = createServerFn({ method: 'POST' })
.validator(UserSchema)
.handler(({ data }) => dbUpdateUser(data))
// Somewhere else in your application
import { useQueryClient } from '@tanstack/react-query'
import { useRouter } from '@tanstack/react-router'
import { useServerFn } from '@tanstack/react-start'
import { updateUser, type User } from '...'
export function useUpdateUser() {
const router = useRouter()
const queryClient = useQueryClient()
const _updateUser = useServerFn(updateUser)
return useCallback(
async (user: User) => {
const result = await _updateUser({ data: user })
router.invalidate()
queryClient.invalidateQueries({
queryKey: ['users', 'updateUser', user.id],
})
return result
},
[router, queryClient, _updateUser],
)
}
// Somewhere else in your application
import { useUpdateUser } from '...'
function MyComponent() {
const updateUser = useUpdateUser()
const onClick = useCallback(async () => {
await updateUser({ id: '1', name: 'John' })
console.log('Updated user')
}, [updateUser])
return <button onClick={onClick}>Click Me</button>
}
import { createServerFn } from '@tanstack/react-start'
import { z } from 'zod'
import { dbUpdateUser } from '...'
const UserSchema = z.object({
id: z.string(),
name: z.string(),
})
export type User = z.infer<typeof UserSchema>
export const updateUser = createServerFn({ method: 'POST' })
.validator(UserSchema)
.handler(({ data }) => dbUpdateUser(data))
// Somewhere else in your application
import { useQueryClient } from '@tanstack/react-query'
import { useRouter } from '@tanstack/react-router'
import { useServerFn } from '@tanstack/react-start'
import { updateUser, type User } from '...'
export function useUpdateUser() {
const router = useRouter()
const queryClient = useQueryClient()
const _updateUser = useServerFn(updateUser)
return useCallback(
async (user: User) => {
const result = await _updateUser({ data: user })
router.invalidate()
queryClient.invalidateQueries({
queryKey: ['users', 'updateUser', user.id],
})
return result
},
[router, queryClient, _updateUser],
)
}
// Somewhere else in your application
import { useUpdateUser } from '...'
function MyComponent() {
const updateUser = useUpdateUser()
const onClick = useCallback(async () => {
await updateUser({ id: '1', name: 'John' })
console.log('Updated user')
}, [updateUser])
return <button onClick={onClick}>Click Me</button>
}
要了解有关突变的更多信息,请查阅突变指南。
TanStack Router 的另一个强大功能是数据加载。它允许您在渲染之前为 SSR 获取数据并预加载路由数据。这是通过路由的 loader 函数完成的。
以下是数据加载工作原理的快速概述
要了解有关数据加载的更多信息,请查阅数据加载指南。
您的每周 JavaScript 资讯。每周一免费发送给超过 10 万开发者。