vue2+ts+vue-property-decorator

前言

其实看 Vue2 的官方文档,对 Typescript 的支持还是相对比较少的,有两种方式定义组件

  • 使用 Vue.componentVue.extend 定义Vue组件
  • 使用 vue-class-component 装饰器来定义基于类的Vue组件

其实市面上还有一种实现方式,那就是由社区基于 vue-class-component 装饰器的二次封装 vue-property-decorator, 它也是今天我们要分享主要内容

npm vue-property-decorator 文档地址

搭建 webpack5 + Vue2 + Ts

安装 node, vue-cli, webpack ts 这些在这里我就不过多描述了,不懂的小伙伴可以看我前几篇文章,有关于环境搭建的,那搭建项目也比较简单,参考 vue-cli 搭建项目

进入需要创建工程的目录,cmd 执行命令

vue create vue-ts-demo 
复制代码

回车,我们选择第三个自定义选项

image.png

然后,根据自己的选择来勾选,记住勾选 typescript ,如下是我都选的

image.png

回车之后,选择 2.x 因为我们分享的是 vue 2.x + ts

image.png

紧跟着是一些里的描述于配置,yes/no 自己决定,当然也可以一系列 yes 到底,然后就到了这里

image.png

很熟悉,让我们进入工程目录,然后启动项目,接下来我们可以简单看一下此时的工程目录结构,目录结构大多数都是很熟悉的东西,就是有一点小小的改变

image.png
  • 添加了 tsconfig.json (ts 配置文件)
  • shims-tsx.d.ts (支持 tsx)
  • shims-vue.d.ts (支持vue使用 ts)

其它的不同就是打开文件后发现 vue 都是用 ts 编写的,拿一个 Home.vue 展示一下吧

<template>
  <div class="home">
    <img alt="Vue logo" src="../assets/logo.png">
    <HelloWorld msg="Welcome to Your Vue.js + TypeScript App"/>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import HelloWorld from '@/components/HelloWorld.vue'; // @ is an alias to /src

@Component({
  components: {
    HelloWorld,
  },
})
export default class Home extends Vue {}
</script>

复制代码

看起来很不一样哈,跟我们之前写的 vue 模板语法都不一样,不要着急,我们今天就是来分享vue2 如何使用 ts 的,好了,进入整体,我们直接开始撸代码

如何编写组件

这里呢,我不动目前所有的工程结构,直接拿 Aboud.vue 组件来演示如何使用 vue-property-deractor 来创建基于类的 vue 组件

@Component 创建组件

@Component 装饰器可以接收一个对象作为参数,可以在对象中声明 components ,filters,directives等未提供装饰器的选项,(下文会做一些演示)

基础模板语法

<template>
  <div class="about">
    <h1>我是about.vue组件</h1>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from "vue-property-decorator";

@Component
export default class about extends Vue {}
</script>

复制代码

@Prop 接收参数

@Prop装饰器接收一个参数,常常这样用

  • @Prop('String') : 指定 prop 的类型,字符串 String,Number,Boolean
  • @Prop({ default: 1, type: Number }) : 对象,指定默认值 {default:'1'}
  • @Prop([String,Number]) : 数组,指定 prop 的可选类型 [String, Boolean]
// 父组件:
<template>
  <div class="Props">
    <Child :name="name" :age="age" :sex="sex"></Child>
  </div>
</template>
 
<script lang="ts">
import {Component, Vue,} from 'vue-property-decorator';
import Child from './Child.vue';
 
@Component({
  components: {Child}, // 上边有说 @Component 可接受的参数
})
export default class PropsPage extends Vue {
  private name = 'Hs';
  private age = 18;
  private sex = 1;
}
</script>
 
// 子组件:
<template>
  <div>
    name: {{name}} | age: {{age}} | sex: {{sex}}
  </div>
</template>
 
<script lang="ts">
import {Component, Vue, Prop} from 'vue-property-decorator';
 
@Component
export default class Child extends Vue {
   @Prop(String) readonly name!: string | undefined;
   @Prop({ default: 20, type: Number }) private age!: number;
   @Prop([String, Number]) private sex!: string | number;
}
</script>
复制代码

@Propsync 不一样的@Prop

