TypeScript+vue使用与迁移经验总结

源宝导读:ERP平台的前端底层使用了Vue作为组件的基础架构,而使用了TypeScript语言进行组件的封装与开发。本文将简要介绍平台在使用TypeScript和Vue框架进行老功能重构时的经验总结。

一、背景

下面主要探讨是以下三个方面:

  • 目前项目中使用到的vue+ts的哪些特性,还有哪些特性值得去使用,不会涉及到太多的ts语法知识;

  • 老项目的迁移为ts,有哪些点需要改造;

  • 各抒己见,探讨下各位都有哪些心得和见解。

二、为什么要用typescript

TypeScript简单介绍:

  • 是 JavaScript 的强类型版本。然后在编译期去掉类型和特有语法,生成纯粹的 JavaScript 代码。由于最终在浏览器中运行的仍然是 JavaScript,所以 TypeScript 并不依赖于浏览器的支持,也并不会带来兼容性问题。

  • TypeScript 是 JavaScript 的超集,这意味着他支持所有的 JavaScript 语法。并在此之上对 JavaScript 添加了一些扩展,如 class / interface / module 等。这样会大大提升代码的可阅读性。

总结优势:

  • 静态类型检查: 类型校验,能够避免许多低级代码错误;

  • IDE 智能提示: 使用一个方法时,能清楚知道方法定义的参数和类型和返回值类型;使用一个对象时,只需要.就可以知道有哪些属性以及属性的类型;

  • 代码重构: 经过不停的需求迭代,代码重构避免不了,在重构时,如果前期有清晰和规范的接口定义、类定义等,对于重构帮助很大;

  • 规范性和可读性: 类似于强类型语言,有了合理的类型定义、接口定义等,对于代码实现的规范性和可读性都有很大提高,不然搜索整个项目这个方法在哪里调用、怎么定义等。

个人认为最有价值点:写代码前,会先构思功能需求的整体代码架构。

三、安装和起步

一般我们会面临两个情况:

  • 新项目创建;

  • 觉得ts不错,想将老项目切换为vue+ts。

3.1、新项目起步

  • 安装vue-cli3.0;

  • vue create vue-ts-hello-world;

  • 选择Manually select features,勾选typescript。其他配置根据项目情况勾选。

3.2、老项目切换为vue+ts

  • 安装ts依赖(或使用yarn);

    • yarn add vue-class-component vue-property-decorator;

    • yarn add ts-loader typescript tslint tslint-loader tslint-config-standard —dev。

  • 配置 webpack,添加ts-loader和tslint-loader;

  • 添加 tsconfig.json;

// 这是平台目前用的tsconfig.json
{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "strict": true,
    "jsx": "preserve",
    "importHelpers": true,
    "moduleResolution": "node",
    "experimentalDecorators": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strictNullChecks": false,
    "sourceMap": true,
    "baseUrl": ".",
    "types": [
      "webpack-env",
      "jest"
    ],
    "paths": {
      "@/*": [
        "src/*"
      ],
      // 别名追加
      "components/*": [
        "src/components/*"
      ],
    },
    "lib": [ // 编译过程中需要引入的库文件的列表
      "esnext",
      "dom",
      "dom.iterable",
      "scripthost"
    ]
  },
  "include": [
    "src/**/*.ts",
    "src/**/*.tsx",
    "src/**/*.vue",
    "tests/**/*.ts",
    "tests/**/*.tsx"
  ],
  "exclude": [
    "node_modules",
    "ui-tests"
  ]
}

备注: ts 也可支持 es6 / es7,在 tsconfig.json中,添加对es6 / es7的支持。

 "lib": [
  "dom",
  "es5",
  "es6",
  "es7",
  "es2015.promise"
]
  • 添加 tslint.json 或者 prettierrc(可以视情况而定)。

// 目前平台使用的是.prettierrc.js
module.exports = {
  "$schema": "http://json.schemastore.org/prettierrc",
  "singleQuote": true,
  "endOfLine": "auto",
  "semi": false
}
  • 让 ts 识别 .vue。

declare module "*.vue" {
  import Vue from "vue";
  export default Vue;
}
    • 而在代码中导入 .vue 文件的时候,需要写上 .vue 后缀。原因还是因为 TypeScript 默认只识别 .ts 文件,不识别 *.vue 文件。

    • 添加vue-shim.d.ts,让vue文件给vue模块来编译。

  • 改造 .vue文件,将vue中script切换为<script lang="ts">;

  • 改造.js文件,修改为ts语法,定义类型等。

