代码分割与懒加载是一种强大的技术,可显著改善应用的包体积和加载性能。
TanStack Router 将代码分为两类:
关键路由配置 - 渲染当前路由和尽早启动数据加载流程所必需的代码。
非关键/懒加载路由配置 - 路由匹配非必需,可按需加载的代码。
🧠 为何不分割加载器?
加载器本身已是异步边界,若分割则需额外等待代码块加载和加载器执行
相比组件,加载器导致包体积过大的概率更低
加载器是路由最重要的可预加载资源之一(特别是在使用默认预加载行为时,如悬停链接),因此需要确保其可用性而不增加异步开销
若了解分割加载器的弊端后仍希望实施,请参阅数据加载器分割章节。
由于 TanStack Router 基于文件的路由系统支持扁平与嵌套结构,无需额外配置即可将路由文件封装至独立目录。
操作步骤:将路由文件移至同名目录内,并重命名为 route.tsx。
改造前
改造后
TanStack Router 支持多种代码分割方式。若使用基于代码的路由,请跳转至基于代码的分割章节。
使用基于文件的路由时,可选以下方案:
这是最便捷高效的代码分割方式。
启用 autoCodeSplitting 功能后,TanStack Router 会自动根据前述非关键路由配置分割代码。
[!重要] 自动代码分割仅限基于文件的路由,且需配合支持的打包器使用。 不兼容纯 CLI 模式 (@tanstack/router-cli)。
启用方式(在打包器插件配置中添加):
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { TanStackRouterVite } from '@tanstack/router-plugin/vite'
export default defineConfig({
plugins: [
TanStackRouterVite({
// ...
autoCodeSplitting: true,
}),
react(), // 确保该插件位于路由插件之后
],
})
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { TanStackRouterVite } from '@tanstack/router-plugin/vite'
export default defineConfig({
plugins: [
TanStackRouterVite({
// ...
autoCodeSplitting: true,
}),
react(), // 确保该插件位于路由插件之后
],
})
完成!路由文件将自动按关键/非关键配置分割。更多控制选项详见自动代码分割指南。
若无法使用自动分割,可通过 .lazy.tsx 后缀手动分割:将代码移至 .lazy.tsx 文件,并使用 createLazyFileRoute 替代 createFileRoute。
[!重要] > __root.tsx 根路由文件(使用 createRootRoute 或 createRootRouteWithContext)不支持代码分割。
createLazyFileRoute 仅支持以下选项:
导出名称 | 说明 |
---|---|
component | 路由渲染组件 |
errorComponent | 加载错误时渲染的组件 |
pendingComponent | 加载过程中渲染的组件 |
notFoundComponent | 未找到路由时渲染的组件 |
改造前(单文件)
// src/routes/posts.tsx
import { createFileRoute } from '@tanstack/solid-router'
import { fetchPosts } from './api'
export const Route = createFileRoute('/posts')({
loader: fetchPosts,
component: Posts,
})
function Posts() {
// ...
}
// src/routes/posts.tsx
import { createFileRoute } from '@tanstack/solid-router'
import { fetchPosts } from './api'
export const Route = createFileRoute('/posts')({
loader: fetchPosts,
component: Posts,
})
function Posts() {
// ...
}
改造后(拆分为两个文件)
关键路由配置保留在原文件:
// src/routes/posts.tsx
import { createFileRoute } from '@tanstack/solid-router'
import { fetchPosts } from './api'
export const Route = createFileRoute('/posts')({
loader: fetchPosts,
})
// src/routes/posts.tsx
import { createFileRoute } from '@tanstack/solid-router'
import { fetchPosts } from './api'
export const Route = createFileRoute('/posts')({
loader: fetchPosts,
})
非关键配置移至 .lazy.tsx 文件:
// src/routes/posts.lazy.tsx
import { createLazyFileRoute } from '@tanstack/solid-router'
export const Route = createLazyFileRoute('/posts')({
component: Posts,
})
function Posts() {
// ...
}
// src/routes/posts.lazy.tsx
import { createLazyFileRoute } from '@tanstack/solid-router'
export const Route = createLazyFileRoute('/posts')({
component: Posts,
})
function Posts() {
// ...
}
若分割后原路由文件为空,可直接删除该文件!系统将自动生成虚拟路由作为代码分割文件的锚点,该虚拟路由会直接存在于生成的路由树文件中。
改造前(虚拟路由)
// src/routes/posts.tsx
import { createFileRoute } from '@tanstack/solid-router'
export const Route = createFileRoute('/posts')({
// 空文件
})
// src/routes/posts.tsx
import { createFileRoute } from '@tanstack/solid-router'
export const Route = createFileRoute('/posts')({
// 空文件
})
改造后(虚拟路由)
// src/routes/posts.lazy.tsx
import { createLazyFileRoute } from '@tanstack/solid-router'
export const Route = createLazyFileRoute('/posts')({
component: Posts,
})
function Posts() {
// ...
}
// src/routes/posts.lazy.tsx
import { createLazyFileRoute } from '@tanstack/solid-router'
export const Route = createLazyFileRoute('/posts')({
component: Posts,
})
function Posts() {
// ...
}
完成!🎉
使用代码路由时,可通过 Route.lazy() 方法和 createLazyRoute 函数实现分割:
创建懒加载路由:
// src/posts.tsx
export const Route = createLazyRoute('/posts')({
component: MyComponent,
})
function MyComponent() {
return <div>My Component</div>
}
// src/posts.tsx
export const Route = createLazyRoute('/posts')({
component: MyComponent,
})
function MyComponent() {
return <div>My Component</div>
}
在 app.tsx 中通过 .lazy 方法导入:
// src/app.tsx
const postsRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/posts',
}).lazy(() => import('./posts.lazy').then((d) => d.Route))
// src/app.tsx
const postsRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/posts',
}).lazy(() => import('./posts.lazy').then((d) => d.Route))
警告! 分割加载器存在风险。
虽能减小包体积,但代价如代码分割原理章节所述。可通过 lazyFn 实现分割,但会弱化类型安全:
import { lazyFn } from '@tanstack/solid-router'
const route = createRoute({
path: '/my-route',
component: MyComponent,
loader: lazyFn(() => import('./loader'), 'loader'),
})
// 另一文件中
export const loader = async (context: LoaderContext) => {
// ...
}
import { lazyFn } from '@tanstack/solid-router'
const route = createRoute({
path: '/my-route',
component: MyComponent,
loader: lazyFn(() => import('./loader'), 'loader'),
})
// 另一文件中
export const loader = async (context: LoaderContext) => {
// ...
}
基于文件的路由需配合自动代码分割的定制打包选项才能分割加载器。
组件与路由分离时,可使用 getRouteApi 安全访问路由 API:
import { createRoute } from '@tanstack/solid-router'
import { MyComponent } from './MyComponent'
const route = createRoute({
path: '/my-route',
loader: () => ({
foo: 'bar',
}),
component: MyComponent,
})
import { createRoute } from '@tanstack/solid-router'
import { MyComponent } from './MyComponent'
const route = createRoute({
path: '/my-route',
loader: () => ({
foo: 'bar',
}),
component: MyComponent,
})
import { getRouteApi } from '@tanstack/solid-router'
const route = getRouteApi('/my-route')
export function MyComponent() {
const loaderData = route.useLoaderData()
// ^? { foo: string }
return <div>...</div>
}
import { getRouteApi } from '@tanstack/solid-router'
const route = getRouteApi('/my-route')
export function MyComponent() {
const loaderData = route.useLoaderData()
// ^? { foo: string }
return <div>...</div>
}
getRouteApi 支持的类型安全 API:
Your weekly dose of JavaScript news. Delivered every Monday to over 100,000 devs, for free.