进阶指南

类型体操

模块增强原理深入、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

为什么使用子路径导出

packages/core/package.json
{
  "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 确保:

  1. 模块增强可合并:多个 .d.ts 增强同一个模块 ID
  2. 打包后路径不变import('@karinjs/template-react/registry-types') 在源码和产物中都指向同一模块
  3. 避免循环依赖:registry-types 独立于主入口

类型增强生效流程

// 步骤 1:约定扫描生成注册表
// conventions/registry.ts
const routes = await discoverTemplateRoutes(config.templateDir)
// → ['user/card', 'user/profile']

await generateTypeAugmentation(routes, config.cacheDir)

生成的类型文件:

.ktr/registry-types.d.ts
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>) => {
data: UserCardData

当前模板使用的数据,类型由 defineTemplate 的泛型决定。

return <>{.}</> } // 3. defineTemplate 自动推导 const = ({ : }) // 4. DataOf 提取类型 type ExtractedData = <typeof >
type ExtractedData = UserCardData
// 验证类型一致 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
//                                          推导出数据类型

实际应用

src/utils/render.ts
// ✅ 类型正确
await ('user/card', { : 'Alice', : '/a.png' })

// ❌ 类型错误
await ('user/card', { name: 'Alice' }) // 缺少 avatar
Argument of type '{ name: string; }' is not assignable to parameter of type '{ name: string; avatar: string; }'. Property 'avatar' is missing in type '{ name: string; }' but required in type '{ name: string; avatar: string; }'.

条件类型

基础条件类型

type <> =  extends string ? true : false

type A = <'hello'>
type A = true
type B = <42>
type B = false

分布式条件类型

type <> =  extends any ? [] : never

type Result = <string | number>
type Result = string[] | number[]
// 等价于 string[] | number[]

推导 infer

// 提取数组元素类型
type <> =  extends <infer > ?  : 

type A = <string[]>
type A = string
type B = <string>
type B = string
// 提取 Promise 类型 type <> = extends <infer > ? : type C = <<number>>
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 PartialUser = {
    name?: string | undefined;
    age?: number | undefined;
}
// 全部只读 type <> = { readonly [ in keyof ]: [] } type ReadonlyUser = <User>
type ReadonlyUser = {
    readonly name: string;
    readonly age: number;
}

高级映射

interface User {
  : string
  : number
}

// 挑选指定键
type <,  extends keyof > = {
  [ in ]: []
}

type UserName = <User, 'name'>
type UserName = {
    name: string;
}
// 排除指定键 type <, extends keyof > = <, <keyof , >> type UserWithoutAge = <User, 'age'>
type UserWithoutAge = {
    name: string;
}

框架中的映射类型

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/'>
type UserRoutes = {
    'user/card': TemplateDef<{
        name: string;
    }>;
    'user/profile': TemplateDef<{
        age: number;
    }>;
}

高级类型推导场景

场景 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
TemplateDef<unknown>.component: React.ComponentType<TemplateProps<unknown>>

实际渲染用户图片模板的 React 组件。

}

场景 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 = ()
const data: {
    name: string;
}

场景 4:递归类型

// 深度只读
type <> = {
  readonly [ in keyof ]: [] extends object ? <[]> : []
}

interface User {
  : string
  : {
    : number
    : {
      : string
    }
  }
}

type ReadonlyUser = <User>
type ReadonlyUser = {
    readonly name: string;
    readonly profile: DeepReadonly<{
        age: number;
        address: {
            city: string;
        };
    }>;
}
// 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 Sections = "user" | "admin"
// 构建路由 type < extends string, extends string> = `${}/${}` type UserCard = <'user', 'card'>
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'>
type UserRoutes = "user/card" | "user/profile"

场景 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 >
type Fns = {
    'user/card': (data: {
        name: string;
    }) => Promise<void>;
    'user/profile': (data: {
        age: number;
    }) => Promise<void>;
}
// 使用 const : = { 'user/card': async () => { data.
data: {
    name: string;
}
}, 'user/profile': async () => { data.
data: {
    age: number;
}
} }

场景 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>
type InvalidExtract = never
// 所以任何值都赋不进去 const bad: = { : 'test' }
Type '{ name: string; }' is not assignable to type 'never'.
// 验证类型正确 const : <{ : string }> = {} as any type = <typeof > const : = { : 'test' } // ✅

使用类型断言

import type { ,  } from '@karinjs/template-react'

// 强制显示中间类型
type Step1 = 
type Step1 = {
    [x: string]: TemplateDef<any>;
}
type Step2 = keyof
type Step2 = string
type Step3 = ['user/card']
type Step3 = TemplateDef<any>
type Step4 = <>
type Step4 = any

类型体操最佳实践

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 > ?  : never

5. 测试类型

types.test.ts
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. 类型文档

types/README.md
# 类型系统

## 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-types

tsd 类型测试

types.test-d.ts
import { ,  } from 'tsd'
import type { ,  } from '@karinjs/template-react'

// 类型断言测试
const : <{ : string }> = {} as any
<{ : string }>( as any as <typeof >)

// 类型错误测试
<<string>>(undefined)
Argument of type 'undefined' is not assignable to parameter of type 'never'.

On this page