四、vue+ts常用的装饰器

    这里主要用到了vue-property-decorator,这个是在vue-class-component基础上做了一层增强,新增了一些装饰器,使用更加便捷。这里只分享一些常用的,对于老项目改写vue文件很有用:

4.1、@Component

    标识该vue文件是一个组件,并且可以引入其他组件。

非ts版本:

import MyComponent from '@/components/MyComponent'
export default {
    components: {
        MyComponent
    }
}

ts版本:

import { Vue, Component } from 'vue-property-decorator'
import MyComponent from '@/components/MyComponent'
@Component({
    components: {
        MyComponent
    }
})
export default class YourComponent extends Vue {
}

备注:这里不管有没有引入其他组件,都必须要使用@Component,目的是为了注册这个组件。否则在其他组件各种莫名其妙的问题。比如:路由找不到组件,而且不会报错。

4.2、@Prop

非ts版本:

export default {
  props: {
    propA: {
      type: Number
    },
    propB: {
      default: 'default value'
    },
    propC: {
      type: [String, Boolean]
    },
    propD: {
        type: Object,
        default: () => {},
        validator(val: object) {
          return val.prop = '1'
        }
    }
  }
}

ts版本:

import { Vue, Component, Prop } from 'vue-property-decorator'


@Component
export default class YourComponent extends Vue {
  @Prop(Number)
  readonly propA: number | undefined


  @Prop({ default: 'default value' })
  readonly propB!: string


  @Prop([String, Boolean])
  readonly propC: string | boolean | undefined


  // 也可以一起
  @Prop({type: Object, default: () => {},
    validator(val: object) {
      return val.prop = '1'
    }
  })
  readonly propD!: object // 只是举例,一般会定义一个interface
}

4.3、@Watch

非ts版本:

export default {
  watch: {
    child: {
        handler: 'onChildChanged',
        immediate: false,
        deep: false
    },
    person: [
      {
        handler: 'onPersonChanged1',
        immediate: true,
        deep: true
      },
      {
        handler: 'onPersonChanged2',
        immediate: false,
        deep: false
      }
    ]
  },
  methods: {
    onChildChanged(val, oldVal) {},
    onPersonChanged1(val, oldVal) {},
    onPersonChanged2(val, oldVal) {}
  }
}

ts版本:

import { Vue, Component, Watch } from 'vue-property-decorator'


@Component
export default class YourComponent extends Vue {
  @Watch('child')
  onChildChanged(val: string, oldVal: string) {}


  @Watch('person', { immediate: true, deep: true })
  onPersonChanged1(val: Person, oldVal: Person) {}


  @Watch('person')
  onPersonChanged2(val: Person, oldVal: Person) {}
}

4.4、@Provide和@Inject

    场景:一般用于父级嵌套比较深的子孙vue组件,但是数据不是很方便传到深层级vue组件中,利用树型结构组件。

非ts版本:

// 父组件
provide () {
  return {
    OptionGroup: this
  }
}


// 子孙组件
inject: ['OptionGroup']

ts版本:

父组件:

@Provide()
  getObj () {
    return this
  }

子孙组件:

@Inject() getObj!: any


get obj() {
    return this.getObj()
}

Privide的弊端:

  • 依赖注入它将你应用程序中的组件与它们当前的组织方式耦合起来,使重构变得更加困难;

  • 同时所提供的属性是非响应式的。这是出于设计的考虑,因为使用它们来创建一个中心化规模化的数据跟使用 $root做这件事都是不够好的。

建议:

一般不推荐过度使用。

  • provide 和 inject的绑定并不是可响应的,这是刻意为之的。但是,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的;

  • 如果你想要共享的这个属性是你的应用特有的,而不是通用化的,或者如果你想在祖先组件中更新所提供的数据,那么这意味着你可能需要换用一个像Vuex这样真正的状态管理方案了。

4.5、@Ref

非ts版本:

export default {
  computed() {
    anotherComponent () {
        return this.$refs.anotherComponent
    },
    button () {
        return this.$refs.aButton
    }
  }
}

ts版本:

import { Vue, Component, Ref } from 'vue-property-decorator'
import AnotherComponent from '@/Components/another-component.vue'


