Vue 2.x 如何接入

  • vue-property-decorator
  • vue-class-component
  • vuex-class

依赖

npm install —save-dev typescript webpack webpack-cli ts-loader css-loader vue vue-loader vue-template-compiler

webpack.config.js

  1. var path = require('path')
  2. var webpack = require('webpack')
  3. const VueLoaderPlugin = require('vue-loader/lib/plugin')
  4. module.exports = {
  5. entry: './src/index.ts',
  6. output: {
  7. path: path.resolve(__dirname, './dist'),
  8. publicPath: '/dist/',
  9. filename: 'build.js'
  10. },
  11. module: {
  12. rules: [
  13. {
  14. test: /\.vue$/,
  15. loader: 'vue-loader',
  16. options: {
  17. loaders: {
  18. // Since sass-loader (weirdly) has SCSS as its default parse mode, we map
  19. // the "scss" and "sass" values for the lang attribute to the right configs here.
  20. // other preprocessors should work out of the box, no loader config like this necessary.
  21. 'scss': 'vue-style-loader!css-loader!sass-loader',
  22. 'sass': 'vue-style-loader!css-loader!sass-loader?indentedSyntax',
  23. }
  24. // other vue-loader options go here
  25. }
  26. },
  27. {
  28. test: /\.tsx?$/,
  29. loader: 'ts-loader',
  30. exclude: /node_modules/,
  31. options: {
  32. appendTsSuffixTo: [/\.vue$/],
  33. }
  34. },
  35. {
  36. test: /\.(png|jpg|gif|svg)$/,
  37. loader: 'file-loader',
  38. options: {
  39. name: '[name].[ext]?[hash]'
  40. }
  41. },
  42. {
  43. test: /\.css$/,
  44. use: [
  45. 'vue-style-loader',
  46. 'css-loader'
  47. ]
  48. }
  49. ]
  50. },
  51. resolve: {
  52. extensions: ['.ts', '.js', '.vue', '.json'],
  53. alias: {
  54. 'vue$': 'vue/dist/vue.esm.js'
  55. }
  56. },
  57. devServer: {
  58. historyApiFallback: true,
  59. noInfo: true
  60. },
  61. performance: {
  62. hints: false
  63. },
  64. devtool: '#eval-source-map',
  65. plugins: [
  66. // make sure to include the plugin for the magic
  67. new VueLoaderPlugin()
  68. ]
  69. }
  70. if (process.env.NODE_ENV === 'production') {
  71. module.exports.devtool = '#source-map'
  72. // http://vue-loader.vuejs.org/en/workflow/production.html
  73. module.exports.plugins = (module.exports.plugins || []).concat([
  74. new webpack.DefinePlugin({
  75. 'process.env': {
  76. NODE_ENV: '"production"'
  77. }
  78. }),
  79. new webpack.optimize.UglifyJsPlugin({
  80. sourceMap: true,
  81. compress: {
  82. warnings: false
  83. }
  84. }),
  85. new webpack.LoaderOptionsPlugin({
  86. minimize: true
  87. })
  88. ])
  89. }

tsconfig.json

使用 tsc —init 生成 tsconfig.json

  1. {
  2. "compilerOptions": {
  3. "outDir": "./built/",
  4. "sourceMap": true,
  5. "strict": true,
  6. "noImplicitReturns": true,
  7. "module": "es2015",
  8. "moduleResolution": "node",
  9. "target": "es5"
  10. },
  11. "include": [
  12. "./src/**/*"
  13. ]
  14. }

helloComponent.ts

  1. // src/components/Hello.ts
  2. import Vue from "vue";
  3. export default Vue.extend({
  4. template: `
  5. <div>
  6. <div>Hello {{name}}{{exclamationMarks}}</div>
  7. <button @click="decrement">-</button>
  8. <button @click="increment">+</button>
  9. </div>
  10. `,
  11. props: ['name', 'initialEnthusiasm'],
  12. data() {
  13. return {
  14. enthusiasm: this.initialEnthusiasm,
  15. }
  16. },
  17. methods: {
  18. increment() { this.enthusiasm++; },
  19. decrement() {
  20. if (this.enthusiasm > 1) {
  21. this.enthusiasm--;
  22. }
  23. },
  24. },
  25. computed: {
  26. exclamationMarks(): string {
  27. return Array(this.enthusiasm + 1).join('!');
  28. }
  29. }
  30. });

使用单文件组件

告诉 TypeScript .vue文件在导入时是什么样子,新增 vue-shim.d.ts 文件。

我们不需要将该文件导入到任何地方。

它自动包含在 TypeScript 中,并告诉它导入的以 . Vue 结尾的任何内容与 Vue 构造函数本身具有相同的形状。

  1. // src/vue-shims.d.ts
  2. declare module "*.vue" {
  3. import Vue from "vue";
  4. export default Vue;
  5. }

第一个单文件组件

  1. <!-- src/components/Hello.vue -->
  2. <template>
  3. <div>
  4. <div class="greeting">Hello {{name}}{{exclamationMarks}}</div>
  5. <button @click="decrement">-</button>
  6. <button @click="increment">+</button>
  7. </div>
  8. </template>
  9. <script lang="ts">
  10. import Vue from "vue";
  11. export default Vue.extend({
  12. props: ['name', 'initialEnthusiasm'],
  13. data() {
  14. return {
  15. enthusiasm: this.initialEnthusiasm,
  16. }
  17. },
  18. methods: {
  19. increment() { this.enthusiasm++; },
  20. decrement() {
  21. if (this.enthusiasm > 1) {
  22. this.enthusiasm--;
  23. }
  24. },
  25. },
  26. computed: {
  27. exclamationMarks(): string {
  28. return Array(this.enthusiasm + 1).join('!');
  29. }
  30. }
  31. });
  32. </script>
  33. <style>
  34. .greeting {
  35. font-size: 20px;
  36. }
  37. </style>

