想象你要装修一栋楼。开发商给了你两套“半成品房间”:React 房和 Vue 房。房间的格局可以定制——墙上留多少插座、门开在哪、窗户多大——这些参数,就是组件的 Props。门铃怎么响、窗户开了怎么通知你,是事件。墙上预留的挂口,是你想挂什么就挂什么的 Slot。 但问题来了:这些半成品房间没有“规格说明书”。你把一张大床硬塞进只有 1 米宽的卧室,把三相电钻插进普通插座——装修师傅只会在施工到一半时才发现装不进去,届时要砸墙重来。TypeScript 就是把规格说明书提前写好:房间里每一项参数的尺寸、材质、电流,都在图纸上标明。施工队(代码编辑器里的类型检查器)一看图纸就知道“这床太大,放不进这间房”,根本不用等施工才发现。 本文就是带着你,以 TypeScript 监理的身份,把 React 和 Vue 这两套房的图纸(组件类型)逐个核对一遍。

岗位关联

对 AI 从业者而言,掌握框架的类型约定意味着能用最小的心智成本组合出没有运行时 bug 的组件;尤其当你使用 AI 辅助编程时,清晰的类型边界既能约束生成代码的合法性,也能让 AI 准确理解你的接口意图,把错误从“运行后才暴露”提前到“写代码时就发现”。

目录
  1. 可视化演示
  2. 1. React组件类型:FC与Props接口
  3. 2. Vue3泛型推导:defineComponent
  4. 3. 事件类型设计:回调签名
  5. 4. Slot类型化:插槽声明
  6. 5. 模板类型检查:vue-tsc
  7. 6. 常见类型错误:四大雷区
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

模拟React.FC类型约束、Vue3 defineComponent泛型推导与vue-tsc模板类型检查三模式,展示TypeScript与框架深度集成机制。

本节配有交互动效(进入视口后自动加载,约 3KB)。

模式1:React.FC类型约束 Props泛型约束组件属性类型 事件类型通过ReactEventHandler推导 FC = (props: P) => JSX 类型约束在编译期捕获Props错误。

1. React组件类型:FC与Props接口

装修时,开发商会给你一份“精装房套餐合同”。合同上写明:这个套餐叫 React.FC,你作为住户(开发者)需要填写一份《家具清单》(Props)。清单上写什么项目、每种项目的规格,都由你来定——但一旦定了,施工队就按这份清单施工,清单上没有的东西一律不许搬进来。

在 React 中,每个函数组件都像一间这样的精装房。React.FC 是 React 官方提供的“套餐合同”类型:它是一个泛型接口,尖括号里的 P 代表你这间房《家具清单》的类型。进入房间的所有“行李”(props)都必须符合这张清单,返回的“成品房”也必须是 ReactElement。我们通过 interface 定义 Props 的“规格书”,比如 UserCard 组件需要 name 必须为 string,age 可选为 number。

用 React.FC<UserCardProps> 声明组件后,组件内部取 props 时编辑器会自动提示有哪些属性、什么类型;漏传 name 会直接标红。可选属性还可以在解构赋值时提供默认值,保证“没带家具的房间也有基础配置”。

有了 React.FC 这份套餐合同,开发者和编辑器就像有了同一张图纸:组件名字、入参结构、返回值一目了然。后续要换施工队(改成 memo 组件或换成别的实现),只要合同不变,里面的“家具”和“房间”依然能无缝配合。

