7.1 前端框架集成


7.1 前端框架集成

这节在地图里是"TS 落地到 UI"的第一站。React 和 Vue 都把类型用在组件边界上:props、事件、状态。契约写得好,改一个字段名时所有用错的地方立刻标红,这是 UI 层最大的类型收益。

React:函数组件 props 类型化

import { FC } from "react"; interface ButtonProps { label: string; disabled?: boolean; onClick: (e: React.MouseEvent) => void; } const Button: FC<ButtonProps> = ({ label, disabled, onClick }) => ( <button disabled={disabled} onClick={onClick}>{label}</button> ); // 使用时:缺 onClick 或 label 类型错,编译期拦 <Button label="提交" onClick={() => {}} />; // OK // <Button label="提交" />; // 报错:缺必填 onClick

输入输出:正确传参编译通过;漏必填项立即标红。FC<ButtonProps> 把组件签名和 props 契约绑在一起。

泛型让 hooks 保类型

useState/useRef 等自带泛型,传入初始值即可推断,或显式指定。

import { useState } from "react"; // 推断为 string const [name, setName] = useState(""); // 显式指定联合,初始可为 null const [user, setUser] = useState<{ id: number; name: string } | null>(null); // 自定义泛型 hook function useList<T>(initial: T[]): [T[], (v: T) => void] { const [list, setList] = useState(initial); const add = (v: T) => setList((p) => [...p, v]); return [list, add]; } const [nums, addNum] = useList<number>([1]); // nums: number[]

背景:想让一个列表 hook 服务多种数据类型且都保安全。操作:给 hook 加泛型 T。结果:用 numberaddNum 只接受 number。解读:泛型把"复用"与"安全"同时给到 hooks。

事件类型:别再 any EventHandler

function Input() { const onChange = (e: React.ChangeEvent<HTMLInputElement>) => { console.log(e.target.value); // 精确:e.target 是 input }; return <input onChange={onChange} />; }

React 提供全套事件类型(MouseEvent/ChangeEvent/FormEvent),精准描述 target 形状,比 any 强且不用手写。

这张图对比"无类型 props"与"类型化 props"的反馈差异:

07-01-fig01

Vue:组合式 API 类型化

import { ref, reactive, defineComponent } from "vue"; const count = ref(0); // 推断 Ref<number> const state = reactive({ name: "张三", age: 20 }); // 推断深层响应式 // defineComponent 让 props 获得类型 export default defineComponent({ props: { title: { type: String, required: true }, }, setup(props) { props.title; // 类型 string,必填 }, });

Vue 3 的组合式 API 对 TS 友好,ref/reactive 都能自动推断。组件用 defineComponent 包一层,模板里的 props 才有类型。

工程取舍:props 用 interface 还是 type

组件 props 通常用 interface(第三章),因为 props 常需要声明合并扩展(如高阶组件注入字段)。若项目统一用 type 也完全可行,关键是别混。

实战:用泛型约束列表组件的数据形状

列表类组件常要服务多种数据,用泛型让"每项形状"贯穿到 render 回调。

interface ListProps<T> { items: T[]; keyOf: (item: T) => string; render: (item: T) => React.ReactNode; } function List<T>({ items, keyOf, render }: ListProps<T>) { return <>{items.map((it) => <div key={keyOf(it)}>{render(it)}</div>)}</>; } // 使用时 T 被推断,render 里 it 有完整类型 <List items={[{ id: 1, n: "a" }]} keyOf={(x) => String(x.id)} render={(x) => x.n} />;

输入输出:传入 {id,n}[] 后,renderx 自动是 {id:number; n:string},拼错 x.z 立即标红。泛型把"列表项契约"从组件签名一路带到渲染回调,是 React + TS 最高频的复用模式之一。

实战:Vue 的 ref 解包与类型陷阱

ref 在模板里自动解包,但在 TS 逻辑里要用 .value 访问,两者类型不同,易踩。

import { ref } from "vue"; const count = ref(0); count.value; // number,逻辑里必须 .value // 在模板 <div>{{ count }}</div> 中自动解包为 number // 若把 ref 直接当 number 用(count + 1)会类型错

背景:新手常把 ref(0)number 直接用。操作:逻辑层始终 .value,模板层靠 Vue 自动解包。结果:类型对得上,运行也对得上。解读:ref 的"双层性"(包装对象 vs 解包值)是 Vue + TS 最该记牢的点,弄混会在编译期就报错,反而是好事。

React:Context 也能带泛型

跨组件共享数据时,createContext 配合泛型让"消费方拿到的 value"始终有类型。

import { createContext, useContext } from "react"; interface UserCtx { id: number; name: string; } const Ctx = createContext<UserCtx | null>(null); function useUser() { const u = useContext(Ctx); if (!u) throw new Error("需在 Provider 内使用"); return u; // 收窄后 u: UserCtx } // 消费处 u 有完整字段,拼错 u.age 立即标红

背景:全局用户、主题、配置常走 Context。操作:用泛型给 Context 定值类型,useContext 后收窄。结果:所有消费点共享同一契约。解读:Context 的"Provider/Consumer 解耦"容易让类型在消费端丢失,泛型把契约钉在创建处,下游一律受益。

Vue:emits 与事件载荷类型化

Vue 3 的 emits 可配合类型描述"子组件向上抛的事件及其载荷",让父组件监听处获得提示与检查。

import { defineComponent } from "vue"; export default defineComponent({ emits: { // 声明事件并校验载荷 submit: (payload: { id: number }) => typeof payload.id === "number", }, setup(_, { emit }) { emit("submit", { id: 1 }); // OK // emit("submit", { id: "x" }); // 报错:载荷类型不符 }, });

背景:子父通信靠自定义事件,载荷常是对象。操作:在 emits 里用函数形式描述载荷类型。结果:父组件监听 submit 时拿到 { id: number }。解读:把"事件契约"写进组件定义,向上通信也有类型护栏,和 props 向下通信对称。

框架无关的边界原则

无论 React 还是 Vue,类型收益集中在"组件边界":props/state/emits/context。组件内部实现细节不必过度类型化,边界清楚即可。

类型化重点(投入产出比高): - 组件入参(props / emits 载荷) - 共享状态(Context / store 形状) - 列表项与回调(render / keyOf 泛型) 类型化次要(不必强求): - 纯内部局部变量(TS 推断已够) - 一次性计算的中间值

背景:有人想"全组件都写满类型",反而淹没重点。操作:把类型预算花在边界。结果:收益最大、维护最省。解读:框架集成的类型策略,本质是"在边界设卡"的又一次应用——和全书的契约主线一致。

本节要点回顾

  • React 用 FC<Props> 绑定组件与 props 契约
  • 泛型 hook 同时给复用与安全
  • 用 React 自带事件类型,别回退 any
  • Vue 用 defineComponent + ref/reactive 推断

⚠️ 别在事件处理器里写 any 然后 e.target.value 随便取。React 事件类型能精确描述 target,放弃它等于主动丢掉 UI 层最便宜的一类保护。

💡 组件 props 优先 interface 并配 defineComponent/FC,让"改 props 名"这种重构能一次性红遍所有调用点,比靠测试覆盖更稳。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U