@PropSync装饰器与@prop用法基本类似,只是多了一个参数, 父组件在传递的时候需要配合 .sync

  • 第一个参数是父组件传递过来的属性名
  • 第二个参数与@Prop的第一个参数一样
  • @PropSync 会生成一个新的计算属性 , 可逆向修改父组件传递过来的属性,父组件会同步修改
// 父组件:
<template>
  <div class="Props">
    <Child :name.sync="name"></Child>
  </div>
</template>
 
<script lang="ts">
import {Component, Vue,} from 'vue-property-decorator';
import Child from './Child.vue';
 
@Component({
  components: { Child }, // 上边有说 @Component 可接受的参数
})
export default class PropsPage extends Vue {
  private name = 'Hs';
}
</script>
 
// 子组件:
<template>
  <div>
    name: {{name_copy}}
    <button @click="setProp">修改prop</button>
  </div>
</template>
 
<script lang="ts">
import {Component, Vue, PropSync} from 'vue-property-decorator';
 
@Component
export default class Child extends Vue {
   @PropSync("name",String) name_copy!: string | undefined;
   setProp(){
       this.name_copy = "abcd" // 父组件会同步修改
   }
}
</script>
复制代码

@Watch 监听

@Watch 装饰器接收两个参数:

  • 被监听的属性名
  • 可选属性: {immediate?:boolean 监听开始之后是否立即调用该回调函数,deep?:boolean 是否深度监听}
<template>
  <div class="about">
    <h3> {{age}}</h3>
  </div>
</template>

<script lang="ts">
import { Component, Vue, Watch } from "vue-property-decorator";

@Component
export default class About extends Vue {
  private age = 18;

  @Watch("age")
  // 可选参数 @Watch('age', {immediate: true, deep: true})
  onChangeAge(v: number, o: number): void {}

}
</script>

复制代码

@Emit 广播事件

@Emit 装饰器接收一个可选参数,广播事件名,如果没有定义这个参数,则是以回调方法名为广播事件名

  • 回调函数的返回值默认为第二个参数,如果返回是 promise ,则会默认为 resolve 之后触发回调
// 父组件
<template>
  <div class="about">
    <ChildComp @childEmit="chileEmit" />
  </div>
</template>

<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
import ChildComp from "./Child.vue";

@Component
export default class About extends Vue {
  chileEmit(n: string): void {
    console.log("子组件 emit 触发,参数:", n);
  }
}
</script>

//子组件
<template>
  <div>
    <h3>我是子组件</h3>
    <button @click="customClickName"> @emit age+1 </button>
  </div>
</template>

<script lang="ts">
import { Component, Emit, Vue } from "vue-property-decorator";

@Component
export default class Child extends Vue {
  @Emit("childEmit")
  customClickName(): string {
    return "hs"
  }
}
</script>

复制代码

@ref 句柄

@Ref 装饰器接收一个可选参数,用来指向元素或子组件的引用信息,即 ref="这个值"

<template>
  <div class="about">
    <button @click="getRef"
            ref="child_btn">age++</button>
    <hr>
    <ChildComp :name="name"
               :age="age"
               ref="child_c"
               @childEmit="chileEmit" />
  </div>
</template>

<script lang="ts">
import { Component, Provide, Ref, Vue, Watch } from "vue-property-decorator";
import ChildComp from "./Child.vue";

@Component({
  components: { ChildComp }
})
export default class About extends Vue {
  @Ref("child_c") readonly child_comp!: ChildComp;
  @Ref("child_btn") readonly child_btn_dom!: HTMLButtonElement;
  getRef() {
    console.log(this.child_comp, this.child_btn_dom);
  }
}
</script>

复制代码

