type-challenges 00006 Simple Vue 完全指南:computed 与 methods 里的 this 推断是怎么做到的
type-challenges 00006 Simple Vue 完全指南:computed 与 methods 里的 this 推断是怎么做到的【免费下载链接】type-challengesCollection of TypeScript type challenges with online judge项目地址: https://gitcode.com/GitHub_Trending/ty/type-challenges你有没有好奇过:为什么在 Vue 组件里,this能穿越出当前函数,直接摸到 data 和别的计算属性?在 type-challenges 仓库里,编号 00006 的 Hard 题 Simple Vue 让你用纯 TypeScript 复现这个 this 推断机制——只写一个函数签名,就让 computed 和 methods 内部的this各取所需。下面带你一步步拆开它。一、这道题真正想解决什么:让 this 看见整个组件写组件配置时,你期待在fullname()里直接this.firstname。但 TypeScript 的默认规则是:函数内部只能看见自己的参数,看不到外层对象字面量的其他字段。题目的设定是:实现一个SimpleVue,入参只有一个对象,包含data、computed、methods三个字段。三个字段对this的可见范围各不相同:data:返回的对象会挂到this上,但它自己看不见任何计算属性和方法;computed:函数以this为上下文,但结果要以普通值(而不是函数)暴露给上下文;methods:同样以this为上下文,且按函数原样暴露,还能访问 data、computed 的字段和其他方法。返回类型则完全不设限,测试只盯着配置对象内部的推断。起点模板就一行(见 template.ts):declare function SimpleVue(options: any): any你的任务,就是把这行的any换成能驱动推断的类型。二、先认识三个零件:泛型提取、ThisType 与条件类型⚙️ 这道题的全部技巧,其实是三件现成的 TypeScript 零件。先认清它们,再看组装,你会省掉一半试错。零件一:把字段拆成泛型参数。给函数声明D, C, M,再把三个字段分别标注为D、C、M。调用时传入对象字面量,类型系统会自动回填这三个泛型——data的返回对象被提取成D,你拿到的是真实结构而不是any。零件二:ThisTypeT。这是个内置标记类型,挂在对象字面量的类型上,就能改变这个对象里所有方法内部this的推断来源。它和函数类型的this参数不同:后者只作用于单个函数本身,而ThisType作用于整个对象(细节见第四节)。零件三:条件类型 infer提取返回值。写法长这样:C[K] extends (...args: any[]) infer R ? R : never // 从函数类型中捕获返回值 R,非函数成员兜底为 never它能把函数成员整体改造成值成员——这正是 computed 的规则:暴露结果,不暴露函数。三、三步搭出 this 上下文:拆泛型、注上下文、合体第一步:拆泛型,封住 data。declare function SimpleVueD, C, M(options: { data: (this: unknown) D // this 置为 unknown,data 内部谁都不许碰 computed: C // 先原样承接,下一步再加工 methods: M }): any注意这里:data用了函数this参数,写死为unknown。测试用例(test-cases.ts)里 data 内部有三处ts-expect-error,靠的就是访问属性必报错这一点。第二步:给 computed 注入上下文。computed: C ThisType D { [K in keyof C]: C[K] extends (...args: any[]) infer R ? R : never } // 映射类型把每个计算函数去函数化,只剩返回值 这一行的逻辑:用映射类型把C里每个函数键换成它的返回值,再和D交叉,最后用ThisType挂到C上。于是computed里的方法能读 data 字段、能读别的计算属性(值是 string 而不是函数),但摸不到 methods。第三步:methods 合体,多挂一份M。methods: M ThisType D { [K in keyof C]: C[K] extends (...args: any[]) infer R ? R : never } M // 与 computed 的上下文只差一个 M:多看见自己的兄弟方法 最终签名:declare function SimpleVueD, C, M(options: { data: (this: unknown) D computed: C ThisTypeD { [K in keyof C]: C[K] extends (...args: any[]) infer R ? R : never } methods: M ThisTypeD { [K in keyof C]: C[K] extends (...args: any[]) infer R ? R : never } M }): any丢个最小例子验证一下:const vm SimpleVue({ data() { // ts-expect-error this.firstname // 报错,符合预期 return { firstname: Type, amount: 10 } }, computed: { greeting() { return hi this.firstname // 正常:string }, }, methods: { hi() { alert(this.greeting) // 值,不是函数 this.amount // 直接读 data }, }, })三处行为(data 封闭、computed 值为普通类型、methods 互相可见)全部命中。四、几个为什么这样写才对的决策点 签名能过测试不稀奇,难的是解释每一步的取舍。挑四个最关键的。this: unknown而不是this: any。any会静默放行一切属性访问,ts-expect-error会因没报错而反过来炸掉编译;unknown访问任何属性都报错,才是测试要的封闭。ThisType与函数this参数分工不同。函数this参数只约束它自己,ThisType约束整个对象字面量的所有方法。本题两处都用:前者封data,后者开computed和methods。用交叉类型C ThisType...而不是直接替换C。交叉只附加推断信息,原始类型信息(键名、函数形态)原样保留,副作用最小。条件类型优于内置ReturnType。ReturnType遇到any等边缘输入时行为不稳定,而条件类型版本对非函数分支能显式落到never,边界更可控。顺带一提,仓库的工具类型(utils/index.d.ts)里的Equal、Expect等断言,本身就是条件类型 infer的同类应用,可当参照。五、从一道题到一类题:这套模式的复用价值这道题的真正分量在于:它浓缩了框架级声明文件的通用配方——泛型拆分、上下文注入、形态转换三件套。进阶路径就一步之遥。info.yml 里标注了关联题 213,即 00213 Vue Basic Props:在 Simple Vue 基础上加一个props字段,规则更细——props 注入的键在data、computed、methods全部上下文可见(不再是封闭的 data);prop 值由构造函数或{ type: ... }对象定义,props: { foo: Boolean }推断出{ foo: boolean },多个构造函数则推断为联合类型。把本题的上下文注入,叠加一层 prop 类型映射,就接近真实defineComponent的声明形态了。往工程上迁移,同配方还能用在:给 Options API 风格组件库写类型、为配置驱动的中间件设计this上下文、给 Pinia 风格的 store 推导 getters 与 actions 的互相引用。题目变了,套路不变。一句话收束:Simple Vue 这道题,本质上是在练习如何用类型系统给嵌套函数发一张上下文通行证。下一步,去 00213 Vue Basic Props 把props加进你的签名,看看这套三件套如何无缝扩展。【免费下载链接】type-challengesCollection of TypeScript type challenges with online judge项目地址: https://gitcode.com/GitHub_Trending/ty/type-challenges创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考