React.FC⟨P⟩=(props:P)⇒ReactElement\displaystyle \mathrm{React.FC}\langle P \rangle = (props: P) \Rightarrow \mathrm{ReactElement}
这个公式说的是:React.FC 是一个“工厂公式”——传入你定义好的清单类型 P,它会产出一种函数类型;这个函数接收清单 P 类型的 props,最后返回一个 React 元素。
  1. React.FC 标记这是 React 官方提供的函数组件类型标记,相当于“精装房套餐合同”的印章,表明这间房是按标准施工的。
    React.FC\mathrm{React.FC}
  2. 泛型参数 P尖括号里的 P 是用自定义的 Props 类型去填充的占位符,就像把《家具清单》填进合同,合同才真正有效。
    ⟨P⟩\langle P \rangle
  3. 函数签名 (propsP):合同规定:入住的人必须拿一份类型为 P 的清单进来,房间里的一切操作都以这份清单为准,多带物品会被拒绝。
    (props:P)(props: P)
  4. 返回类型 ReactElement交房时必须交付一个标准的 React 元素,不能是数字、字符串或其他杂项,保证“成品”的形态统一。
    ⇒ReactElement\Rightarrow \mathrm{ReactElement}

2. Vue3泛型推导:defineComponent

不同于 React 房要自己填合同,Vue 3 的开发商提供了一个“标准户型合同生成器”:你只要在表单上勾选户型参数,它自动生成对应的合同文本——这就是 defineComponent 的泛型推导。在 Vue 3 中写组件,使用 defineComponent 包裹组件对象,TypeScript 就能从你写的 props 选项中自动推导出组件实例的类型。

你不用手动写 interface,而只需要用字面量声明 props 的类型构造器。比如 props 写 name: String,推导出来后你访问 name 就会获得 string 类型;写 count: Number,就是 number。如果用 as const 修饰字面量,泛型还能保留“精确到字面量”的类型,并连同可选标记一起保留。defineComponent 返回的组件既能在模板中使用,也能作为类型在别的组件里准确引用它的 Props 和 emits。

Vue 内部用 ExtractPropTypes 这类工具类型把“构造器写法”翻译成标准 TS 类型。下面这个公式展示了这种“自动翻译机”的工作原理:

这个“表单自动生成合同”的方案让 Vue 的门槛比 React 低一条街:你不需要自己执笔写 interface 规格书,只需要勾选参数,合同自动到手。但注意:自动生成的前提是你能正确地“勾选”——写错了构造器(比如把 String 直接写成 string),翻译机当场罢工。

ExtractPropTypes({ name:String, age:Number, tags?:Array})={ name: string, age: number, tags?: string[]}\displaystyle \mathrm{ExtractPropTypes}\Big( \{\ \mathrm{name}: \mathrm{String},\ \mathrm{age}: \mathrm{Number},\ \mathrm{tags}?: \mathrm{Array} \} \Big) = \{\ \mathrm{name}:\ \mathrm{string},\ \mathrm{age}:\ \mathrm{number},\ \mathrm{tags}?:\ \mathrm{string}[] \}
这段公式展示的是“翻译机”的输入输出:左边是你用运行时构造器写的方案,右边是翻译出来的标准 TS 类型,可选标记?会原样保留。
  1. ExtractPropTypes 工具类型Vue 提供的“翻译机”,专门把 props 选项翻译成 TS 类型,是 defineComponent 泛型推导的核心引擎。
    ExtractPropTypes\mathrm{ExtractPropTypes}
  2. 输入 props 选项对象花括号里是组件声明时的写法,String、Number、Array 都是运行时构造器,并不是 TS 类型本身。
    name:String, age:Number\mathrm{name}: \mathrm{String},\ \mathrm{age}: \mathrm{Number}
  3. 构造器到原始类型翻译机会调用“构造器类型推断”的逻辑,把 String 变成 string、Number 变成 number、Array 变成数组类型。
    name: string, age: number\mathrm{name}:\ \mathrm{string},\ \mathrm{age}:\ \mathrm{number}
  4. 保留可选标记声明里的?可选修饰符会原样保留到最终类型上,表示这个 prop 可以缺省。
    tags?: string[]\mathrm{tags}?:\ \mathrm{string}[]

3. 事件类型设计:回调签名

房间里的门铃、对讲机、报警器,都是“事件”:它们把外面发生的事通知给房间里面的人。为了让通知不走样,门铃的“信号协议”必须提前写在图纸上——是交流电还是直流电、电压多少、响铃时长——这些协议就是事件回调的类型签名。