使用装饰器

组件也可以使用装饰器来定义。

在两个附加包的帮助下(vue-class-component和vue-property-decorator),我们的组件可以按照以下方式重写:

  1. import { Vue, Component, Prop } from "vue-property-decorator";
  2. @Component
  3. export default class HelloDecorator extends Vue {
  4. @Prop() name!: string;
  5. @Prop() initialEnthusiasm!: number;
  6. enthusiasm = this.initialEnthusiasm;
  7. increment() {
  8. this.enthusiasm++;
  9. }
  10. decrement() {
  11. if (this.enthusiasm > 1) {
  12. this.enthusiasm--;
  13. }
  14. }
  15. get exclamationMarks(): string {
  16. return Array(this.enthusiasm + 1).join('!');
  17. }
  18. }

vue-class-component


vue-class-component 对 Vue 组件进行了一层封装,让 Vue 组件语法在结合了 TypeScript 语法之后更加扁平化:**

  1. <template>
  2. <div>
  3. <input v-model="msg">
  4. <p>prop: {{propMessage}}</p>
  5. <p>msg: {{msg}}</p>
  6. <p>helloMsg: {{helloMsg}}</p>
  7. <p>computed msg: {{computedMsg}}</p>
  8. <button @click="greet">Greet</button>
  9. </div>
  10. </template>
  11. <script>
  12. import Vue from 'vue'
  13. import Component from 'vue-class-component'
  14. @Component({
  15. props: {
  16. propMessage: String
  17. }
  18. })
  19. export default class App extends Vue {
  20. // initial data
  21. msg = 123
  22. // use prop values for initial data
  23. helloMsg = 'Hello, ' + this.propMessage
  24. // lifecycle hook
  25. mounted () {
  26. this.greet()
  27. }
  28. // computed
  29. get computedMsg () {
  30. return 'computed ' + this.msg
  31. }
  32. // method
  33. greet () {
  34. alert('greeting: ' + this.msg)
  35. }
  36. }
  37. </script>

vue-property-decorator

vue-property-decorator 是在 vue-class-component 基础上增强了更多的结合 Vue 特性的装饰器,新增了这 7 个装饰器

  • @Emit 指定事件 emit,可以使用此修饰符,也可以直接使用 this.$emit()
  • @Inject 指定依赖注入)
  • @Mixins mixin 注入
  • @Model 指定 model
  • @Prop 指定 Prop
  • @Provide 指定 Provide
  • @Watch 指定 Watch
  • @Component export from vue-class-component

Vue 2.x + TypeScript - 图1

  1. import { Component, Emit, Inject, Model, Prop, Provide, Vue, Watch } from 'vue-property-decorator'
  2. @Component
  3. export class MyComponent extends Vue {
  4. @Prop()
  5. propA: number = 1
  6. @Prop({ default: 'default value' })
  7. propB: string
  8. @Prop([String, Boolean])
  9. propC: string | boolean
  10. @Prop({ type: null })
  11. propD: any
  12. @Watch('child')
  13. onChildChanged(val: string, oldVal: string) { }
  14. }
  15. // 相当于
  16. export default {
  17. props: {
  18. checked: Boolean,
  19. propA: Number,
  20. propB: {
  21. type: String,
  22. default: 'default value'
  23. },
  24. propC: [String, Boolean],
  25. propD: { type: null }
  26. }
  27. methods: {
  28. onChildChanged(val, oldVal) { }
  29. },
  30. watch: {
  31. 'child': {
  32. handler: 'onChildChanged',
  33. immediate: false,
  34. deep: false
  35. }
  36. }
  37. }

文件样式指南

api 顺序

vue 文件中 TS 上下文顺序

  • data
  • @Prop
  • @State
  • @Getter
  • @Action
  • @Mutation
  • @Watch
  • 生命周期钩子
    • beforeCreate(按照生命周期钩子从上到下)
    • created
    • beforeMount
    • mounted
    • beforeUpdate
    • updated
    • activated
    • deactivated
    • beforeDestroy
    • destroyed
    • errorCaptured(最后一个生命周期钩子)

vuex

store 下面一个文件夹对应一个模块,每一个模块都有一个 interface 进行接口管理

Vue 大型 TS 项目实践经验

1 业务层面千万做好类型检测或者枚举定义,这样不仅便利了开发,还能在出了问题的时候迅速定位 2 如果定义了 .d.ts 文件,请重新启动服务让你的服务能够识别你定义的模块,并重启 vscode 让编辑器也能够识别 3 设置好你的 tsconfig ,比如记得把 strictPropertyInitialization 设为 false,不然你定义一个变量就必须给它一个初始值 4 跨模块使用 vuex,请直接使用 rootGetters

拓展vue 的声明 增强类型 支持this.$http这种写法

image.png

什么时候建议用 TypeScript

  • 大型项目
  • 项目生命周期长
  • 团队成员接受程度高
  • 项目是框架、库