[TOC]
# Vue3.0 前的 TypeScript 入門實踐
## 前言
**其實`Vue`官方從`2.6.X`版本開始就部分使用`Ts`重寫了。**
我個人對更嚴格類型限制沒有積極的看法,畢竟各類轉類型的騷寫法寫習慣了。
然鵝最近的一個項目中,是`TypeScript`+`Vue`,毛計喇,學之...…真香!
**注意此篇標題的“前”,本文旨在講`Ts`混入框架的使用,不講`Class API`**

## 1\. 使用官方腳手架構建
~~~
npm install -g @vue/cli
# OR
yarn global add @vue/cli
復制代碼
~~~
新的`Vue CLI`工具允許開發者 使用`TypeScript`集成環境 創建新項目。
只需運行`vue create my-app`。
然后,命令行會要求選擇預設。使用箭頭鍵選擇`Manually select features`。
接下來,只需確保選擇了`TypeScript`和`Babel`選項,如下圖:

完成此操作后,它會詢問你是否要使用`class-style component syntax`。
然后配置其余設置,使其看起來如下圖所示。

Vue CLI工具現在將安裝所有依賴項并設置項目。

接下來就跑項目喇。

總之,先跑起來再說。
## 2\. 項目目錄解析
通過`tree`指令查看目錄結構后可發現其結構和正常構建的大有不同。

