学习基础知识

本指南将帮助您了解 TanStack Start 的基本工作原理,无论您如何设置项目。

依赖项

TanStack Start 由 ViteTanStack Router 提供支持。

  • TanStack Router:用于构建 Web 应用程序的路由库。
  • Vite:用于打包应用程序的构建工具。

一切都从路由“开始”

此文件将决定 TanStack Router 在 Start 中的行为。在这里,您可以配置从默认的 预加载功能缓存过期 的所有内容。

tsx
// app/router.tsx
import { createRouter as createTanStackRouter } from '@tanstack/solid-router'
import { routeTree } from './routeTree.gen'

export function createRouter() {
  const router = createTanStackRouter({
    routeTree,
    scrollRestoration: true,
  })

  return router
}

declare module '@tanstack/solid-router' {
  interface Register {
    router: ReturnType<typeof createRouter>
  }
}
// app/router.tsx
import { createRouter as createTanStackRouter } from '@tanstack/solid-router'
import { routeTree } from './routeTree.gen'

export function createRouter() {
  const router = createTanStackRouter({
    routeTree,
    scrollRestoration: true,
  })

  return router
}

declare module '@tanstack/solid-router' {
  interface Register {
    router: ReturnType<typeof createRouter>
  }
}
  • 请注意 scrollRestoration 属性。此属性用于在路由之间导航时恢复页面的滚动位置。

路由生成

首次运行 TanStack Start(通过 npm run devnpm run start)时,系统会生成 routeTree.gen.ts 文件。此文件包含生成的路由树和一些 TS 实用程序,使 TanStack Start 完全类型安全。

服务器入口点(可选)

注意

开箱即用,服务器入口点是可选的。如果未提供,TanStack Start 将使用以下默认配置自动处理服务器入口点。

这是通过 src/server.ts 文件完成的。

tsx
// src/server.ts
import {
  createStartHandler,
  defaultStreamHandler,
} from '@tanstack/solid-start/server'

import { createRouter } from './router'

export default createStartHandler({
  createRouter,
})(defaultStreamHandler)
// src/server.ts
import {
  createStartHandler,
  defaultStreamHandler,
} from '@tanstack/solid-start/server'

import { createRouter } from './router'

export default createStartHandler({
  createRouter,
})(defaultStreamHandler)

无论我们是静态生成应用程序还是动态提供应用程序,server.ts 文件都是进行所有 SSR 相关工作的入口点。

  • 重要的是,每个请求都必须创建一个新的路由实例。这可确保路由处理的任何数据都是请求特有的。
  • 使用 defaultStreamHandler 函数将应用程序渲染为流,使我们能够利用流式 HTML 向客户端发送内容。(这是默认处理程序,但您也可以使用其他处理程序,如 defaultRenderHandler,甚至可以构建自己的处理程序。)

客户端入口点(可选)

注意

开箱即用,客户端入口点是可选的。如果未提供,TanStack Start 将使用以下默认配置自动处理客户端入口点。

将 HTML 发送到客户端只是成功的一半。一旦到达客户端,我们需要在路由解析到客户端后水合(hydrate)客户端 JavaScript。我们通过使用 StartClient 组件水合应用程序的根来完成此操作。

tsx
// app/client.tsx
import { hydrate } from 'solid-js/web'
import { StartClient } from '@tanstack/solid-start'
import { createRouter } from './router'

const router = createRouter()

hydrate(() => <StartClient router={router} />, document)
// app/client.tsx
import { hydrate } from 'solid-js/web'
import { StartClient } from '@tanstack/solid-start'
import { createRouter } from './router'

const router = createRouter()

hydrate(() => <StartClient router={router} />, document)

这使用户的初始服务器请求得到满足后,即可启动客户端路由。

应用程序的根目录

除了客户端入口点(默认情况下是可选的)之外,应用程序的 __root 路由是应用程序的入口点。此文件中的代码将包装应用程序中的所有其他路由,包括主页。它充当整个应用程序的无路径布局路由。

因为它始终渲染,所以它是构建应用程序外壳和处理任何全局逻辑的理想位置。

tsx
// app/routes/__root.tsx
import {
  Outlet,
  createRootRoute,
  HeadContent,
  Scripts,
} from '@tanstack/solid-router'

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 <Outlet />
}
// app/routes/__root.tsx
import {
  Outlet,
  createRootRoute,
  HeadContent,
  Scripts,
} from '@tanstack/solid-router'

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 <Outlet />
}
  • 随着我们推出 SPA 模式,此布局将来可能会发生变化,该模式允许根路由渲染 SPA 外壳而不包含任何特定页面的内容。
  • 请注意 Scripts 组件。此组件用于加载应用程序的所有客户端 JavaScript。