React 中,事件处理器是组件的 props 之一。每个 DOM 事件都有精确的类型:输入框的 change 是 ChangeEvent<HTMLInputElement>,按钮的点击是 MouseEvent<HTMLButtonElement>。用错事件类型就像把门铃接到 380V 电网上——编译不报错,直到“按下”那一刻才烧毁设备。

我们推荐用 React.ComponentProps<'input'> 来提取原生元素的回调签名,而不是手写,因为它保证 onChange 等属性类型与浏览器 DOM 完全一致。Vue 3 则在 defineComponent 的 emits 选项里声明事件名和参数表:声明 emit('select', id: number),子组件触发时就必须传 number,父组件接收时也获得对应类型提示。两家的共同点是:事件回调返回值约定为 void,不产生额外副作用。

把每个事件的信号协议写清楚,施工队就能在装修前把线缆全部接对:onClick 只接受点击信号的接头,onChange 只接受变更信号的接头,接错了一个“接口形状不匹配”的红灯当场亮起。房间投入使用后,就不再有“按下按钮却没响应”的诡异事故。

onChange : ChangeEvent⟨HTMLInputElement⟩⇒void\displaystyle \mathrm{onChange}\ :\ \mathrm{ChangeEvent}\langle \mathrm{HTMLInputElement} \rangle \Rightarrow \mathrm{void}
onChange 这个手柄(事件属性)被绑定到一个函数类型上:它接收一个“输入框变化事件”参数,返回值约定为空。
  1. onChange 属性表单输入框的事件属性名,像门铃的按钮,触发时机是输入框内容发生变化。
    onChange\mathrm{onChange}
  2. 泛型事件对象ChangeEvent<HTMLInputElement> 描述了事件的“信号包”,尖括号里的 HTMLInputElement 指明这个事件绑定在哪种原生元素上。
    ChangeEvent⟨HTMLInputElement⟩\mathrm{ChangeEvent}\langle \mathrm{HTMLInputElement} \rangle
  3. 返回 void 约定事件回调不需要返回有意义的值,声明 void 后任何试图返回别的类型的行为都会触发编译警告,避免“按门铃还要写收据”的荒谬事。
    ⇒void\Rightarrow \mathrm{void}

4. Slot类型化:插槽声明

房间的墙上常留一些“标准挂口”——你可以在上面挂画、挂电视、挂架子,但挂口的尺寸和承重是固定的。这套挂口就是 Vue 的插槽(Slot):组件留出位置,由使用方决定这块墙最终放什么。

在 TypeScript 视角下,插槽也是“类型化的接口”。默认插槽、命名插槽、作用域插槽,各有各的签名。作用域插槽最像“带电的挂口”:它是接收子组件数据的函数,必须验证输入输出类型。Vue 3.3 及以上版本支持用 defineSlots 声明插槽类型,或者在 TSX 语境下用泛型给插槽一个完整的类型描述。

我们定义插槽类型为“插槽名到函数的映射”:函数接收作用域数据(若有),返回 VNode 数组。比如 header 插槽接收一个 title 字符串;default 插槽无参数。这样使用方在模板里填插槽内容时,编辑器会提示:当前插槽能收到什么变量、必须渲染什么。相比 React 的 children(只约定 ReactNode),Vue 的插槽更接近“参数化墙体”。

给挂口标好承重和电压后,房间里挂什么都清清楚楚:挂电视(插槽内容)的前提是墙上有对应的电源和信号线(作用域数据)。之前“随便挂、挂上才发现掉下来”的烦恼,都化解在挂墙之前——也就是编译期。

