前言
其實看 Vue2 的官方文檔惠桃,對 Typescript 的支持還是相對比較少的,有兩種方式定義組件
- 使用
Vue.component
或Vue.extend
定義Vue組件 - 使用
vue-class-component
裝飾器來定義基于類的Vue組件
其實市面上還有一種實現(xiàn)方式辖试,那就是由社區(qū)基于 vue-class-component
裝飾器的二次封裝 vue-property-decorator
, 它也是今天我們要分享主要內容
npm vue-property-decorator 文檔地址
搭建 webpack5 + Vue2 + Ts
安裝 node, vue-cli, webpack ts 這些在這里我就不過多描述了辜王,不懂的小伙伴可以看我前幾篇文章,有關于環(huán)境搭建的罐孝,那搭建項目也比較簡單呐馆,參考 vue-cli 搭建項目
進入需要創(chuàng)建工程的目錄,cmd 執(zhí)行命令
vue create vue-ts-demo
復制代碼
回車莲兢,我們選擇第三個自定義選項
然后汹来,根據自己的選擇來勾選,記住勾選 typescript 改艇,如下是我都選的
回車之后收班,選擇 2.x 因為我們分享的是 vue 2.x + ts
緊跟著是一些里的描述于配置,yes/no 自己決定谒兄,當然也可以一系列 yes 到底摔桦,然后就到了這里
很熟悉,讓我們進入工程目錄承疲,然后啟動項目邻耕,接下來我們可以簡單看一下此時的工程目錄結構,目錄結構大多數(shù)都是很熟悉的東西燕鸽,就是有一點小小的改變
- 添加了 tsconfig.json (ts 配置文件)
- shims-tsx.d.ts (支持 tsx)
- shims-vue.d.ts (支持vue使用 ts)
其它的不同就是打開文件后發(fā)現(xiàn) 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
來創(chuàng)建基于類的 vue 組件
@Component 創(chuàng)建組件
@Component
裝飾器可以接收一個對象作為參數(shù),可以在對象中聲明 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 接收參數(shù)
@Prop
裝飾器接收一個參數(shù),常常這樣用
-
@Prop('String')
: 指定prop
的類型,字符串String碱屁,Number磷脯,Boolean
等 -
@Prop({ default: 1, type: Number })
: 對象,指定默認值{default:'1'}
-
@Prop([String,Number])
: 數(shù)組娩脾,指定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 可接受的參數(shù)
})
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用法基本類似赵誓,只是多了一個參數(shù), 父組件在傳遞的時候需要配合 .sync
- 第一個參數(shù)是父組件傳遞過來的屬性名
- 第二個參數(shù)與@Prop的第一個參數(shù)一樣
- @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 可接受的參數(shù)
})
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 監(jiān)聽
@Watch 裝飾器接收兩個參數(shù):
- 被監(jiān)聽的屬性名
- 可選屬性: {immediate?:boolean 監(jiān)聽開始之后是否立即調用該回調函數(shù),deep?:boolean 是否深度監(jiān)聽}
<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")
// 可選參數(shù) @Watch('age', {immediate: true, deep: true})
onChangeAge(v: number, o: number): void {}
}
</script>
復制代碼
@Emit 廣播事件
@Emit
裝飾器接收一個可選參數(shù)柿赊,廣播事件名俩功,如果沒有定義這個參數(shù),則是以回調方法名為廣播事件名
- 回調函數(shù)的返回值默認為第二個參數(shù)碰声,如果返回是 promise 诡蜓,則會默認為 resolve 之后觸發(fā)回調
// 父組件
<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 觸發(fā),參數(shù):", 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
裝飾器接收一個可選參數(shù)胰挑,用來指向元素或子組件的引用信息蔓罚,即 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提供的數(shù)據,在子組件內部使用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
蘸朋,用一個實例來體現(xiàn)一下 現(xiàn)有的工程不變核无,我們搭建環(huán)境的時候已經支持了 tsx
創(chuàng)建一個 demo.tsx
,鍵入如下簡單內容藕坯,大致看下來团南,基本上跟我們上邊分享的類組件一樣,唯一有一點不一樣的就是模板變成 render 函數(shù)炼彪,這樣可以讓我們更加靈活吐根。
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" }
// 渲染函數(shù)
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"
]
}
參考鏈接:資料