@Provide / @Inject 和 @ProvideReactive / @InjectReactive`

提供/注入装饰器,key可以为string或者symbol类型,使用方式都一样

  • 相同: Provide/ProvideReactive提供的数据,在子组件内部使用Inject/InjectReactive都可取到
  • 不同: ProvideReactive 的值被父组件修改,子组件可以使用 InjectReactive 捕获
// 顶层组件
<template>
  <div class="about">
    <ChildComp />
  </div>
</template>

<script lang="ts">
import { Component, Provide,Vue } from "vue-property-decorator";
import ChildComp from "./Child.vue";

@Component({
  components: { ChildComp },
})
export default class About extends Vue {
  @Provide("provide_value") private p = "from provide";
}
</script>

// 子组件
<template>
  <div>
    <h3>我是子组件</h3>
    <h3>provide/inject 跨级传参 {{provide_value}}</h3>

  </div>
</template>

<script lang="ts">
import { Component, Inject, Vue } from "vue-property-decorator";

@Component
export default class Child extends Vue {
  @Inject() readonly provide_value!: string;
}
</script>

复制代码

如何使用 tsx

其实 tsx 用起来会让我们有 react 的感觉,写过 react 的都知道是使用 jsx javascript + xml, 那么 tsx 基本上跟 jsx 差不多,等同于 typescript + xml,用一个实例来体现一下 现有的工程不变,我们搭建环境的时候已经支持了 tsx

创建一个 demo.tsx ,键入如下简单内容,大致看下来,基本上跟我们上边分享的类组件一样,唯一有一点不一样的就是模板变成 render 函数,这样可以让我们更加灵活。

import { Component, Emit, Prop, PropSync, Vue, Watch } from "vue-property-decorator";

@Component
export default class Demo extends Vue {

  public name = "Hs"
  public str = "hello tsx"
  public data = [1, 2, 3, 4]

  // Prop
  @Prop() demo_name!: string
  @Prop(Number) demo_age!: number

  // Propsync
  @PropSync("propsync", Number) propsync_copy!: number | undefined

  // Computed
  get _age(): number {
    this.str = this.str + "-x"
    return this.demo_age * 10
  }

  //watch
  @Watch("str")
  onhangeStr(v: string, o: string) {
    console.log(v, o)
  }

  //emit
  @Emit("tsx_emit")
  clickEvent() { return "params 123" }

  // 渲染函数    
  render() {
    return (
      <div>
        <h2>data属性: {this.name}-{this.str}</h2>
        <h2>prop: {this.demo_name}</h2>
        <h2>计算属性: {this._age}</h2>
        <h2>prop-sync: {this.propsync_copy}</h2>
        <h2>遍历</h2>
        {
          this.data.map(c => {
            return <span>{c} - </span>
          })
        }
        <button onClick={() => this.clickEvent()}>emit</button>
      </div>
    )
  }

}

复制代码

ok,tsx 的使用基本上跟 ts 差不多,这里都是简单的例子和使用方式,便于理解和学习,更重要的是如何学习好 ts, 它真的很枪手。

贴出我的package.json 文件,仅供版本差异的参考:

{
  "name": "vue2-ts-demo2",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
  },
  "dependencies": {
    "vue": "^2.6.11",
    "vue-class-component": "^7.2.3",
    "vue-property-decorator": "^9.1.2",
    "vue-router": "^3.2.0",
    "vuex": "^3.4.0"
  },
  "devDependencies": {
    "@typescript-eslint/eslint-plugin": "^4.18.0",
    "@typescript-eslint/parser": "^4.18.0",
    "@vue/cli-plugin-eslint": "^4.0.5",
    "@vue/cli-plugin-router": "^4.0.5",
    "@vue/cli-plugin-typescript": "^4.0.5",
    "@vue/cli-plugin-vuex": "^4.0.5",
    "@vue/cli-service": "^4.0.5",
    "@vue/eslint-config-prettier": "^6.0.0",
    "@vue/eslint-config-typescript": "^7.0.0",
    "eslint": "^6.7.2",
    "eslint-plugin-prettier": "^3.3.1",
    "eslint-plugin-vue": "^6.2.2",
    "node-sass": "^4.12.0",
    "prettier": "^2.2.1",
    "sass-loader": "^8.0.2",
    "typescript": "~4.1.5",
    "vue-template-compiler": "^2.6.11"
  },
  "eslintConfig": {
    "root": true,
    "env": {
      "node": true
    },
    "extends": [
      "plugin:vue/essential",
      "eslint:recommended",
      "@vue/typescript/recommended",
      "@vue/prettier",
      "@vue/prettier/@typescript-eslint"
    ],
    "parserOptions": {
      "ecmaVersion": 2020
    },
    "rules": {}
  },
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not dead"
  ]
}

参考链接:资料

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容