SlotsProps={ default: ()⇒VNode[];header?: (t: string)⇒VNode[] }\displaystyle \mathrm{SlotsProps} = \{\ \mathrm{default}:\ ()\Rightarrow \mathrm{VNode}[];\quad \mathrm{header}?:\ (t:\ \mathrm{string})\Rightarrow \mathrm{VNode}[]\ \}
这是组件插槽的“挂口规格表”:default 是万能挂口(无参数),header 是带参数 t 的挂口,t 必须是 string 类型。
  1. SlotsProps 类型别名给插槽规格起一个名字,代表组件整个“挂口墙”的类型,方便在其它地方引用这个规格。
    SlotsProps={… }\mathrm{SlotsProps} = \{ \dots \}
  2. default 插槽签名默认挂口不接受任何作用域数据,但使用方必须提供返回 VNode 数组的函数,保证挂上去的东西确实渲染得出来。
    default: ()⇒VNode[]\mathrm{default}:\ ()\Rightarrow \mathrm{VNode}[]
  3. header 可选插槽命名挂口 header 是可选的(带?),它接收 string 参数 t,返回 VNode 数组。使用方若提供了内容,就必须按这个签名构造,数据从组件传入插槽。
    header?: (t: string)⇒VNode[]\mathrm{header}?:\ (t:\ \mathrm{string})\Rightarrow \mathrm{VNode}[]

5. 模板类型检查:vue-tsc

图纸画得再细,施工前还得有监理。在 Vue 世界,.vue 文件是一个“黑箱施工包”:模板部分用的是类似 HTML 的格式,TypeScript 本身“看”不到里面的变量类型。于是“监理”出场了,它就是 vue-tsc。

vue-tsc 是 Vue 官方提供的类型检查工具,它把 .vue 文件拆成 script 和 template 两部分,分别做类型检查,再合成一份“施工审计报告”。像 Vite 的构建工具(esbuild)为了快,默认不做类型检查——所以“我 npm run build 没报错,线上却出了类型相关 bug”,多半是漏掉了 vue-tsc 这一步。

使用命令 vue-tsc --noEmit 检查类型;在 tsconfig.json 里需要 include 项目的 .vue 文件和 src 目录。检查内容主要是模板里访问的变量是否在组件实例中存在、v-model 双向绑定的类型是否匹配、事件回调参数类型是否通过。React 项目则用 tsc(配合 jsx 配置)完成类似检查,整体思路相同:编译前先“读图”。

监理(vue-tsc)在毛坯阶段就拿着图纸把每个施工环节过了一遍:电线的粗细、插座的位置、承重墙的开孔,全都要比对。只要监理签字通过,后面就算交付延期也不会出现“住进去才发现插座全在三相电上没有零线”的灾难。

SFC → vue-tsc  Report={ errors: string[]; warnings: string[] }\displaystyle \mathrm{SFC}\ \xrightarrow{\ \mathrm{vue\text{-}tsc}\ }\ \mathrm{Report} = \{\ \mathrm{errors}:\ \mathrm{string}[];\ \mathrm{warnings}:\ \mathrm{string}[]\ \}
一次 vue-tsc 检查,相当于把整个 .vue 文件放进一台“读图机器”,产出错误清单和警告清单,有错误就不能通过验收。
  1. SFC 输入SFC 是 Single File Component(单文件组件),即 .vue 文件,它同时包含模板、脚本和样式三段,是一个需要拆解的黑箱。
    SFC\mathrm{SFC}
  2. vue-tsc 变换箭头上的 vue-tsc 是一个类型检查变换:解析 SFC 内部结构,在模板与脚本之间做交叉验证。
    → vue-tsc \xrightarrow{\ \mathrm{vue\text{-}tsc}\ }
  3. 检查报告输出输出是错误和警告的列表,errors 非空时 vue-tsc 以非零码退出,CI 直接挂掉,把坏图纸挡在交付之前。
    errors: string[]; warnings: string[]\mathrm{errors}:\ \mathrm{string}[];\ \mathrm{warnings}:\ \mathrm{string}[]

6. 常见类型错误:四大雷区

