从零开始构建项目

Note

如果您选择使用示例或克隆项目快速开始,可以跳过本指南,直接转到学习基础知识指南。

所以您想要从零开始构建一个 TanStack Start 项目?

本指南将帮助您构建一个非常基础的 TanStack Start Web 应用程序。我们将一起使用 TanStack Start 来:

  • 提供一个索引页面...
  • 显示一个计数器...
  • 带有一个按钮来持久地增加计数器。

这就是它的样子

让我们创建一个新的项目目录并初始化它。

shell
mkdir myApp
cd myApp
npm init -y
mkdir myApp
cd myApp
npm init -y

Note

> 我们在所有这些示例中使用 npm,但您可以使用您选择的包管理器。

TypeScript 配置

我们强烈建议在 TanStack Start 中使用 TypeScript。创建一个 tsconfig.json 文件,至少包含以下设置:

json
{
  "compilerOptions": {
    "jsx": "react-jsx",
    "moduleResolution": "Bundler",
    "module": "ESNext",
    "target": "ES2022",
    "skipLibCheck": true,
    "strictNullChecks": true
  }
}
{
  "compilerOptions": {
    "jsx": "react-jsx",
    "moduleResolution": "Bundler",
    "module": "ESNext",
    "target": "ES2022",
    "skipLibCheck": true,
    "strictNullChecks": true
  }
}

Note

> 启用 verbatimModuleSyntax 可能导致服务器包泄漏到客户端包中。建议保持此选项禁用。

安装依赖

TanStack Start 由 ViteTanStack Router 提供支持,需要它们作为依赖项。

要安装它们,请运行:

shell
npm i @tanstack/react-start @tanstack/react-router vite
npm i @tanstack/react-start @tanstack/react-router vite

您还需要 React:

shell
npm i react react-dom
npm i react react-dom

以及一些 TypeScript:

shell
npm i -D typescript @types/react @types/react-dom vite-tsconfig-paths
npm i -D typescript @types/react @types/react-dom vite-tsconfig-paths

更新配置文件

然后我们将更新 package.json 以使用 Vite 的 CLI 并设置 "type": "module"

json
{
  // ...
  "type": "module",
  "scripts": {
    "dev": "vite dev",
    "build": "vite build"
  }
}
{
  // ...
  "type": "module",
  "scripts": {
    "dev": "vite dev",
    "build": "vite build"
  }
}

��后在 vite.config.ts 中配置 TanStack Start 的 Vite 插件:

ts
// vite.config.ts
import { defineConfig } from 'vite'
import tsConfigPaths from 'vite-tsconfig-paths'
import { tanstackStart } from '@tanstack/react-start/plugin/vite'

export default defineConfig({
  server: {
    port: 3000,
  },
  plugins: [tsConfigPaths(), tanstackStart()],
})
// vite.config.ts
import { defineConfig } from 'vite'
import tsConfigPaths from 'vite-tsconfig-paths'
import { tanstackStart } from '@tanstack/react-start/plugin/vite'

export default defineConfig({
  server: {
    port: 3000,
  },
  plugins: [tsConfigPaths(), tanstackStart()],
})

添加基本模板

TanStack Start 使用需要 2 个必需文件:

  1. 路由器配置
  2. 应用程序的根

配置完成后,我们将拥有如下所示的文件树:

.
├── src/
│   ├── routes/
│   │   └── `__root.tsx`
│   ├── `router.tsx`
│   ├── `routeTree.gen.ts`
├── `.gitignore`
├── `vite.config.ts`
├── `package.json`
└── `tsconfig.json`
.
├── src/
│   ├── routes/
│   │   └── `__root.tsx`
│   ├── `router.tsx`
│   ├── `routeTree.gen.ts`
├── `.gitignore`
├── `vite.config.ts`
├── `package.json`
└── `tsconfig.json`

路由器配置

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

Note

您还没有 routeTree.gen.ts 文件。此文件将在您首次运行 TanStack Start 时生成。

tsx
// 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>
  }
}

应用程序的根

最后,我们需要创建应用程序的根。这是所有其他路由的入口点。此文件中的代码将包装应用程序中的所有其他路由。

tsx
// src/routes/__root.tsx
/// <reference types="vite/client" />
import type { ReactNode } from 'react'
import {
  Outlet,
  createRootRoute,
  HeadContent,
  Scripts,
} from '@tanstack/react-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 (
    <RootDocument>
      <Outlet />
    </RootDocument>
  )
}

function RootDocument({ children }: Readonly<{ children: ReactNode }>) {
  return (
    <html>
      <head>
        <HeadContent />
      </head>
      <body>
        {children}
        <Scripts />
      </body>
    </html>
  )
}
// src/routes/__root.tsx
/// <reference types="vite/client" />
import type { ReactNode } from 'react'
import {
  Outlet,
  createRootRoute,
  HeadContent,
  Scripts,
} from '@tanstack/react-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 (
    <RootDocument>
      <Outlet />
    </RootDocument>
  )
}

function RootDocument({ children }: Readonly<{ children: ReactNode }>) {
  return (
    <html>
      <head>
        <HeadContent />
      </head>
      <body>
        {children}
        <Scripts />
      </body>
    </html>
  )
}

编写您的第一个路由

现在我们已经设置了基本模板,我们可以编写第一个路由。这是通过在 src/routes 目录中创建一个新文件来完成的。

tsx
// 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 项目并编写了您的第一个路由。🎉

您现在可以运行 npm run dev 来启动服务器并导航到 http://localhost:3000 来查看您的路由运行情况。

您想要部署您的应用程序?查看托管指南