在vue2中使用ts

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"介绍"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"文本所介绍的内容是使用 TypeScript 编写 Vue2.6.11 前端应用,具体 demo 地址可访问: "},{"type":"link","attrs":{"href":"https://github.com/hy08/all-demo/tree/master/vue-demo","title":null},"content":[{"type":"text","text":"vue-ts-demo"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"总结几个月来在 ts 环境 中使用 vue 的经验,提炼一个最小可运行案例,该案例将包括:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"搭建 ts 项目,配置 tsconfig.json"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"单文件组件(template 组件)的使用"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"tsx 组件的使用"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"vue-router 的 ts 方案"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":5,"align":null,"origin":null},"content":[{"type":"text","text":"vuex 的 ts 方案"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":6,"align":null,"origin":null},"content":[{"type":"text","text":"api 类型定义的建议"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"项目搭建与配置"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ts 环境下 vue2 版本的项目可直接使用官方的脚手架 vue-cli 进行搭建,根据项目组情况判断是否需要使用 tsx、css 预处理+css module、单元测试。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"项目创建完成,默认生成一份"},{"type":"codeinline","content":[{"type":"text","text":"tsconfig.json"}]},{"type":"text","text":"文件。ts 配置项解释可以参考"},{"type":"link","attrs":{"href":"https://www.tslang.cn/docs/handbook/tsconfig-json.html","title":null},"content":[{"type":"text","text":"TypeScript 官方教程"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在"},{"type":"codeinline","content":[{"type":"text","text":"package.json"}]},{"type":"text","text":"中默认安装"},{"type":"link","attrs":{"href":"https://github.com/vuejs/vue-class-component","title":null},"content":[{"type":"text","text":"vue-class-component"}]},{"type":"text","text":",该库通过装饰器模式实现了 vue 的 ts 适配,也是官方推荐的使用 ts 方式。不过更建议使用"},{"type":"link","attrs":{"href":"https://github.com/kaorun343/vue-property-decorator","title":null},"content":[{"type":"text","text":"vue-property-decorator"}]},{"type":"text","text":"包,因为后者在前者基础上进行了修改与扩充。"},{"type":"codeinline","content":[{"type":"text","text":"vue-class-component"}]},{"type":"text","text":"拥有的功能"},{"type":"codeinline","content":[{"type":"text","text":"vue-property-decorator"}]},{"type":"text","text":"都具备,并且功能更强大,也更易于使用。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"对于使用 Vuex 的项目,建议安装"},{"type":"link","attrs":{"href":"https://github.com/championswimmer/vuex-module-decorators","title":null},"content":[{"type":"text","text":"vuex-module-decorators"}]},{"type":"text","text":"包,这是在 ts 环境下中使用 vuex 的一种解决方案。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由于 vue 对 jsx 的支持问题,如果想实现如同 react 的组件 props 的智能提示,需要安装"},{"type":"link","attrs":{"href":"https://github.com/wonderful-panda/vue-tsx-support","title":null},"content":[{"type":"text","text":"vue-tsx-support"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"单文件组件(template 组件)的使用"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"组件实例"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"vue-class-component 允许我们通过使用类语法声明 vue 组件,需要使用"},{"type":"codeinline","content":[{"type":"text","text":"@Component"}]},{"type":"text","text":"装饰器。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":" import { Vue, Component } from 'vue-property-decorator';\n\n @Component\n export default class Index extends Vue {\n\n }\n\n //相当于\n \n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"生命周期"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"生命周期钩子的使用和原先使用的区别:在类语法中直接将生命周期生命为方法(方法名称和生命周期名称一致)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":" import { Vue, Component } from 'vue-property-decorator';\n\n @Component\n export default class Index extends Vue {\n created() {\n console.log('created');\n }\n mounted() {\n console.log('mounted');\n }\n }\n\n //相当于\n \n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"响应式数据 data"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"类语法中可以直接定义为类的实例属性作为组件的响应式数据。原始类型的数据不需要定义类型,ts 可以实现类型推断,但是复杂的类型需要定义。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其中值得注意的一点是:当数据的值是 undefined 或者只定义未赋初值,"},{"type":"codeinline","content":[{"type":"text","text":"vue-class-component"}]},{"type":"text","text":"不会将该属性修饰为响应式数据!这会导致异常。推荐方案是进行赋初值,或者扩展一个 null 类型再赋值未 null。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":" import { Vue, Component } from 'vue-property-decorator';\n\n type User = {\n name: string;\n age: number;\n };\n @Component\n export default class Index extends Vue {\n message = 'hello world';\n info: User = { name: 'test', age: 25 };\n //如果数据的值是undefined或者未赋初值,则不会成为响应式数据。解决方案:追加类型定义null\n count: number;\n }\n\n //相当于\n \n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"计算属性 computed"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"类语法中的计算属性的实现,是通过 get 取值函数。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":" import { Vue, Component } from 'vue-property-decorator';\n\n @Component\n export default class Index extends Vue {\n //computed定义\n get introduction() {\n return `姓名:${this.info.name}, 年龄:${this.info.age}`;\n }\n }\n\n //相当于\n \n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"数据监听 watch"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"类语法实现响应式的数据监听,是由"},{"type":"codeinline","content":[{"type":"text","text":"vue-property-decorator"}]},{"type":"text","text":"依赖提供"},{"type":"codeinline","content":[{"type":"text","text":"@Watch"}]},{"type":"text","text":"装饰器来完成"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":" import { Vue, Component } from 'vue-property-decorator';\n\n @Component\n export default class Index extends Vue {\n //watch定义,其中Wacth装饰器第一个参数:响应式数据字符串(也可以定义为'a.b');\n //第二个参数options成员[immediate,deep]分别对应的是原生的用法\n @Watch('$route', { immediate: true })\n changeRouter(val: Route, oldVal: Route) {\n console.log('$route watcher: ', val, oldVal);\n }\n }\n\n //相当于\n \n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"方法 methods"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在类语法实现原生 vue 的方法的方式,即通过直接定义类方法成员。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":" import { Vue, Component } from 'vue-property-decorator';\n\n @Component\n export default class Index extends Vue {\n hello(){\n console.log('hello world');\n }\n }\n\n //相当于\n \n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"引入组件"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"和原生写法一致,都需要先引入在注册,区别在于类语法注册在修饰器中。组件使用方式和 vue 原生一致。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":" import { Vue, Component } from 'vue-property-decorator';\n import Header from '../component/header/index.vue';\n\n @Component({\n components: {\n Header,\n },\n })\n export default class Index extends Vue {\n }\n\n //相当于\n \n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"组件属性 props"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"类语法实现组件 props 定义是通过装饰器"},{"type":"codeinline","content":[{"type":"text","text":"@Prop"}]},{"type":"text","text":"实现"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":" import { Vue, Component, Prop } from 'vue-property-decorator';\n import { User } from '@/types/one';\n\n @Component\n export default class Header extends Vue {\n @Prop({ type: String, default: '标题' }) readonly title?: string;\n //复杂类型type参数的值为Object,默认值需要以函数形式返回\n @Prop({ type: Object, default: () => ({ name: '-', age: '-' }) }) readonly author!: User;\n }\n\n\n //相当于\n \n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"事件触发"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ts 环境下 vue 的事件触发方式和 js 环境下是一致的,区别只是事件回调定义的地方不同(ts 定义为类的实例方法,js 定义在 methods 属性中)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"ref 使用"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在类语法中使用 ref 需要借助"},{"type":"codeinline","content":[{"type":"text","text":"vue-property-decorator"}]},{"type":"text","text":"提供的"},{"type":"codeinline","content":[{"type":"text","text":"@Ref"}]},{"type":"text","text":"装饰器,使用方法如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"//模板和原生vue保持一致\n\n\n\n\n//相当于\n\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"mixins 使用"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"类语法使用 mixins 需要继承"},{"type":"codeinline","content":[{"type":"text","text":"vue-property-decorator"}]},{"type":"text","text":"提供的 Mixins 函数所生成的类。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Mixins 函数的参数是 Vue 实例类,正确使用会用 mixin 成员的的智能提示,使用方式如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"// mixins.js\n import Vue from 'vue';\n import Component from 'vue-class-component';\n\n // You can declare mixins as the same style as components.\n @Component\n export class Hello extends Vue {\n /**\n * mixin中的响应式数据\n */\n mixinText = 'Hello mixins';\n\n obj: { name: string } = { name: 'han' };\n }\n\n//index.vue\n\n\n//相当于\n\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"slots 和 scopedSlots"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"slots 和 scopedSlots 的使用方式和原生 vue 保持一致。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"tsx 组件的使用"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果在项目中需要使用 jsx,默认 vue-cli 创建项目会提示是否支持 jsx,但是由于 vue 对 jsx 的支持不完善,导致在使用不像 react 那样可以提示组件 props 的类型定义,使用上非常难受。因此引入"},{"type":"codeinline","content":[{"type":"text","text":"vue-tsx-support"}]},{"type":"text","text":"解决该问题。详情请见:"},{"type":"link","attrs":{"href":"https://github.com/wonderful-panda/vue-tsx-support","title":null},"content":[{"type":"text","text":"vue-tsx-support(github 文档)"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"至于 在 vue 中如何使用 jsx,推荐"},{"type":"link","attrs":{"href":"https://zhuanlan.zhihu.com/p/37920151","title":null},"content":[{"type":"text","text":"在 Vue 中使用 JSX 的正确姿势"}]},{"type":"text","text":",该文详细介绍了 vue 实现 jsx 的原理以及几种 props 的区别和使用。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"tsx 组件的很多地方和 template 组件使用方式一致,但是 props 定义、scopedSlots 定义和使用,以及引入第三方组件之后的处理方式有差异。其他地方例如生命周期、data、computed、watch、methods、事件触发、ref 使用都是一致的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"配置"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下载完"},{"type":"codeinline","content":[{"type":"text","text":"vue-tsx-support"}]},{"type":"text","text":",我们需要配置"},{"type":"codeinline","content":[{"type":"text","text":"tsconfig.json"}]},{"type":"text","text":",设置内容如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":" \"compilerOptions\": {\n \"jsx\": \"preserve\",\n \"jsxFactory\": \"VueTsxSupport\",\n \"...\": \"...\"\n },\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"之后,我们需要在项目入口处引入"},{"type":"codeinline","content":[{"type":"text","text":"import 'vue-tsx-support/enable-check';"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"现在 tsx 组件的 props 智能提示开始生效。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"组件定义的方式"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"vue-tsx-support"}]},{"type":"text","text":"支持的 tsx 组件定义方式可以使用类似与原生 vue 的对象的写法,或者类语法编写。更推荐使用类语法编写组件,这样和模板写法也更相近。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果喜欢接近原生 vue 的对象风格,可以参考:"},{"type":"link","attrs":{"href":"https://github.com/wonderful-panda/vue-tsx-support#writing-components-by-object-style-api-like-vueextend","title":null},"content":[{"type":"text","text":"官方文档"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用类语法编写组件有两种方式:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"通过继承"},{"type":"codeinline","content":[{"type":"text","text":"vue-tsx-support"}]},{"type":"text","text":"提供的 Component 类来编写"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"通过继承 Vue 类并且声明"},{"type":"codeinline","content":[{"type":"text","text":"_tsx"}]},{"type":"text","text":"成员"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"项目中一直在使用前者,但是最近总结经验,发现后者更好些。主要是继承 Component 之后使用 mixins 想要有智能提示的话,需要将定义挂载在 Vue 上,不够友好。因此推荐使用:"},{"type":"text","marks":[{"type":"strong"}],"text":"通过继承 Vue 类并且声明"},{"type":"codeinline","content":[{"type":"text","text":"_tsx"}],"marks":[{"type":"strong"}]},{"type":"text","marks":[{"type":"strong"}],"text":"成员"},{"type":"text","text":",下文都是针对该方案的说明。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"组件实例"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"声明 tsx 组件,文件后缀必须为"},{"type":"codeinline","content":[{"type":"text","text":".tsx"}]},{"type":"text","text":",这点和 react 不同,react 在 ts 文件中也是可以使用 jsx 的,但是 vue 不可以。如果一定要在"},{"type":"codeinline","content":[{"type":"text","text":".ts"}]},{"type":"text","text":"文件中,可以使用定义 jsx 原始方式,具体可参照"},{"type":"link","attrs":{"href":"https://cn.vuejs.org/v2/guide/render-function.html#%E5%AE%8C%E6%95%B4%E7%A4%BA%E4%BE%8B","title":null},"content":[{"type":"text","text":"vue 官网:"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 tsx 文件中,声明组件的方式和 template 组件是一致的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"import { Vue, Component, Prop } from 'vue-property-decorator';\nimport * as tsx from 'vue-tsx-support';\n\n@Component\nexport default class Header extends Vue {\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"dataProps 定义"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先我们需要和 template 组件一样将所有的 props 定义好。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然后根据情况,如果可以将所有 data 数据、computed 方法、方法定义设置为私有,这样可以使用"},{"type":"codeinline","content":[{"type":"text","text":"vue-tsx-support"}]},{"type":"text","text":"提供的"},{"type":"codeinline","content":[{"type":"text","text":"AutoProps别名"}]},{"type":"text","text":",来声明 Props。如果有成员需要设置为 public,可以使用 tsx 提供的"},{"type":"codeinline","content":[{"type":"text","text":"PickProps别名"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"//AutoProps\nimport { Vue, Component, Prop } from 'vue-property-decorator';\nimport * as tsx from 'vue-tsx-support';\nimport { User } from '@/types/one';\nimport styles from './index.less';\n\n@Component\nexport default class Header extends Vue {\n _tsx!: tsx.DeclareProps>;\n\n @Prop({ type: String, default: '标题' }) readonly title?: string;\n @Prop({ type: Object, default: () => ({ name: '-', age: '-' }) }) readonly author!: User;\n\n private goAboutMe() {\n this.$router.push('/about');\n }\n\n render() {\n return (\n
\n
\n

{this.title}

\n \n 作者:\n {this.author.name}\n \n
\n
\n );\n }\n}\n\n//PickProps\nimport { Vue, Component, Prop } from 'vue-property-decorator';\nimport * as tsx from 'vue-tsx-support';\nimport { User } from '@/types/one';\nimport styles from './index.less';\n\n@Component\nexport default class Header extends Vue {\n _tsx!: tsx.DeclareProps>;\n\n @Prop({ type: String, default: '标题' }) readonly title?: string;\n @Prop({ type: Object, default: () => ({ name: '-', age: '-' }) }) readonly author!: User;\n\n goAboutMe() {\n this.$router.push('/about');\n }\n\n render() {\n return (\n
\n
\n

{this.title}

\n \n 作者:\n {this.author.name}\n \n
\n
\n );\n }\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"eventProps 定义"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"_tsx"}]},{"type":"text","text":"成员的类型可以定义为交叉类型,将事件类型定义混入到"},{"type":"codeinline","content":[{"type":"text","text":"_tsx"}]},{"type":"text","text":"中就可以了"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"import * as tsx from 'vue-tsx-support';\nimport { Vue, Component, Prop } from 'vue-property-decorator';\nexport default class Header extends Vue {\n _tsx!: tsx.DeclareProps> & tsx.DeclareOnEvents;\n render(){\n return
\n }\n}\n\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"scopedSlotsProps 定义"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"vue 中的 scopedSlots 相当于 react 中的 renderProp。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"tsx 组件中定义如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"import * as tsx from 'vue-tsx-support';\nimport { Vue, Component, Prop } from 'vue-property-decorator';\nexport default class Header extends Vue {\n //这样就声明了两个scopedSlot,默认的scopedSlot参数类型为空,header参数类型为string\n $scopedSlots!: tsx.InnerScopedSlots;\n render(){\n return
\n }\n}\n\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"mixins 使用"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"mixins 使用和 template 组件保持一致"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"第三方组件 props 推断"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由于 vue 实现的 jsx 没有参数类型提示,因此引入第三方组件也是没有 props 提示。所有我们需要使用"},{"type":"codeinline","content":[{"type":"text","text":"vue-tsx-support"}]},{"type":"text","text":"来进行 jsx 支持。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这里我创建一份"},{"type":"codeinline","content":[{"type":"text","text":"propsCovert.ts"}]},{"type":"text","text":"文件,使用"},{"type":"codeinline","content":[{"type":"text","text":"vue-tsx-support"}]},{"type":"text","text":"提供的 ofType 方法来对第三方组件的 props 进行定义推导。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"递归第三方组件的 dataProps,并将其类型推导出。eventProps 定义为索引类型,参数类型定义为 any。scopedSlotsProps 同样定义为索引类型,参数类型定义为 any。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"之后每次使用第三方组件,只要用 antdPropsConvert 方法包装下即可在使用时得到 props 的智能提示。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果是单页应用,也可以创建一份组件清单文件,在该文件中转换所有的组件并导出,这样就省的一次次转换。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"//propsConvert.ts\n\nimport { ofType } from 'vue-tsx-support';\n\ntype PowerPartial = {\n // 如果是 object,则递归类型\n [U in keyof T]?: T[U] extends Function ? Function : T[U] extends object ? PowerPartial : T[U];\n};\ntype Omit = Pick>;\ntype OmitVue = PowerPartial>;\n\ninterface AnyEvent {\n [key: string]: any;\n}\ninterface AnyScopedSlots {\n [key: string]: any;\n}\n\nfunction antdPropsConvert(componentType: new (...args: any[]) => T) {\n return ofType, AnyEvent, AnyScopedSlots>().convert(componentType);\n}\nexport { antdPropsConvert };\n\n\n// sider.tsx\nimport { Vue, Component } from 'vue-property-decorator';\nimport * as tsx from 'vue-tsx-support';\nimport { Button as AButton } from 'ant-design-vue';\nimport styles from './index.less';\nimport { antdPropsConvert } from '@/utils/propsConvert';\n\nconst Button = antdPropsConvert(AButton);\n\n@Component\nexport default class Sider extends Vue {\n _tsx!: tsx.DeclareOnEvents;\n\n $scopedSlots!: tsx.InnerScopedSlots;\n\n render() {\n return (\n
\n {this.$scopedSlots.default && this.$scopedSlots.default()}\n
\n \n
\n
\n );\n }\n}\n\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"事件修饰符"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如何在 tsx 组件中使用事件修饰符,推荐官方教程,"},{"type":"link","attrs":{"href":"https://github.com/wonderful-panda/vue-tsx-support#modifiers","title":null},"content":[{"type":"text","text":"modifiers"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"遗留问题"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在单文件组件模式中,文件跳转正常(ctrl+鼠标点击可以跳转到定义),但是暂未实现路径的智能提示。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在"},{"type":"codeinline","content":[{"type":"text","text":".tsx | .ts"}]},{"type":"text","text":"文件引入"},{"type":"codeinline","content":[{"type":"text","text":".vue"}]},{"type":"text","text":",路径智能提示正常,但是会发生无法跳转到 vue 文件的情况。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"vue-router 的 ts 方案"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"vue-router"}]},{"type":"text","text":"官方已经支持 ts,在我们使用"},{"type":"codeinline","content":[{"type":"text","text":"vue-cli"}]},{"type":"text","text":"创建了 ts 项目之后就可以使用。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但是如果我们需要在组件中定义路由钩子函数,需要先在全局进行注册"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"// class-component-hooks.js\nimport Component from 'vue-class-component';\n\n// Register the router hooks with their names\nComponent.registerHooks(['beforeRouteEnter', 'beforeRouteLeave', 'beforeRouteUpdate']);\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然后需要给 Vue 类型扩展定义"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"import Vue from 'vue';\nimport { Route, NavigationGuardNext } from 'vue-router';\ndeclare module 'vue/types/vue' {\n // Augment component instance type\n interface Vue {\n beforeRouteEnter?(to: Route, from: Route, next: NavigationGuardNext): void;\n\n beforeRouteLeave?(to: Route, from: Route, next: NavigationGuardNext): void;\n\n beforeRouteUpdate?(to: Route, from: Route, next: NavigationGuardNext): void;\n }\n}\n\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用前,在项目的入口文件引入注册文件即可。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"import '@/utils/class-component-hooks';\nimport Vue from 'vue';\nimport 'vue-tsx-support/enable-check';\nimport App from './App';\nimport router from './router';\nimport store from '@/modules';\nVue.config.productionTip = false;\nnew Vue({\n router,\n store,\n render: (h) => h(App),\n}).$mount('#app');\n\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然后在组件中定义路由钩子,即可获得准确的提示。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"vuex 的 ts 方案"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"为了在 ts 环境中使用 vuex,vue 社区推出了"},{"type":"link","attrs":{"href":"https://github.com/championswimmer/vuex-module-decorators","title":null},"content":[{"type":"text","text":"vuex-module-decorators"}]},{"type":"text","text":",其工作方式和"},{"type":"codeinline","content":[{"type":"text","text":"vue-property-decorator"}]},{"type":"text","text":"一致,都是通过装饰器来实现。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"模块创建"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"vuex-module-decorators"}]},{"type":"text","text":"中常使用的成员:"},{"type":"codeinline","content":[{"type":"text","text":"VuexModule, Module, Mutation, Action, getModule"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"创建步骤:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"定义 Module 实例之前,我们需要先定义 state 的接口,这是为了之后"},{"type":"codeinline","content":[{"type":"text","text":"vuex-module-decorators"}]},{"type":"text","text":"进行类型检测。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"自定义 Module 类型,继承 VuexModule 类型,并实现 state 的接口"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"使用"},{"type":"codeinline","content":[{"type":"text","text":"@Module"}]},{"type":"text","text":"装饰器装饰自定义 module,如果是动态 Module(意味着引入的时候自动注入到 vuex 中),需要传参"},{"type":"codeinline","content":[{"type":"text","text":"dynamic, store, name"}]},{"type":"text","text":"给 Module 函数"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"定义 action 和 mutation 我们都需要使用对应的装饰器"},{"type":"codeinline","content":[{"type":"text","text":"@Action、@Mutation"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":5,"align":null,"origin":null},"content":[{"type":"text","text":"导出自定义 Module,将自定义 Module 作为函数参数传递给 getModule 函数,该 module 中所有的 state,action,mutation 都绑定在导出对象上"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"完整示例:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"import { VuexModule, Module, Mutation, Action, getModule } from 'vuex-module-decorators';\nimport store from './index';\n\ntype TodoItem = {\n id: string;\n content: string;\n isDone: boolean;\n};\ntype TodoListState = {\n todos: TodoItem[];\n};\nconst todos: TodoItem[] = [\n {\n id: '0',\n content: 'todo-item1',\n isDone: false,\n },\n {\n id: '1',\n content: 'todo-item2',\n isDone: true,\n },\n {\n id: '2',\n content: 'todo-item3',\n isDone: false,\n },\n];\n@Module({ dynamic: true, store, name: 'todoListModule' })\nclass TodoListModule extends VuexModule implements TodoListState {\n todos: TodoItem[] = [];\n\n //获取当前的todoList\n @Action\n async getAllTodoItems() {\n const data = await new Promise((resolve) => {\n setTimeout(resolve, 1000, todos);\n });\n this._saveTodos(data);\n }\n\n @Mutation\n private _saveTodos(data: TodoItem[]) {\n this.todos = data;\n }\n}\nexport default getModule(TodoListModule);\n\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"store 创建和使用"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"创建 store 实例,由于项目是使用动态导入 module,因此很简洁。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果需要在入口文件定义好全部 module,可以参照"},{"type":"link","attrs":{"href":"https://github.com/championswimmer/vuex-module-decorators#usage","title":null},"content":[{"type":"text","text":"官方教程"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"import Vue from 'vue';\nimport Vuex from 'vuex';\n\nVue.use(Vuex);\n\n// Declare empty store first, dynamically register all modules later.\nconst Store = new Vuex.Store({});\nexport default Store;\n\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"vuex 使用和原生 vue 一致,都是引入 store 的入口文件,然后将其传入 Vue 实例中"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"在组件中使用 vuex(动态导入 Module)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用步骤:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"需要导入对应的 module 文件"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"导入 state,因为 state 成员通过计算属性使用,因此在 ts 中需要通过 get 函数导入"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"调用 action 或者 mutation 方法,直接调用对应的 Module 即可"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"import { Component, Vue } from 'vue-property-decorator';\nimport TodoListModule from '@/modules/todoList';\n\n@Component\nexport default class Index extends Vue {\n get todos() {\n return TodoListModule.todos;\n }\n\n created() {\n TodoListModule.getAllTodoItems().then(() => {\n console.log('todos', this.todos);\n });\n }\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"api 类型定义的建议"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在项目中,定义 api 接口的类型是个麻烦事,尤其是接口很多的情况下。如果手动定义,成本会很大,也影响效率。当接口修改(这是常常发生的),我们将不得不进行同步的修正。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因此我建议使用阿里团队出品的"},{"type":"codeinline","content":[{"type":"text","text":"pont"}]},{"type":"text","text":"库,该库有效的解决了 api 接口定义的麻烦问题。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"详情请见官网:"},{"type":"link","attrs":{"href":"https://github.com/alibaba/pont","title":null},"content":[{"type":"text","text":"pont"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章