再好的图纸,施工时也常出现“图纸冲突”。最常见的冲突有四种:把本应 220V 的插孔插了 380V 设备(类型不匹配);在可选雷点上忽略了 undefined(可选链问题);把泛型参数随意替换成 any(泛型丢失);以及为了省事把所有未知类型都标成 any(any 滥用)。这四类都是框架集成中最常见的雷区。

类型不匹配,比如 Props 声明 age 是 number,父组件却传了字符串“18“。可选链问题,比如 props 上有可选属性,取用时直接 this.optional.value,未传时运行期崩溃。泛型丢失,比如用泛型 T 封装列表组件,却在内部把 T 断言为 any,等于给房间贴了”家具尺寸随便”的免责声明。any 滥用则把整本规格说明书撕掉,让 TypeScript 彻底沦为摆设。

解决方案对应各种“工具型手段”:keyof 取属性名联合,用于约束对象访问;索引访问 T[K] 取属性值的类型,用于精确联动;可选访问?. 提前处理 undefined;泛型 T 则保留调用方传入的类型,直到使用它的那一刻才确定。注意一定要开启 strictNullChecks 选项,让 null 和 undefined 成为“看得见的雷”。

图纸冲突不可怕,可怕的是不知道哪儿会冲突。keyof、索引访问、可选链这些工具,相当于给房间装了烟雾报警器:一旦出现“型号不匹配”,监控面板(编辑器)会立刻弹出红色告警,而不是等入住了才发现。

keyof UserCardProps={ name, age },UserCardProps[name]=string\displaystyle \mathrm{keyof}\ \mathrm{UserCardProps} = \{\ \mathrm{name},\ \mathrm{age}\ \},\quad \mathrm{UserCardProps}[\mathrm{name}] = \mathrm{string}
keyof 拿起“属性名扫描仪”,把 UserCardProps 中的所有键名扫出来组成集合;索引访问 T[K] 则从类型中取出某个属性对应的值类型。
  1. keyof 运算返回类型所有公共属性名的“字符串字面量联合类型”,相当于把房间里的所有家具登记造册。
    keyof UserCardProps\mathrm{keyof}\ \mathrm{UserCardProps}
  2. 属性名集合结果结果包含 name 和 age 两个字面量,之后可以用 K in keyof T 做遍历,约束访问 key 的合法性。
    { name, age }\{\ \mathrm{name},\ \mathrm{age}\ \}
  3. 索引访问 T[K]从类型 T 中按属性名 K 取出值的类型,类似 JS 里对象取值 obj[“name“] 的”类型镜像”。
    UserCardProps[name]\mathrm{UserCardProps}[\mathrm{name}]
  4. 得到 string 结果因为 name 声明为 string,取到的类型是 string。用这个技巧可以让“根据某个 key 拿到对应 value 类型”的联动检查成为可能。
    =string= \mathrm{string}

AI 应用场景

场景一:AI 辅助编码的接口契约

阶段:代码生成与静态校验

解决的问题:AI 生成组件代码时,最容易产出“类型错乱”的 Props 和事件签名。传统方式是人工逐行 review AI 的输出,效率极低;而用 TypeScript 的 FC 泛型、defineComponent 推导和 vue-tsc 检查后,AI 生成的代码会先被类型系统“过一遍筛子”,不合格的直接编译失败,相当于让 AI 在交作业前自己改错。

具体案例:前端团队使用 Copilot 生成一个用户信息卡片组件。开发者在 prompt 中先给出 interface UserCardProps { name: string; age?: number },再由 AI 补全实现。由于类型契约已存在,AI 生成的解构代码、事件回调签名都被限制在契约内,最终通过 vue-tsc --noEmit 或 tsc --noEmit 校验后合入主干。

场景二:跨团队组件库的类型统一

阶段:模型输出接口对齐与契约同步

解决的问题:多个团队共享一个组件库时,React 团队和 Vue 团队各自维护组件,Props 的命名和类型经常漂移。传统方式靠文档同步,容易过期。把组件 Props、事件、Slot 都类型化后,组件库的 .d.ts 声明文件就是“活文档”,任何团队消费组件时编辑器都会给出准确的类型提示。