路由

路由是 TanStack Router 的一个广泛功能,在路由指南中有详细介绍。简而言之:

  • 路由是使用 createFileRoute 函数定义的。
  • 路由会自动进行代码拆分和延迟加载。
  • 关键数据获取是从路由的加载器协调的。
  • 还有更多!
tsx
// app/routes/index.tsx
import * as fs from 'node:fs'
import { createFileRoute, useRouter } from '@tanstack/solid-router'
import { createServerFn } from '@tanstack/solid-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>
  )
}
// app/routes/index.tsx
import * as fs from 'node:fs'
import { createFileRoute, useRouter } from '@tanstack/solid-router'
import { createServerFn } from '@tanstack/solid-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 组件导航到新路由。
  • 使用 useNavigate hook 命令式导航。
  • 在应用程序中的任何位置使用 useRouter hook 来访问路由实例并执行失效操作。
  • 所有返回状态的路由 hook 都是响应式的,这意味着它们会在相关状态更改时自动重新运行。

以下是如何使用 Link 组件导航到新路由的快速示例。

tsx
import { Link } from '@tanstack/solid-router'

function Home() {
  return <Link to="/about">About</Link>
}
import { Link } from '@tanstack/solid-router'

function Home() {
  return <Link to="/about">About</Link>
}

有关导航的更深入信息,请参阅导航指南

服务器函数 (RPC)

您可能已经注意到我们上面使用 createServerFn 创建的服务器函数。这是 TanStack 最强大的功能之一,它允许您创建可以在 SSR 期间从服务器端和从客户端调用的服务器端函数!

以下是服务器函数工作原理的快速概述。

  • 服务器函数是使用 createServerFn 函数创建的。
  • 它们可以从服务器(在 SSR 期间)和客户端调用。
  • 它们可用于从服务器获取数据,或执行其他服务器端操作。

以下是如何使用服务器函数从服务器获取并返回数据的快速示例。

tsx
import { createServerFn } from '@tanstack/solid-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/solid-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' })

要了解有关服务器函数的更多信息,请参阅服务器函数指南

变更

服务器函数也可用于在服务器上执行变异(mutation)。这也是使用相同的 createServerFn 函数完成的,但需要额外要求您使受变异影响的任何客户端数据失效。

  • 如果您仅使用 TanStack Router,则可以使用 router.invalidate() 方法使所有路由数据失效并重新获取。
  • 如果您使用 TanStack Query,则可以使用 queryClient.invalidateQueries() 方法使数据失效,还可以使用其他更具体的方法来定位特定查询。

以下是如何使用服务器函数在服务器上执行变异并在客户端使数据失效的快速示例。

tsx
import { createServerFn } from '@tanstack/solid-start'

const UserSchema = z.object({
  id: z.string(),
  name: z.string(),
})

const updateUser = createServerFn({ method: 'POST' })
  .validator(UserSchema)
  .handler(async ({ data }) => {
    return db
      .update(users)
      .set({ name: data.name })
      .where(eq(users.id, data.id))
  })

// Somewhere else in your application
await updateUser({ data: { id: '1', name: 'John' } })
import { createServerFn } from '@tanstack/solid-start'

const UserSchema = z.object({
  id: z.string(),
  name: z.string(),
})

const updateUser = createServerFn({ method: 'POST' })
  .validator(UserSchema)
  .handler(async ({ data }) => {
    return db
      .update(users)
      .set({ name: data.name })
      .where(eq(users.id, data.id))
  })

// Somewhere else in your application
await updateUser({ data: { id: '1', name: 'John' } })

要了解有关变异的更多信息,请参阅变异指南

数据加载

TanStack Router 的另一个强大功能是数据加载。这允许您为 SSR 获取数据,并在路由渲染之前预加载其数据。这是通过路由的 loader 函数完成的。

以下是数据加载工作原理的快速概述。

  • 数据加载是使用路由的 loader 函数完成的。
  • 数据加载器是同构的,这意味着它们在服务器和客户端上都会执行。
  • 要执行仅服务器逻辑,请在加载器内部调用服务器函数。
  • 与 TanStack Query 类似,数据加载器会在客户端缓存,并在数据过期时在后台重用甚至重新获取。

要了解有关数据加载的更多信息,请参阅数据加载指南

我们的合作伙伴
Code Rabbit
Netlify
Neon
Clerk
Convex
Sentry
Prisma
订阅 Bytes

您的每周 JavaScript 资讯。每周一免费发送给超过 10 万开发者。

Bytes

无垃圾邮件。您可以随时取消订阅。

订阅 Bytes

您的每周 JavaScript 资讯。每周一免费发送给超过 10 万开发者。

Bytes

无垃圾邮件。您可以随时取消订阅。