TanStack Start 是一个全栈框架,用于构建基于 TanStack Router 之上的服务器渲染 React 应用程序。
要设置 TanStack Start 项目,你需要
按照本指南构建一个基础的 TanStack Start Web 应用程序。我们将一起使用 TanStack Start 来
如果你从头开始,请创建一个新项目。
mkdir myApp
cd myApp
npm init -y
mkdir myApp
cd myApp
npm init -y
创建一个 tsconfig.json 文件,至少包含以下设置
{
"compilerOptions": {
"jsx": "react-jsx",
"moduleResolution": "Bundler",
"module": "Preserve",
"target": "ES2022",
"skipLibCheck": true,
},
}
{
"compilerOptions": {
"jsx": "react-jsx",
"moduleResolution": "Bundler",
"module": "Preserve",
"target": "ES2022",
"skipLibCheck": true,
},
}
TanStack Start 由以下软件包驱动,需要作为依赖项安装
注意
Vinxi 是一个临时依赖项,将被一个简单的 vite 插件或专用的 Start CLI 替换。
要安装它们,运行
npm i @tanstack/react-start @tanstack/react-router vinxi
npm i @tanstack/react-start @tanstack/react-router vinxi
你还需要 React 和 Vite React 插件,所以也安装它们的依赖项
npm i react react-dom @vitejs/plugin-react
npm i react react-dom @vitejs/plugin-react
请为了你、你的开发者伙伴以及你的用户的利益,使用 TypeScript
npm i -D typescript @types/react @types/react-dom
npm i -D typescript @types/react @types/react-dom
然后我们将更新我们的 package.json 以使用 Vinxi 的 CLI 并设置 "type": "module"
{
// ...
"type": "module",
"scripts": {
"dev": "vinxi dev",
"build": "vinxi build",
"start": "vinxi start",
},
}
{
// ...
"type": "module",
"scripts": {
"dev": "vinxi dev",
"build": "vinxi build",
"start": "vinxi start",
},
}
然后配置 TanStack Start 的 app.config.ts 文件
// app.config.ts
import { defineConfig } from '@tanstack/react-start/config'
export default defineConfig({})
// app.config.ts
import { defineConfig } from '@tanstack/react-start/config'
export default defineConfig({})
TanStack Start 使用需要四个必需的文件
配置完成后,我们将拥有如下的文件树结构
.
├── app/
│ ├── routes/
│ │ └── `__root.tsx`
│ ├── `client.tsx`
│ ├── `router.tsx`
│ ├── `routeTree.gen.ts`
│ └── `ssr.tsx`
├── `.gitignore`
├── `app.config.ts`
├── `package.json`
└── `tsconfig.json`
.
├── app/
│ ├── routes/
│ │ └── `__root.tsx`
│ ├── `client.tsx`
│ ├── `router.tsx`
│ ├── `routeTree.gen.ts`
│ └── `ssr.tsx`
├── `.gitignore`
├── `app.config.ts`
├── `package.json`
└── `tsconfig.json`
这个文件将决定 Start 中使用的 TanStack Router 的行为。在这里,你可以配置从默认的预加载功能到缓存过期时间的所有内容。
// app/router.tsx
import { createRouter as createTanStackRouter } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen'
export function createRouter() {
const router = createTanStackRouter({
routeTree,
})
return router
}
declare module '@tanstack/react-router' {
interface Register {
router: ReturnType<typeof createRouter>
}
}
// app/router.tsx
import { createRouter as createTanStackRouter } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen'
export function createRouter() {
const router = createTanStackRouter({
routeTree,
})
return router
}
declare module '@tanstack/react-router' {
interface Register {
router: ReturnType<typeof createRouter>
}
}
routeTree.gen.ts 不是你现在应该拥有的文件。它将在你第一次运行 TanStack Start (通过 npm run dev 或 npm run start) 时生成。
由于 TanStack Start 是一个 SSR 框架,我们需要将此路由信息管道传输到我们的服务器入口点
// app/ssr.tsx
import {
createStartHandler,
defaultStreamHandler,
} from '@tanstack/react-start/server'
import { getRouterManifest } from '@tanstack/react-start/router-manifest'
import { createRouter } from './router'
export default createStartHandler({
createRouter,
getRouterManifest,
})(defaultStreamHandler)
// app/ssr.tsx
import {
createStartHandler,
defaultStreamHandler,
} from '@tanstack/react-start/server'
import { getRouterManifest } from '@tanstack/react-start/router-manifest'
import { createRouter } from './router'
export default createStartHandler({
createRouter,
getRouterManifest,
})(defaultStreamHandler)
这使我们能够知道当用户访问给定路由时,我们需要执行哪些路由和加载器。
现在我们需要一种方法在路由解析到客户端后,水合我们的客户端 JavaScript。我们通过将相同的路由信息管道传输到我们的客户端入口点来做到这一点
// app/client.tsx
import { hydrateRoot } from 'react-dom/client'
import { StartClient } from '@tanstack/react-start'
import { createRouter } from './router'
const router = createRouter({
scrollRestoration: true,
})
hydrateRoot(document!, <StartClient router={router} />)
// app/client.tsx
import { hydrateRoot } from 'react-dom/client'
import { StartClient } from '@tanstack/react-start'
import { createRouter } from './router'
const router = createRouter({
scrollRestoration: true,
})
hydrateRoot(document!, <StartClient router={router} />)
这使我们能够在用户的初始服务器请求完成后启动客户端路由。
最后,我们需要创建应用的根目录。这是所有其他路由的入口点。此文件中的代码将包裹应用程序中的所有其他路由。
// app/routes/__root.tsx
import { createRootRoute, HeadContent, Scripts } from '@tanstack/react-router'
import { Outlet } from '@tanstack/react-router'
import * as React 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 }: { children: React.ReactNode }) {
return (
<html>
<head>
<HeadContent />
</head>
<body>
{children}
<Scripts />
</body>
</html>
)
}
// app/routes/__root.tsx
import { createRootRoute, HeadContent, Scripts } from '@tanstack/react-router'
import { Outlet } from '@tanstack/react-router'
import * as React 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 }: { children: React.ReactNode }) {
return (
<html>
<head>
<HeadContent />
</head>
<body>
{children}
<Scripts />
</body>
</html>
)
}
现在我们已经完成了基础模板设置,我们可以编写我们的第一个路由。这通过在 app/routes 目录中创建一个新文件来完成。
// app/routes/index.tsx
import * as fs from '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
onClick={() => {
updateCount({ data: 1 }).then(() => {
router.invalidate()
})
}}
>
Add 1 to {state}?
</button>
)
}
// app/routes/index.tsx
import * as fs from '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
onClick={() => {
updateCount({ data: 1 }).then(() => {
router.invalidate()
})
}}
>
Add 1 to {state}?
</button>
)
}
就是这样!🤯 你现在已经设置了一个 TanStack Start 项目并编写了你的第一个路由。🎉
你现在可以运行 npm run dev 来启动你的服务器并导航到 https://127.0.0.1:3000 以查看你的路由运行效果。
你每周的 JavaScript 新闻速递。每周一免费发送给超过 10 万名开发者。