具体案例:某中台团队维护一个“筛选面板”组件,Vue3 版用 defineComponent 声明了 modelValue: string[] 和 emit('change', val: string[]),React 版用 React.FC<FilterPanelProps> 声明同样契约。两个团队各自在 CI 中加入类型检查脚本,任何一方的 Props 变更都会在联调前暴露。

Python 演示

python60 行
"""模拟 TypeScript 对组件 Props 的类型检查决策逻辑""" TYPE_MAP = {    "string": str,    "number": (int, float),    "boolean": bool,}  class PropSpec:    """一条 Props 规格 期望类型 + 是否必填"""     def __init__(self, type_name, optional=False):        self.type_name = type_name        self.optional = optional  def check_props(component, props, spec_map):    """对照规格表检查 props 返回错误列表"""    errors = []    for key, spec in spec_map.items():        if key not in props:            if not spec.optional:                errors.append("缺少必需 prop: '{0}'".format(key))            continue        if not isinstance(props[key], TYPE_MAP[spec.type_name]):            errors.append(                "prop '{0}' 期望 {1} 实际得到 {2}".format(                    key, spec.type_name, type(props[key]).__name__                )            )    for key in props:        if key not in spec_map:            errors.append("未知 prop: '{0}'".format(key))    return errors  def render(component, props, spec_map):    """先检查再渲染 模拟 vue-tsc 在编译期的拦截"""    errors = check_props(component, props, spec_map)    if errors:        print("[类型检查失败] <{0}> 被拦截:".format(component))        for e in errors:            print("  - " + e)        return None    print("类型检查通过 渲染 <{0}>".format(component))    return "<{0}>".format(component)  user_card_spec = {    "name": PropSpec("string"),    "age": PropSpec("number", optional=True),    "avatar": PropSpec("string", optional=True),} render("UserCard", {"name": "Alice"}, user_card_spec)print()render("UserCard", {"name": 42}, user_card_spec)print()render("UserCard", {"name": "Bob", "email": "bob@x.com"}, user_card_spec)
  1. class PropSpec:定义一条 Props 规格,类似 TS 中 Props 接口里的一个属性声明,包含期望类型和是否必填。
  2. def check_props(component, props, spec_map):检查器的入口函数,对应 vue-tsc 的检查流程,返回错误列表。
  3. if key not in props:检查必填属性是否缺失,对应 TS 中“缺少必需 prop”的编译错误。
  4. isinstance(props[key], TYPE_MAP[spec.type_name])判断传入值的类型是否符合规格,相当于 TS 的静态类型匹配。
  5. for key in props:遍历实际传入的 props,找出规格之外的未知字段,对应 TS 的“对象字面量多余属性检查”。
  6. if errors: ... return None存在类型错误时拒绝渲染,模拟编译失败终止构建的行为。
  7. render("UserCard", {"name": 42}, user_card_spec)演示一个错误的调用被拦截,name 传入 int 而规格要求 string。

常见误区

误区我用的 Vite,每次 `npm run build` 都编译得很成功,所以 Vue 模板里的类型应该没问题。如果有问题,vite 会告诉我。

正解Vite 的构建内核 esbuild 为了极致的构建速度,默认只做“语法转译”,不做类型检查。它把 .vue 里的 TS 全部去掉类型注解,根本不会校验模板中的变量访问、Props 类型、事件签名。必须单独执行 vue-tsc --noEmit,或者在构建脚本中把“类型检查”和“打包”两步串起来,才能真正拦截类型错误。

为何常见:因为“构建成功 = 代码正确”是很多从纯 JS 时代过来的开发者的直觉,他们不了解 esbuild 与 tsc 的分工,看到构建通过就以为万事大吉。

误区React.FC 里的泛型会自动帮我推导 Props,不用认真定义接口,写个 any 也没关系,反正能跑。