@Component
export default class YourComponent extends Vue {
  @Ref() readonly anotherComponent!: AnotherComponent
  @Ref('aButton') readonly button!: HTMLButtonElement


  // 我们目前是这样使用的
  $refs!: {
    popover: any
    search: HcProjectSelectSearch
    tree: HcProjectTree
  }
}

4.6、@Emit

用的很少,参数和时机不是很好控制。

非ts版本:

export default {
  methods: {
    handleClick(e) {
      this.$emit('click', e)
    },
    loadData() {
      const promise = new Promise(resolve => {
        setTimeout(() => {
          resolve(20)
        }, 0)
      })
      promise.then(value => {
        this.$emit('load', value)
      })
    }
  }
}

ts版本:

import { Vue, Component, Emit } from 'vue-property-decorator'


@Component
export default class YourComponent extends Vue {
  @Emit('click')
  handleClick(e) {
    // todo
  }
  @Emit()
  promise() {
    return new Promise(resolve => {
      setTimeout(() => {
        resolve(20)
      }, 0)
    })
  }
}

五、mixin改写

定义mixin:

export const cusMixin = {
  mounted() {
    this.$refs = {}
    // $0 instanceof HTMLElement
    // this.$refs = {}
    console.log('mixin mounted')
  },


  beforeUpdate() {
    this.$refs = {}
    // console.log('global mounted')
  },
  updated() {
    this.$refs = {}
    // console.log('global mounted')
  }
}

引入mixin:

import { Vue, Component } from 'vue-property-decorator'
import cusMixin from '@/mixin'


@Component({
  components: {},
  mixins: [cusMixin]
})
export default class YourComponent extends Vue {}


// 或者尝试使用
import { Component, Mixins, Vue } from 'vue-property-decorator';
import { MyOtherMixin } from './MyOtherMixin';


@Component
export class MyMixin extends Vue {
   private created() {
    console.log('what?');
  }
}


@Component
// 继承多个mixin,使用数组 [MyMixin, MyOtherMixin] 
export default class App extends Mixins(MyMixin) { 
  private test = "test";
  private laowang = 'laowang';


  created() {
    console.log(this.test)
    console.log(this.Kitchen)
    console.log(this.Tv)
  }


}

六、vue识别全局的方法和变量

  • vue-shim.d.ts文件中,增加如下代码:

import Vue from 'vue'
import VueRouter, { Route } from 'vue-router'
import { Store } from 'vuex'
// 声明全局方法
declare module 'vue/types/vue' {
  interface Vue {
    // 内部变量
    $router: VueRouter;
    $route: Route;
    $store: Store<any>;
    // element-ui等组件
    $Message: any
    $Modal: any
    // 自定义挂载到Vue.prototype上的变量
    $api: any
    $mock: any
    $configs: any
  }
}

七、vuex的改写

    关于store的改造,配置和结构和原来一样,具体编码设计没有特定套路,根据项目具体设计改写为ts的语法。

    主要是关于ts在vue如何使用,目前主流的方案是vue-class-component + vuex-class,一般常用的mapGetters和mapActions改写:

yarn add vuex-class

非ts版本:

import { mapGetters, mapActions } from 'vuex'
export default Vue.extend({
  computed: {
     ...mapGetters({
       'name',
       'age'
     })
  },
  methods: {
    ...mapActions([
        'setNameAction'
    ])
  }
})

ts版本:

import { Vue, Component } from 'vue-property-decorator'
import { Getter, Action } from 'vuex-class'
import { Test } from '@/store'


export default class YourComponent extends Vue {
  @Getter('name') name: string
  @Getter('age') age: number
  @Action('setNameAction') setNameAction: Function


  get innerName (): string {
     return this.name
  }
  get innerAge (): number {
     return this.age
  }


  setName (name: string) {
    this.setNameAction(products)
  }
}

备注:tsconfig.json需要调整下:

{
  "compilerOptions": {
    // 启用 vue-class-component 及 vuex-class 需要开启此选项
    "experimentalDecorators": true,


    // 启用 vuex-class 需要开启此选项
    "strictFunctionTypes": false
  }
}

八、vue render jsx语法改写

    改写的原理还是和上面类似,都是借助目前流行的两个库,除了使用vue-property-decorator以外,还需要借助vue-tsx-support,vue-tsx-support是在Vue外面包装了一层,将prop、event等以泛型的方式加了一层ts接口定义传了进去,目的是为了防止ts的类检查报错。

  • 步骤:

    • 引入 yarn add vue-tsx-support --dev;

    • 导入ts声明,在main,ts中import "vue-tsx-support/enable-check";

    • vue.config.jsextensions添加.tsx。

  • 使用:

import { Component, Prop } from "vue-property-decorator";
import * as tsx from "vue-tsx-support";


interface YourComponentsProps {
  name?: string
  age?: number
}


@Component
export default class YourComponents extends tsx.Component<YourComponentsProps> {
  @Prop() public name!: string;
  @Prop() public age!: number;


  protected render() {
    return (
      <div>
        <h1>姓名:{this.name}</h1>
        <h1>年龄:{this.age}</h1>
      </div>
    );
  }
}

    这里jsx改写为tsx大致简单了解下,如果大家有兴趣,以后可以一起学习探讨下。

九、思考

  • 关于老项目ts的改造,如何才能平滑过渡,不影响现有的功能。

  • 在vue中ts的实践,数据、视图、控制器分层设计的问题。

------ END ------

作者简介

罗同学: 研发工程师,目前负责ERP建模平台的设计与开发工作。

也许您还想看

从案例角度解析建模平台动态规则引擎

WEB页面前端性能诊断方法与实践

前端异步对象的原理与使用方法

Web页面适配移动端方案研究

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Vue 项目中使用 TypeScript,可以通过配置 TSLint 来进行代码规范和类型检查。以下是一些设置 TSLint 的经验总结: 1. 安装依赖 ``` npm install --save-dev tslint tslint-config-prettier tslint-plugin-prettier tslint-eslint-rules typescript ``` 2. 配置 TSLint 在项目根目录下创建 tslint.json 文件,并添加以下内容: ``` { "extends": [ "tslint:recommended", "tslint-config-prettier" ], "rules": { "interface-name": false, "no-console": false, "no-empty": false, "no-unused-expression": false, "no-unused-variable": false, "semicolon": [true, "always"] }, "linterOptions": { "exclude": [ "node_modules/**", "dist/**" ] } } ``` 以上配置文件的含义: - extends:继承的规则,包含了 TSLint 推荐的规则和 Prettier 的规则。 - rules:自定义的规则,可以根据团队的需求进行配置。 - linterOptions:指定需要忽略的文件或目录。 3. 配置 VS Code 在 VS Code 中安装以下插件: - TSLint - Prettier - Code formatter 然后在项目根目录下创建 .prettierrc 文件,并添加以下内容: ``` { "singleQuote": true, "trailingComma": "es5", "semi": true, "tabWidth": 2 } ``` 最后,在 VS Code 的设置中添加以下配置: ``` { "editor.formatOnSave": true, "vetur.validation.template": false, "prettier.eslintIntegration": true, "eslint.validate": [ "javascript", "javascriptreact", "vue", "typescript" ], "typescript.validate.enable": false, "tslint.enable": true } ``` 以上配置的含义: - editor.formatOnSave:在保存时自动格式化代码。 - vetur.validation.template:禁止 Vetur 对模板的验证,因为它可能与 TSLint 发生冲突。 - prettier.eslintIntegration:启用 Prettier 和 ESLint 的集成。 - eslint.validate:指定需要验证的文件类型。 - typescript.validate.enable:禁止 VS Code 内置的 TypeScript 验证器,因为它可能与 TSLint 发生冲突。 - tslint.enable:启用 TSLint 验证器。 4. 迁移经验 如果是一个已经存在的 Vue 项目,需要将 JavaScript 代码迁移TypeScript,可以按照以下步骤进行: - 安装 TypeScript 和 @types/node - 将 .js 文件改名为 .ts 文件,并修改文件中的代码 - 在 Vue 组件中添加 <script lang="ts"> 标签,并将代码移到其中 - 逐步修改代码,添加类型注解和接口定义 需要注意的是,迁移过程中可能会遇到一些问题,例如: - 无法识别某些模块,需要在 tsconfig.json 中配置 paths - 需要安装额外的类型声明文件,例如 @types/vue、@types/lodash 等 - 一些 JavaScript 的语法不支持 TypeScript,需要进行调整 总之,迁移过程需要耐心和谨慎,可以先从一些简单的模块开始,逐步迁移。同时,使用 TSLint 和 VS Code 可以帮助我们更方便地进行代码规范和类型检查。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值