這里主要關注`shims-tsx.d.ts`和`shims-vue.d.ts`兩個文件
兩句話概括:
* `shims-tsx.d.ts`,允許你以`.tsx`結尾的文件,在`Vue`項目中編寫`jsx`代碼
* `shims-vue.d.ts`主要用于`TypeScript`識別`.vue`文件,`Ts`默認并不支持導入`vue`文件,這個文件告訴`ts`導入`.vue`文件都按`VueConstructor<Vue>`處理。
此時我們打開親切的`src/components/HelloWorld.vue`,將會發現寫法已大有不同
~~~
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<!-- 省略 -->
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
@Component
export default class HelloWorld extends Vue {
@Prop() private msg!: string;
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped></style>
復制代碼
~~~
至此,準備開啟新的篇章`TypeScript`極速入門 和`vue-property-decorator`
## 3.`TypeScript`極速入門
### 3.1 基本類型和擴展類型

`Typescript`與`Javascript`共享相同的基本類型,但有一些額外的類型。
* 元組`Tuple`
* 枚舉`enum`
* `Any`與`Void`
#### 1\. 基本類型合集
~~~
// 數字,二、八、十六進制都支持
let decLiteral: number = 6;
let hexLiteral: number = 0xf00d;
// 字符串,單雙引都行
let name: string = "bob";
let sentence: string = `Hello, my name is ${ name }.
// 數組,第二種方式是使用數組泛型,Array<元素類型>:
let list: number[] = [1, 2, 3];
let list: Array<number> = [1, 2, 3];
let u: undefined = undefined;
let n: null = null;
復制代碼
~~~
#### 2\. 特殊類型
##### 1.**元組`Tuple`**

想象 元組 作為有組織的數組,你需要以正確的順序預定義數據類型。
~~~
const messyArray = [' something', 2, true, undefined, null];
const tuple: [number, string, string] = [24, "Indrek" , "Lasn"]
復制代碼
~~~
如果不遵循 為元組 預設排序的索引規則,那么`Typescript`會警告。

? (`tuple`第一項應為`number`類型)
##### 2\. 枚舉`enum`

`enum`類型是對JavaScript標準數據類型的一個補充。 像C#等其它語言一樣,使用枚舉類型可以為一組數值賦予友好的名字。
~~~
// 默認情況從0開始為元素編號,也可手動為1開始
enum Color {Red = 1, Green = 2, Blue = 4}
let c: Color = Color.Green;
let colorName: string = Color[2];
console.log(colorName); // 輸出'Green'因為上面代碼里它的值是2
復制代碼
~~~
另一個很好的例子是使用枚舉來存儲應用程序狀態。

##### 3.`Void`

在`Typescript`中,**你必須在函數中定義返回類型**。像這樣:

若沒有返回值,則會報錯:

我們可以將其返回值定義為`void`:

此時將無法`return`
##### 4.`Any`

Emmm...就是什么類型都行,當你無法確認在處理什么類型時可以用這個。
但要慎重使用,用多了就失去使用Ts的意義。
~~~
let person: any = "前端勸退師"
person = 25
person = true
復制代碼
~~~
主要應用場景有:
1. 接入第三方庫
2. Ts菜逼前期都用
##### 5.`Never`

用很粗淺的話來描述就是:"`Never`是你永遠得不到的爸爸。"
具體的行為是:
* `throw new Error(message)`
* `return error("Something failed")`
* `while (true) {} // 存在無法達到的終點`

#### 3\. 類型斷言

簡略的定義是:可以用來手動指定一個值的類型。
有兩種寫法,尖括號和`as`:
~~~
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
let strLength: number = (someValue as string).length;
復制代碼
~~~
使用例子有:
當 TypeScript 不確定一個聯合類型的變量到底是哪個類型的時候,我們只能訪問此聯合類型的所有類型里共有的屬性或方法:
~~~
function getLength(something: string | number): number {
return something.length;
}
// index.ts(2,22): error TS2339: Property 'length' does not exist on type 'string | number'.
// Property 'length' does not exist on type 'number'.
復制代碼
~~~
如果你訪問長度將會報錯,而有時候,我們確實需要在還不確定類型的時候就訪問其中一個類型的屬性或方法,此時需要斷言才不會報錯:
~~~
function getLength(something: string | number): number {
if ((<string>something).length) {
return (<string>something).length;
} else {
return something.toString().length;
}
}
復制代碼
~~~
### 3.2 泛型:`Generics`
軟件工程的一個主要部分就是構建組件,構建的組件不僅需要具有明確的定義和統一的接口,同時也需要組件可復用。支持現有的數據類型和將來添加的數據類型的組件為大型軟件系統的開發過程提供很好的靈活性。
在`C#`和`Java`中,可以使用"泛型"來創建可復用的組件,并且組件可支持多種數據類型。這樣便可以讓用戶根據自己的數據類型來使用組件。
#### 1\. 泛型方法
在TypeScript里,**聲明泛型方法**有以下兩種方式:
~~~
function gen_func1<T>(arg: T): T {
return arg;
}
// 或者
let gen_func2: <T>(arg: T) => T = function (arg) {
return arg;
}
復制代碼
~~~
**調用方式**也有兩種:
~~~
gen_func1<string>('Hello world');
gen_func2('Hello world');
// 第二種調用方式可省略類型參數,因為編譯器會根據傳入參數來自動識別對應的類型。
復制代碼
~~~
#### 2\. 泛型與`Any`
`Ts`的特殊類型`Any`在具體使用時,可以代替任意類型,咋一看兩者好像沒啥區別,其實不然:
~~~
// 方法一:帶有any參數的方法
function any_func(arg: any): any {
console.log(arg.length);
return arg;
}
// 方法二:Array泛型方法
function array_func<T>(arg: Array<T>): Array<T> {
console.log(arg.length);
return arg;
}
復制代碼
~~~
* 方法一,打印了`arg`參數的`length`屬性。因為`any`可以代替任意類型,所以該方法在傳入參數不是數組或者帶有`length`屬性對象時,會拋出異常。
* 方法二,定義了參數類型是`Array`的泛型類型,肯定會有`length`屬性,所以不會拋出異常。
#### 3\. 泛型類型
泛型接口:
~~~
interface Generics_interface<T> {
(arg: T): T;
}
function func_demo<T>(arg: T): T {
return arg;
}
let func1: Generics_interface<number> = func_demo;
func1(123); // 正確類型的實際參數
func1('123'); // 錯誤類型的實際參數
復制代碼
~~~
### 3.3 自定義類型:`Interface`vs`Type alias`
`Interface`,國內翻譯成接口。
`Type alias`,類型別名。

以下內容來自:
> [Typescript 中的 interface 和 type 到底有什么區別](https://juejin.im/post/5c2723635188252d1d34dc7d#heading-11)
#### 1\. 相同點
**都可以用來描述一個對象或函數:**
~~~
interface User {
name: string
age: number
}
type User = {
name: string
age: number
};
interface SetUser {
(name: string, age: number): void;
}
type SetUser = (name: string, age: number): void;
復制代碼
~~~
**都允許拓展(extends):**
`interface`和`type`都可以拓展,并且兩者并不是相互獨立的,也就是說`interface`可以`extends type`,`type`也可以`extends interface`。**雖然效果差不多,但是兩者語法不同**。
**interface extends interface**
~~~
interface Name {
name: string;
}
interface User extends Name {
age: number;
}
復制代碼
~~~
**type extends type**
~~~
type Name = {
name: string;
}
type User = Name & { age: number };
復制代碼
~~~
**interface extends type**
~~~
type Name = {
name: string;
}
interface User extends Name {
age: number;
}
復制代碼
~~~
**type extends interface**
~~~
interface Name {
name: string;
}
type User = Name & {
age: number;
}
復制代碼
~~~
#### 2\. 不同點
**`type`可以而`interface`不行**
* `type`可以聲明基本類型別名,聯合類型,元組等類型
~~~
// 基本類型別名
type Name = string
// 聯合類型
interface Dog {
wong();
}
interface Cat {
miao();
}
type Pet = Dog | Cat
// 具體定義數組每個位置的類型
type PetList = [Dog, Pet]
復制代碼
~~~
* `type`語句中還可以使用`typeof`獲取實例的 類型進行賦值
~~~
// 當你想獲取一個變量的類型時,使用 typeof
let div = document.createElement('div');
type B = typeof div
復制代碼
~~~
* 其他騷操作
~~~
type StringOrNumber = string | number;
type Text = string | { text: string };
type NameLookup = Dictionary<string, Person>;
type Callback<T> = (data: T) => void;
type Pair<T> = [T, T];
type Coordinates = Pair<number>;
type Tree<T> = T | { left: Tree<T>, right: Tree<T> };
復制代碼
~~~
**`interface`可以而`type`不行**
`interface`能夠聲明合并
~~~
interface User {
name: string
age: number
}
interface User {
sex: string
}
/*
User 接口為 {
name: string
age: number
sex: string
}
*/
復制代碼
~~~
`interface`有可選屬性和只讀屬性
* 可選屬性
接口里的屬性不全都是必需的。 有些是只在某些條件下存在,或者根本不存在。 例如給函數傳入的參數對象中只有部分屬性賦值了。帶有可選屬性的接口與普通的接口定義差不多,只是在可選屬性名字定義的后面加一個`?`符號。如下所示
~~~
interface Person {
name: string;
age?: number;
gender?: number;
}
復制代碼
~~~
* 只讀屬性
顧名思義就是這個屬性是不可寫的,對象屬性只能在對象剛剛創建的時候修改其值。 你可以在屬性名前用`readonly`來指定只讀屬性,如下所示:
~~~
interface User {
readonly loginName: string;
password: string;
}
復制代碼
~~~
上面的例子說明,當完成User對象的初始化后loginName就不可以修改了。
### 3.4 實現與繼承:`implements`vs`extends`
`extends`很明顯就是ES6里面的類繼承,那么`implement`又是做什么的呢?它和`extends`有什么不同?
`implement`,實現。與C#或Java里接口的基本作用一樣,`TypeScript`也能夠用它來明確的強制一個類去符合某種契約
**implement基本用法**:
~~~
interface IDeveloper {
name: string;
age?: number;
}
// OK
class dev implements IDeveloper {
name = 'Alex';
age = 20;
}
// OK
class dev2 implements IDeveloper {
name = 'Alex';
}
// Error
class dev3 implements IDeveloper {
name = 'Alex';
age = '9';
}
復制代碼
~~~
而`extends`是繼承父類,兩者其實可以混著用:
~~~
class A extends B implements C,D,E
復制代碼
~~~
搭配`interface`和`type`的用法有:

### 3.5 聲明文件與命名空間:`declare`和`namespace`
前面我們講到Vue項目中的`shims-tsx.d.ts`和`shims-vue.d.ts`,其初始內容是這樣的:
~~~
// shims-tsx.d.ts
import Vue, { VNode } from 'vue';
declare global {
namespace JSX {
// tslint:disable no-empty-interface
interface Element extends VNode {}
// tslint:disable no-empty-interface
interface ElementClass extends Vue {}
interface IntrinsicElements {
[elem: string]: any;
}
}
}
// shims-vue.d.ts
declare module '*.vue' {
import Vue from 'vue';
export default Vue;
}
復制代碼
~~~
`declare`:當使用第三方庫時,我們需要引用它的聲明文件,才能獲得對應的代碼補全、接口提示等功能。
這里列舉出幾個常用的:
~~~
declare var 聲明全局變量
declare function 聲明全局方法
declare class 聲明全局類
declare enum 聲明全局枚舉類型
declare global 擴展全局變量
declare module 擴展模塊
復制代碼
~~~
`namespace`:“內部模塊”現在稱做“命名空間”
`module X {`相當于現在推薦的寫法`namespace X {`)
## 跟其他 JS 庫協同
類似模塊,同樣也可以通過為其他 JS 庫使用了命名空間的庫創建`.d.ts`文件的聲明文件,如為`D3`JS 庫,可以創建這樣的聲明文件:
~~~
declare namespace D3{
export interface Selectors { ... }
}
declare var d3: D3.Base;
復制代碼
~~~
所以上述兩個文件:
* `shims-tsx.d.ts`, 在全局變量`global`中批量命名了數個內部模塊。
* `shims-vue.d.ts`,意思是告訴`TypeScript``*.vue`后綴的文件可以交給`vue`模塊來處理。
### 3.6 訪問修飾符:`private`、`public`、`protected`
其實很好理解:
1. 默認為`public`
2. 當成員被標記為`private`時,它就不能在聲明它的類的外部訪問,比如:
~~~
class Animal {
private name: string;
constructor(theName: string) {
this.name = theName;
}
}
let a = new Animal('Cat').name; //錯誤,‘name’是私有的
復制代碼
~~~
3. `protected`和`private`類似,但是,`protected`成員在派生類中可以訪問
~~~
class Animal {
protected name: string;
constructor(theName: string) {
this.name = theName;
}
}
class Rhino extends Animal {
constructor() {
super('Rhino');
}
getName() {
console.log(this.name) //此處的name就是Animal類中的name
}
}
復制代碼
~~~
### 3.7 可選參數 ( ?: )和非空斷言操作符(!.)
**可選參數**
~~~
function buildName(firstName: string, lastName?: string) {
return firstName + ' ' + lastName
}
// 錯誤演示
buildName("firstName", "lastName", "lastName")
// 正確演示
buildName("firstName")
// 正確演示
buildName("firstName", "lastName")
復制代碼
~~~
**非空斷言操作符:**
能確定變量值一定不為空時使用。
與可選參數 不同的是,非空斷言操作符不會防止出現 null 或 undefined。
~~~
let s = e!.name; // 斷言e是非空并訪問name屬性
復制代碼
~~~
## 4.`Vue`組件的`Ts`寫法
從 vue2.5 之后,vue 對 ts 有更好的支持。根據[官方文檔](https://vuejs.org/v2/guide/typescript.html),vue 結合 typescript ,有兩種書寫方式:
\*\*Vue.extend \*\*
~~~
import Vue from 'vue'
const Component = Vue.extend({
// type inference enabled
})
復制代碼
~~~
**vue-class-component**
~~~
import { Component, Vue, Prop } from 'vue-property-decorator'
@Component
export default class Test extends Vue {
@Prop({ type: Object })
private test: { value: string }
}
復制代碼
~~~
理想情況下,`Vue.extend`的書寫方式,是學習成本最低的。在現有寫法的基礎上,幾乎 0 成本的遷移。
但是`Vue.extend`模式,需要與`mixins`結合使用。在 mixin 中定義的方法,不會被 typescript 識別到
,這就意味著會出現**丟失代碼提示、類型檢查、編譯報錯等問題。**
菜鳥才做選擇,大佬都挑最好的。直接講第二種吧:
### 4.1`vue-class-component`

我們回到`src/components/HelloWorld.vue`
~~~
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<!-- 省略 -->
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
@Component
export default class HelloWorld extends Vue {
@Prop() private msg!: string;
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped></style>
復制代碼
~~~
有寫過`python`的同學應該會發現似曾相識:
* `vue-property-decorator`這個官方支持的庫里,提供了函數 \*\*裝飾器(修飾符)\*\*語法
#### 1\. 函數修飾符`@`
“@”,與其說是修飾函數倒不如說是引用、調用它修飾的函數。
或者用句大白話描述:`@`: "下面的被我包圍了。"
舉個栗子,下面的一段代碼,里面兩個函數,沒有被調用,也會有輸出結果:
~~~
test(f){
console.log("before ...");
f()
console.log("after ...");
}
@test
func(){
console.log("func was called");
}
復制代碼
~~~
直接運行,輸出結果:
~~~
before ...
func was called
after ...
復制代碼
~~~
上面代碼可以看出來:
* 只定義了兩個函數:`test`和`func`,沒有調用它們。
* 如果沒有[“@test](mailto:%22@test)”,運行應該是沒有任何輸出的。
但是,解釋器讀到函數修飾符“@”的時候,后面步驟會是這樣:
1. 去調用`test`函數,`test`函數的入口參數就是那個叫“`func`”的函數;
2. `test`函數被執行,入口參數的(也就是`func`函數)會被調用(執行);
換言之,修飾符帶的那個函數的入口參數,就是下面的那個整個的函數。有點兒類似`JavaScrip`t里面的`function a (function () { ... });`

#### 2.`vue-property-decorator`和`vuex-class`提供的裝飾器
`vue-property-decorator`的裝飾器:
* [`@Prop`](https://github.com/kaorun343/vue-property-decorator#Prop)
* [`@PropSync`](https://github.com/kaorun343/vue-property-decorator#PropSync)
* [`@Provide`](https://github.com/kaorun343/vue-property-decorator#Provide)
* [`@Model`](https://github.com/kaorun343/vue-property-decorator#Model)
* [`@Watch`](https://github.com/kaorun343/vue-property-decorator#Watch)
* [`@Inject`](https://github.com/kaorun343/vue-property-decorator#Provide)
* [`@Provide`](https://github.com/kaorun343/vue-property-decorator#Provide)
* [`@Emit`](https://github.com/kaorun343/vue-property-decorator#Emit)
* `@Component`(**provided by**[vue-class-component](https://github.com/vuejs/vue-class-component))
* `Mixins`(the helper function named`mixins`**provided by**[vue-class-component](https://github.com/vuejs/vue-class-component))
`vuex-class`的裝飾器:
* @State
* @Getter
* @Action
* @Mutation
我們拿原始Vue組件模版來看:
~~~
import {componentA,componentB} from '@/components';
export default {
components: { componentA, componentB},
props: {
propA: { type: Number },
propB: { default: 'default value' },
propC: { type: [String, Boolean] },
}
// 組件數據
data () {
return {
message: 'Hello'
}
},
// 計算屬性
computed: {
reversedMessage () {
return this.message.split('').reverse().join('')
}
// Vuex數據
step() {
return this.$store.state.count
}
},
methods: {
changeMessage () {
this.message = "Good bye"
},
getName() {
let name = this.$store.getters['person/name']
return name
}
},
// 生命周期
created () { },
mounted () { },
updated () { },
destroyed () { }
}
復制代碼
~~~
以上模版替換成修飾符寫法則是:
~~~
import { Component, Vue, Prop } from 'vue-property-decorator';
import { State, Getter } from 'vuex-class';
import { count, name } from '@/person'
import { componentA, componentB } from '@/components';
@Component({
components:{ componentA, componentB},
})
export default class HelloWorld extends Vue{
@Prop(Number) readonly propA!: number | undefined
@Prop({ default: 'default value' }) readonly propB!: string
@Prop([String, Boolean]) readonly propC!: string | boolean | undefined
// 原data
message = 'Hello'
// 計算屬性
private get reversedMessage (): string[] {
return this.message.split('').reverse().join('')
}
// Vuex 數據
@State((state: IRootState) => state . booking. currentStep) step!: number
@Getter( 'person/name') name!: name
// method
public changeMessage (): void {
this.message = 'Good bye'
},
public getName(): string {
let storeName = name
return storeName
}
// 生命周期
private created ():void { },
private mounted ():void { },
private updated ():void { },
private destroyed ():void { }
}
復制代碼
~~~
正如你所看到的,我們在生命周期 列表那都添加`private XXXX`方法,因為這不應該公開給其他組件。
而不對`method`做私有約束的原因是,可能會用到`@Emit`來向父組件傳遞信息。
### 4.2 添加全局工具
引入全局模塊,需要改`main.ts`:
~~~
import Vue from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';
Vue.config.productionTip = false;
new Vue({
router,
store,
render: (h) => h(App),
}).$mount('#app');
復制代碼
~~~
`npm i VueI18n`
~~~
import Vue from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';
// 新模塊
import i18n from './i18n';
Vue.config.productionTip = false;
new Vue({
router,
store,
i18n, // 新模塊
render: (h) => h(App),
}).$mount('#app');
復制代碼
~~~
但僅僅這樣,還不夠。你需要動`src/vue-shim.d.ts`:
~~~
// 聲明全局方法
declare module 'vue/types/vue' {
interface Vue {
readonly $i18n: VueI18Next;
$t: TranslationFunction;
}
}
復制代碼
~~~
之后使用`this.$i18n()`的話就不會報錯了。
### 4.3 Axios 使用與封裝
`Axios`的封裝千人千面
如果只是想簡單在Ts里體驗使用`Axios`,可以安裝`vue-axios`**簡單使用`Axios`**
~~~
$ npm i axios vue-axios
復制代碼
~~~
`main.ts`添加:
~~~
import Vue from 'vue'
import axios from 'axios'
import VueAxios from 'vue-axios'
Vue.use(VueAxios, axios)
復制代碼
~~~
然后在組件內使用:
~~~
Vue.axios.get(api).then((response) => {
console.log(response.data)
})
this.axios.get(api).then((response) => {
console.log(response.data)
})
this.$http.get(api).then((response) => {
console.log(response.data)
})
復制代碼
~~~
#### 1\. 新建文件`request.ts`
文件目錄:
~~~
-api
- main.ts // 實際調用
-utils
- request.ts // 接口封裝
復制代碼
~~~
#### 2.`request.ts`文件解析
~~~
import * as axios from 'axios';
import store from '@/store';
// 這里可根據具體使用的UI組件庫進行替換
import { Toast } from 'vant';
import { AxiosResponse, AxiosRequestConfig } from 'axios';
/* baseURL 按實際項目來定義 */
const baseURL = process.env.VUE_APP_URL;
/* 創建axios實例 */
const service = axios.default.create({
baseURL,
timeout: 0, // 請求超時時間
maxContentLength: 4000,
});
service.interceptors.request.use((config: AxiosRequestConfig) => {
return config;
}, (error: any) => {
Promise.reject(error);
});
service.interceptors.response.use(
(response: AxiosResponse) => {
if (response.status !== 200) {
Toast.fail('請求錯誤!');
} else {
return response.data;
}
},
(error: any) => {
return Promise.reject(error);
});
export default service;
復制代碼
~~~
為了方便,我們還需要定義一套固定的 axios 返回的格式,新建`ajax.ts`:
~~~
export interface AjaxResponse {
code: number;
data: any;
message: string;
}
復制代碼
~~~
#### 3.`main.ts`接口調用:
~~~
// api/main.ts
import request from '../utils/request';
// get
export function getSomeThings(params:any) {
return request({
url: '/api/getSomethings',
});
}
// post
export function postSomeThings(params:any) {
return request({
url: '/api/postSomethings',
methods: 'post',
data: params
});
}
復制代碼
~~~
## 5\. 編寫一個組件
為了減少時間,我們來替換掉`src/components/HelloWorld.vue`,做一個博客帖子組件:
~~~
<template>
<div class="blogpost">
<h2>{{ post.title }}</h2>
<p>{{ post.body }}</p>
<p class="meta">Written by {{ post.author }} on {{ date }}</p>
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
// 在這里對數據進行類型約束
export interface Post {
title: string;
body: string;
author: string;
datePosted: Date;
}
@Component
export default class HelloWorld extends Vue {
@Prop() private post!: Post;
get date() {
return `${this.post.datePosted.getDate()}/${this.post.datePosted.getMonth()}/${this.post.datePosted.getFullYear()}`;
}
}
</script>
<style scoped>
h2 {
text-decoration: underline;
}
p.meta {
font-style: italic;
}
</style>
復制代碼
~~~
然后在`Home.vue`中使用:
~~~
<template>
<div class="home">
<img alt="Vue logo" src="../assets/logo.png">
<HelloWorld v-for="blogPost in blogPosts" :post="blogPost" :key="blogPost.title" />
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import HelloWorld, { Post } from '@/components/HelloWorld.vue'; // @ is an alias to /src
@Component({
components: {
HelloWorld,
},
})
export default class Home extends Vue {
private blogPosts: Post[] = [
{
title: 'My first blogpost ever!',
body: 'Lorem ipsum dolor sit amet.',
author: 'Elke',
datePosted: new Date(2019, 1, 18),
},
{
title: 'Look I am blogging!',
body: 'Hurray for me, this is my second post!',
author: 'Elke',
datePosted: new Date(2019, 1, 19),
},
{
title: 'Another one?!',
body: 'Another one!',
author: 'Elke',
datePosted: new Date(2019, 1, 20),
},
];
}
</script>
復制代碼
~~~
這時候運行項目:

這就是簡單的父子組件

## 6\. 參考文章
[TypeScript?—?JavaScript with superpowers?—?Part II](https://medium.com/cleversonder/typescript-javascript-with-superpowers-part-ii-69a6bd2c6842)
[VUE WITH TYPESCRIPT](https://ordina-jworks.github.io/vue/2019/03/04/vue-with-typescript.html#6-your-first-deployment)
[TypeScript + 大型項目實戰](https://juejin.im/post/5b54886ce51d45198f5c75d7#heading-6)
[Python修飾符 (一)—— 函數修飾符 “@”](https://blog.csdn.net/972301/article/details/59537712)
[Typescript 中的 interface 和 type到底有什么區別](https://juejin.im/post/5c2723635188252d1d34dc7d#heading-11)
## 7\. 總結

而關于`Class API`撤銷,其實還是挺舒服的。 用`class`來編寫`Vue`組件確實太奇怪了。 (所以我這篇`Ts`入門壓根沒寫`Class API`)