正解React.FC<P> 的泛型参数 P 是一个“占位符”,它不会自动生成规格。如果你写 React.FC<any>,等于把合同的每一项都留白,施工队完全不知道搬进来的是什么。Props 接口才是决定组件对外契约的关键,FC 只是把接口与函数组件绑定起来。正确的做法是先定义精确的 interface,再交给 FC 去约束,组件内部才能获得完整的提示。

为何常见:初学 TS 的人常把“泛型”误认为“自动推导的魔法”,忽略了它只是一个等待填充的类型参数,于是掉进 any 的陷阱。

误区事件处理函数嘛,网上代码到处都是,把 `onChange`、`onClick` 的类型抄过来用就行,不用理解它是什么。

正解事件类型必须和它绑定的元素匹配:input 的 change 事件是 ChangeEvent<HTMLInputElement>,按钮的 click 是 MouseEvent<HTMLButtonElement>。如果把 ChangeEvent 抄给按钮的 onClick,虽然语法合法,但你在事件对象里访问 e.target.value 时,类型上可能拿不到字符串。更稳妥的方式是用 React.ComponentProps<'input'>['onChange'] 之类的工具类型从原生元素中提取签名,而不是手抄。

为何常见:事件对象是“看着像但不确定”的类型,抄代码时往往只关注函数体逻辑,忽略了参数类型的上下文匹配,导致类型与实际运行环境脱节。

误区遇到类型报错就用 any 覆盖,反正代码能跑,类型检查只是摆设,后面再补类型。

正解any 会把整个“规格说明书”撕掉,让 TypeScript 对这个值彻底失明。一旦这个值被传进组件、触发事件、当作 Slot 数据,所有下游的类型检查全部失灵。更可怕的是,AI 辅助编程时代,AI 看到 any 会倾向于继续产生 any,让整个项目的类型保护层雪崩。正确做法是优先用泛型保留类型信息,用 unknown 替代不明确的类型,再通过类型守卫收窄。

为何常见:类型报错让人焦虑,any 提供了“立刻消除错误”的即时快感,形成负向激励,导致开发者越来越依赖这个拐杖。

面试题

Q1. React.FC<Props> 中的 Props 指的是什么?

单选题 · easy

  • A.组件渲染返回的元素类型
  • B.组件属性的集合数据类型← 正确答案
  • C.组件自身状态的类型集合
  • D.组件生命周期的类型集合

答案:B。React.FC 是函数组件类型,尖括号里的泛型参数 P 代表组件接收的属性集合类型(Props),就像房间的《家具清单》。A 说的是返回值类型,C 是组件状态(State),D 与生命周期概念不符,均不是 Props 的语义。

Q2. keyof 操作符的作用是?

单选题 · easy

  • A.获取类型全部属性的键名联合← 正确答案
  • B.获取类型全部属性的值联合
  • C.遍历对象的所有属性去重
  • D.获取数组索引的数字范围

答案:A。keyof 拿的是“属性名扫描仪”,把类型的所有公共属性名组成一个字符串字面量联合类型。B 描述的是索引访问 T[K] 的结果,C 和 D 是运行时的遍历/索引概念,与类型层面的 keyof 不是一回事。

Q3. 在 Vue3 中,defineComponent 的主要类型价值是?

单选题 · mid

  • A.提升组件运行时的执行效率
  • B.从 props 选项自动推导出类型← 正确答案
  • C.自动启用模板的流程编译
  • D.强制让所有 props 参数必填

答案:B。defineComponent 的核心价值在于让 TypeScript 从 props 选项等配置中推导出组件实例的类型,相当于“表单自动生成合同”。A 是运行时性能问题,C 是编译器职责,D 也不是 defineComponent 的强制行为——可选标记仍然由你决定。

Q4. 事件 onChange 的正确 TS 回调签名是?

单选题 · mid

  • A.泛型数字签名,使用 number 类型
  • B.不写参数类型并使用 any 通配
  • C.精确的 ChangeEvent 泛型对象签名← 正确答案
  • D.声明为 string 类型的事件名

