进阶指南
类型体操
模块增强原理深入、DataOf 类型推导链路、条件类型和映射类型、高级类型推导场景与最佳实践
@karinjs/template-react 使用 TypeScript 模块增强实现逐路由精确类型推导。本页深入讲解类型系统原理和高级应用。
模块增强原理
核心机制
框架通过三个步骤实现类型安全:
// 1. 定义可增强的接口
// packages/core/src/registry-types.ts
export interface ProjectRegistry {}
// 2. 约定生成增强文件
// .ktr/registry-types.d.ts
declare module '@karinjs/template-react/registry-types' {
interface ProjectRegistry {
'user/card': typeof import('../ktr/template/user/card/index').default
}
}
// 3. 类型推导使用增强
// packages/core/src/types/index.ts
export type LoadedRegistry = keyof ProjectRegistry extends never ? AnyRegistry : ProjectRegistry为什么使用子路径导出
{
"exports": {
".": {
"types": "./dist/index.d.mts",
"browser": "./dist/client.mjs",
"import": "./dist/index.mjs"
},
"./registry-types": {
"types": "./dist/registry-types.d.mts",
"import": "./dist/registry-types.mjs"
},
"./plugin": {
"types": "./dist/plugin.d.mts",
"import": "./dist/plugin.mjs"
},
"./styles": {
"style": "./styles/index.css",
"default": "./styles/index.css"
},
"./styles/*.css": "./styles/*.css",
"./package.json": "./package.json"
}
}子路径 @karinjs/template-react/registry-types 确保:
- 模块增强可合并:多个
.d.ts增强同一个模块 ID - 打包后路径不变:
import('@karinjs/template-react/registry-types')在源码和产物中都指向同一模块 - 避免循环依赖:registry-types 独立于主入口
类型增强生效流程
// 步骤 1:约定扫描生成注册表
// conventions/registry.ts
const routes = await discoverTemplateRoutes(config.templateDir)
// → ['user/card', 'user/profile']
await generateTypeAugmentation(routes, config.cacheDir)生成的类型文件:
declare module '@karinjs/template-react/registry-types' {
interface ProjectRegistry {
'user/card': typeof import('../ktr/template/user/card/index').
}
}// 步骤 2:类型推导消费增强
// packages/core/src/types/index.ts
import type { ProjectRegistry } from '@karinjs/template-react/registry-types'
export type LoadedRegistry = keyof ProjectRegistry extends never
? AnyRegistry // 未增强:退化为任意路由
: ProjectRegistry // 已增强:精确路由类型// 步骤 3:用户代码享受类型推导
import { loadTemplateRegistry } from '@karinjs/template-react'
const templates = await loadTemplateRegistry({ root: '.' })
// ^?
// templates 类型:LoadedRegistry
// 键名:'user/card' | 'user/profile'(精确联合类型)
// 值类型:TemplateDef<具体数据类型>DataOf 类型推导链路
推导步骤
import { , type , type } from '@karinjs/template-react'
// 1. 定义数据类型
interface UserCardData {
: string
: string
}
// 2. 组件标注 TemplateProps
const = ({ data }: <UserCardData>) => { return <>{.}</>
}
// 3. defineTemplate 自动推导
const = ({
:
})
// 4. DataOf 提取类型
type ExtractedData = <typeof >
// 验证类型一致
const : extends UserCardData ? true : false = true推导链路详解
import type React from 'react'
import type { RenderContext } from '@karinjs/template-react'
// 1. TemplateProps 携带泛型
// ctx 是 RenderContext:scale + 可选 theme(Partial<ThemeContext>),
// 并带 [key: string]: unknown 索引签名透传调用方自定义字段
export interface <> {
:
: RenderContext
}
// 2. TemplateDef 保留泛型
export interface <> {
: React.<<>>
?: (: unknown) => is
readonly ?: // 类型占位符
}
// 3. DataOf 条件推导
export type <> = extends <infer > ? : never
// 推导出数据类型实际应用
// ✅ 类型正确
await ('user/card', { : 'Alice', : '/a.png' })
// ❌ 类型错误
await ('user/card', { name: 'Alice' }) // 缺少 avatar条件类型
基础条件类型
type <> = extends string ? true : false
type A = <'hello'>type B = <42>分布式条件类型
type <> = extends any ? [] : never
type Result = <string | number>// 等价于 string[] | number[]推导 infer
// 提取数组元素类型
type <> = extends <infer > ? :
type A = <string[]>type B = <string>
// 提取 Promise 类型
type <> = extends <infer > ? :
type C = <<number>>框架中的条件类型
import type { } from '@karinjs/template-react'
// 提取数据类型
export type <> = extends <infer > ? : never
// 提取组件类型
export type <> = extends <any> ? ( extends { : infer } ? : never) : never
// 检查是否为模板定义
export type <> = extends <any> ? true : false映射类型
基础映射
interface User {
: string
: number
}
// 全部可选
type <> = {
[ in keyof ]?: []
}
type PartialUser = <User>
// 全部只读
type <> = {
readonly [ in keyof ]: []
}
type ReadonlyUser = <User>高级映射
interface User {
: string
: number
}
// 挑选指定键
type <, extends keyof > = {
[ in ]: []
}
type UserName = <User, 'name'>
// 排除指定键
type <, extends keyof > = <, <keyof , >>
type UserWithoutAge = <User, 'age'>框架中的映射类型
import type { , } from '@karinjs/template-react'
// 提取所有路由
export type < extends > = keyof
// 提取所有数据类型
export type < extends > = {
[ in keyof ]: [] extends <infer > ? : never
}
// 过滤特定前缀的路由
export type < extends , extends string> = {
[ in keyof as extends `${}${string}` ? : never]: []
}
// 使用示例
declare const : {
'user/card': <{ : string }>
'user/profile': <{ : number }>
'admin/dashboard': <{ : number }>
}
type UserRoutes = <typeof , 'user/'>高级类型推导场景
场景 1:类型守卫
import type { } from '@karinjs/template-react'
function <>(: unknown): is <> {
return typeof === 'object' && !== null && 'component' in && typeof ( as any).component === 'function'
}
// 使用
const : unknown = {}
if (()) {
// 此处 maybeTemplate 类型缩窄为 TemplateDef<unknown>
.component}场景 2:重载签名
// ✅ 精确路由:类型推导
await ('user/card', { : 'Alice', : '/a.png' })
// ✅ 动态路由:接受任意数据
const = 'user/' + 'card'
await (, { : 'Alice', : '/a.png' })场景 3:泛型约束
import type { } from '@karinjs/template-react'
// 约束泛型必须是模板定义
function < extends <any>>(: ): extends <infer > ? : never {
// @ts-ignore
return .
}
// 使用
import { } from '@karinjs/template-react'
const = <{ : string }>({ : () => null })
const data = ()场景 4:递归类型
// 深度只读
type <> = {
readonly [ in keyof ]: [] extends object ? <[]> : []
}
interface User {
: string
: {
: number
: {
: string
}
}
}
type ReadonlyUser = <User>
// profile.address.city 也是只读场景 5:模板字面量类型
import type { } from '@karinjs/template-react'
// 提取板块名
type < extends string> = extends `${infer }/${string}` ? : never
type Sections = <'user/card' | 'user/profile' | 'admin/dashboard'>
// 构建路由
type < extends string, extends string> = `${}/${}`
type UserCard = <'user', 'card'>
// 框架应用:路由补全
type < extends , extends string> = <keyof , `${}/${string}`>
declare const : {
'user/card': any
'user/profile': any
'admin/dashboard': any
}
type UserRoutes = <typeof , 'user'>场景 6:类型谓词
if ((, )) {
// 此处 data 类型已缩窄为 DataOf<LoadedRegistry[typeof path]>
await (, )
}
declare function (: string, : unknown): <void>场景 7:联合类型分发
import type { } from '@karinjs/template-react'
// 为每个路由生成渲染函数类型
type <> = {
[ in keyof ]: [] extends <infer > ? (: ) => <void> : never
}
declare const : {
'user/card': <{ : string }>
'user/profile': <{ : number }>
}
type Fns = <typeof >
// 使用
const : = {
'user/card': async () => {
data. },
'user/profile': async () => {
data. }
}场景 8:协变与逆变
// 协变(Covariant):子类型 → 子类型
type <> = []
const : <string> = ['a', 'b']
const : <string | number> = // ✅ 安全
// 逆变(Contravariant):父类型 → 子类型
type <> = (: ) => void
const : <string | number> = () => {}
const : <string> = // ✅ 安全
// 双变(Bivariant):不安全
interface <> {
(: ): void
}
const : <any> = { : () => {} }
const : <string> = // ⚠️ 不安全框架应用:
import type { } from '@karinjs/template-react'
import type React from 'react'
// 组件 props 是逆变的
type <> = React.<<>>
// 可以用更宽泛的组件替代
const : <any> = () => null
const : <{ : string }> = // ✅类型推导调试
使用 TypeScript Playground
// 复制到 https://www.typescriptlang.org/play
import type { TemplateDef, DataOf } from '@karinjs/template-react'
interface UserData {
name: string
}
const template: TemplateDef<UserData> = {} as any
type Extracted = DataOf<typeof template>
// ^? // 悬停查看推导结果使用 @ts-expect-error
import type { , } from '@karinjs/template-react'
// DataOf 对非 TemplateDef 的类型推导出 never
type InvalidExtract = <string>
// 所以任何值都赋不进去
const bad: = { : 'test' }
// 验证类型正确
const : <{ : string }> = {} as any
type = <typeof >
const : = { : 'test' } // ✅使用类型断言
import type { , } from '@karinjs/template-react'
// 强制显示中间类型
type Step1 = type Step2 = keyof type Step3 = ['user/card']type Step4 = <>类型体操最佳实践
1. 保持类型简单
// ✅ 好:直接明了
type UserData = { name: string; age: number }
// ❌ 差:过度抽象
type UserData<T extends Record<string, any>> = {
[K in keyof T]: T[K] extends infer U ? U : never
}2. 使用工具类型
// ✅ 好:复用内置工具类型
type PartialUser = Partial<User>
// ❌ 差:重新实现
type PartialUser = { [K in keyof User]?: User[K] }3. 渐进式类型推导
import type { } from '@karinjs/template-react'
// 第一步:提取数据类型
type <> = extends <infer > ? : never
// 第二步:添加约束
type < extends <any>> = <>
// 第三步:添加默认值
type <, = unknown> = extends <infer > ? : 4. 类型注释
import type { } from '@karinjs/template-react'
/**
* 从模板定义中提取数据类型。
*
* @example
* const template = defineTemplate<{ name: string }>({ component: MyCard })
* type Data = DataOf<typeof template> // { name: string }
*/
export type <> = extends <infer > ? : never5. 测试类型
import { } from 'tsd'
import type { , } from '@karinjs/template-react'
// 测试 DataOf 推导
const : <{ : string }> = {} as any
<{ : string }>( as any as <typeof >)
// 测试联合类型
type = { : string } | { : number }
<>( as any as <<>>)6. 类型文档
# 类型系统
## LoadedRegistry
精确的模板注册表类型,由 `ktr sync` 自动生成。
未增强时退化为 `AnyRegistry`(任意路由)。
## DataOf<T>
从模板定义中提取数据类型。
**用法**:
- `DataOf<LoadedRegistry[K]>` 提取指定路由的数据类型
- `DataOf<typeof template>` 提取模板定义的数据类型7. 避免类型断言
import type { , } from '@karinjs/template-react'
declare function (: string, : unknown): void
// ❌ 差:类型断言
('user/card', { : 'Alice' } as any)
// ✅ 好:类型推导
function < extends keyof >(: , : <[]>) {
(, )
}
('user/card', { : 'Alice' })8. 类型兼容性
import type { } from '@karinjs/template-react'
// 向后兼容:新类型包含旧类型
type = { : string }
type = { : string; ?: number }
const : <> = {} as any
const : <> = // ❌ 不安全
// 正确:显式转换
function (: <>): <> {
return {
...,
: (): is => {
return .?.() ?? true
}
}
}类型调试工具
tsserver 日志
# VS Code 命令面板
# TypeScript: Open TS Server log
# 查看类型推导过程tsc --traceResolution
pnpm tsc --traceResolution | grep registry-typestsd 类型测试
import { , } from 'tsd'
import type { , } from '@karinjs/template-react'
// 类型断言测试
const : <{ : string }> = {} as any
<{ : string }>( as any as <typeof >)
// 类型错误测试
<<string>>(undefined)