答案:C。onChange 的签名应当是一个函数,接收 ChangeEvent<HTMLInputElement> 类型的参数并返回 void。A 用 number 是类型错配,B 用 any 等于撕掉规格说明书,D 把事件写成了字符串命名——只有 C 精确描述了“输入框变化”这一事件信号包。

Q5. vue-tsc 的核心职责是?

单选题 · mid

  • A.将 .vue 文件打包成 JS
  • B.检查和推导 .vue 模板类型← 正确答案
  • C.把 SCSS 预编译成 CSS
  • D.启动本地开发服务器 HMR

答案:B。vue-tsc 是 Vue 官方“监理”,专门对 .vue 文件的模板和脚本做类型检查。A 是打包工具的职责,C 是样式编译,D 是开发服务器的工作——vue-tsc 只读图、不施工,产出的是错误报告。

Q6. Record<string, VNode[]> 作为 Slot 类型意味着?

单选题 · hard

  • A.组件只有一个匿名的插槽
  • B.所有插槽名为字符串且返回节点数组← 正确答案
  • C.插槽内容必须是 type string
  • D.插槽可以无限嵌套各种泛型

答案:B。Record<string, VNode[]> 是“插槽名到渲染结果的映射”:键是插槽名(字符串),值是 VNode 数组。A 说只有一个插槽不对,可能是多个;C 混淆了插槽名与插槽内容,D 是过度联想——Record 并不负责嵌套规则。

Q7. 下列哪些方式可用于 Props 类型限定?

多选题 · mid

  • A.定义 interface 传给泛型类型← 正确答案
  • B.在 defineComponent 内部推导← 正确答案
  • C.把 props 统一声明成 any
  • D.用 as const 固定字面量类型← 正确答案

答案:ABD。React 中用 interface 配合 FC 泛型限定 Props,Vue3 中 defineComponent 从 props 选项推导类型,as const 可以保留字面量类型,三者都是合法的“规格书”手段。C 用 any 是把规格书撕掉,属于典型的类型逃逸,不能算作限定。

Q8. vue-tsc 能帮助发现的问题有哪些?

多选题 · mid

  • A.模板里访问了不存在的变量← 正确答案
  • B.v-model 的绑定变量类型不匹配← 正确答案
  • C.跨层传递的响应式对象类型错← 正确答案
  • D.组件内样式表 SCSS 语法错误

答案:ABC。vue-tsc 检查的是模板与脚本之间的类型关系:模板访问不存在的变量、v-model 绑定类型不匹配、响应式对象跨层传递类型错误,都属于它的管辖范围。D 是样式编译问题,与类型无关,vue-tsc 不会检查 SCSS 语法。

Q9. React.FC 类型的作用体现在哪些维度?

多选题 · hard

  • A.限制组件 props 的入参结构← 正确答案
  • B.约束组件返回值必须是元素← 正确答案
  • C.提高组件渲染时的性能
  • D.让 JSX 使用提示报错更准确← 正确答案

答案:ABD。React.FC<P> 一方面约束入参 props 必须符合 P 的结构,另一方面约束返回值是 ReactElement,同时让 JSX 使用组件时获得准确的类型反馈。C 混淆了类型与运行时——类型系统在编译期生效,不改变渲染性能。

Q10. 泛型组件(如 const List = <T,>(...) => ...)什么时候最值得用?

单选题 · extreme

  • A.当组件只有两个属性参数时
  • B.当 Props 与数据项类型需关联时← 正确答案
  • C.当组件没有任何属性参数时
  • D.当组件只做纯文本内容渲染时

答案:B。泛型组件的价值在于“让 Props 的某个类型参数与传入的数据项类型保持联动”。比如 List<T> 的 items: T[] 和 onSelect: (item: T) => void,两个属性由同一个 T 约束,避免数据与回调的类型在后续使用中错位。A、C、D 场景没有类型联动的需求,用普通接口即可。