Vue + Ts 一
安裝環境
// 安裝 vue 腳手架, 安裝到全局
npm i @vue/cli -g
// 創建項目
vue create vue-ts
// 自定義項目
// 勾選上 Typescript, Babel
// use class-style component yes 這個的話是用 寫 class 風格的 vue 組件
// 把當前項目跑起來
npm run serve
配置文件:
tsconfig.json 配置 ts 編譯環境
xx.d.ts 支持 vue, jsx, ts 寫法
組件的定義有三種形式,一種是類的形式,一種是函數式,一種是擴展式
src/component
我們開始編寫代碼啦 ~
App.vue
<template>
<div id="app">
<h3>vue + ts</h3>
// 傳值
<ClassComponent p1="呀呀呀" />
<ExtendComponent p1="呀呀呀" />
<FunctionComponent p1="哈哈" />
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import { Component } from 'vue-property-decorator';
import ClassComponent from './components/ClassComponent.vue';
import ExtendComponent from './components/ExtendComponent.vue';
import FunctionComponentfrom './components/FunctionComponent.vue';
// 我們需要用裝飾器來裝飾一下我們的類
// 我們就可以在我們的裝飾器下面通過 components 選項來引入我們的組件了, 那我麼就可以來使用了
@Component({
components: {
ClassComponent,
ExtendComponent,
FunctionComponent
}
})
export default class App extends Vue {
show(arg: number): void {
console.log(arg);
}
}
</script>
<style>
</style>
我們主要關注的點放在類組件上面
第一點:元數據
第二點:定義事件
第三點: 計算屬性
第四點:鉤子函數
第五點:props
第六點:屬性檢測
第七點:ref
第八點:指令、過濾器(全局 ~ vue/ 局部)
類組件
ClassComponent.vue
<template>
<div id="app">
<h3>類組件</h3>
<div>{{msg1}}</div>
<div>{{msg2}}</div>
// 會報錯的,因爲賦值爲 undefined,所以它會導致渲染有問題,還有會導致我們在渲染的過程當中啊,不響應, 所以千萬別做成 undefined 啊
// <div>{{msg3}}</div>
// msg4 呢是不渲染的,所以頁面上我們是看不到的,但是呢,它是響應式的,所以我們可以改變以下,頁面就可以看到啦
<div>{{msg4}}</div>
<div>{{msg5}}</div>
// 計算屬性
<h4>{{cptMsg1}}</h4>
// props
<div>{{this.p1}}</div>
// ref
<div ref="box">box</div>
// 指令
<div v-direc1="'qq'">direc1</div>
//過濾
<div>{{'123'| filt1}}</div>
// 這是講事件了哦 ~
<button @click="show(1, 'bingo')">事件</button>
</div>
</template>
// 書寫 class 風格的 vue 組件
// 第一是語言,ts 語言
// 第二個是裝飾器,從 vue-property-decorator 引入 Component, 用這個裝飾器來裝飾我們的類,一定要裝飾,不然我們就沒辦法識別這個類爲 vue 組件了
<script lang="ts">
import Vue from 'vue';
import { Component, Prop, Watch, Ref } from 'vue-property-decorator';
// 以下是會爆紅呢,我們只需要引入 types 就好了,我們不需要引 types 下面的文件
// import { Person } from '../types/index.ts' 錯誤的方式啦 ~
import { Person } from '../types' // bingo 正確
// 我們需要用裝飾器來裝飾一下我們的類
// 這就是基本的用 類的形式 定義我們的 vue 的組件
@Component({
// 我們的裝飾器不要定義到類內,在編譯打包的時候可能真的會報錯,我們定義到裝飾器裏面去
// 局部指令
directives: {
// 移上去就可以看到參數的類型
direc1: (el: HTMLElement, binding) => console.log('direc1'+binding.value);
},
// 局部過濾器
filters: {
// 可以接收到需要過濾的數據 可以設置默認值
filt1(data: string, arg: number = 2): string {
return '過濾後的數據' + data;
}
},
})
export default class classComponent extends Vue {
// props 定義,需要依賴裝飾器
// 通過 Prop 裝飾器,我們就可以把上方傳給我們的 props 屬性裝飾爲一個類內部的實例屬性
// 我們給 p1 約束了類型之後,飄紅,說我們沒有給 p1 初始化,那這個時候,我們就需要加個 !,表明,如果沒有給它傳入這個 props,那它就是 undefined
// ! 操作符 (Bang Operator) 顯示地告訴編輯器它的值不爲空
// 如果不寫用 ! 操作符,我們也可以用聯合類型,聯合一個 undefined
// 我們也可以給它加上一些修飾符,比如說 readonly ,不允許改,一旦改, 就會報錯
@Prop() readonly p1!: string;
// 如果外部沒有給我們傳呢?那我們是不是應該也可以定義一個默認值啊?
// 在 Prop 中定義一個對象,設置默認值,也可以定義類型
@Prop({default: 'p2默認值', type: string}) readonly p2: string | undefined;
// 特別注意: 這樣是會報錯的!!!如果我們要給默認值,應該像 p2 一樣在 Props 裝飾器中給,而不是這樣給,運行的時候是會報錯噠 !
@Prop() private readonly p3: string ="qq";
// 接下來我們就要說我們的元數據了,就是需要我們的 data 轉變,將它轉變成我們的類內的實例屬性就可以了
// 元數據 data -> 類內的實例屬性
msg1: string = '加油努力哦~';
msg2: string = '加油努力哦~' + this.p1;
msg3: undefined = undefined; // undefined 非響應式,通常我們不會這樣玩
// 頁面雖然不渲染,但是是響應式的,any 是所有類型的父類型,null 是任何類型的子類型,所以 null 賦值給 any 是可以的
msg4: any = null;
msg5: Person = { id: 1, name: 'alex', age: 18}
// 以上的數據呢,我們也可以通過 this 來訪問到它,而且還會有提示哦 ~
// ts 呢,是一開始使用的時候可能會有點麻煩,但是用了之後,可好用了 ~ 動態一時爽,重構火葬場
// 方法 我們的方法不是寫在 methods 裏面了,而是類內的一個實例方法
// 實例方法 -> methods 裏面的方法
show(arg1: number, arg2: string): void {
alert('這是類組件的實例方法' + arg1+ arg2);
}
// 計算屬性
// 計算屬性一定有返回值啊,我們讓它返回 string 類型,然後飄紅,爲什麼呢?
// 因爲我們約束了它返回 string 類型的值,但是沒有給返回值啊
// 那這就是計算屬性了嗎?那它跟普通方法有什麼區別呢?
// 計算屬性前面加上 get
// 計算屬性和元數據一樣,也是可以響應式渲染的
get cptMsg1(): string{
return '經過計算後的' + this.msg1;
}
// 鉤子函數在類組件中的定義和擴展形組件定義是一樣的
mounted(): void {
console.log('class 組件 mounted', this.msg5.id);
// 比如說我們在這裏來訪問一下我們的 ref
this.bbox.style.background = 'red';
}
// 屬性檢測 在 vue 中呢用的是 watch, 那我們去引入 Watch 裝飾器了
// 比如我想觀測上面元數據的變化,數據變化觸發 onMsgChange 事件,而且可以接收到 newValue 和 oldValue
@Watch('msg1')
onMsgChange(newValue: string, oldValue: string): void {
console.log('msg1 數據變化了');
}
// 屬性觀測有時候觀測不到複雜性的數據,所以我們需要深度觀測
// 而且屬性觀測很奇怪,在組件第一次掛載的時候,它不會執行,所以我們需要去定義一下
// 就比如說我們的 msg5,這個時候我們就需要深度觀測了,而且我們需要讓它在組件一掛載的時候就運行,所以我們需要要設置它的配置選項
@Watch('msg5', {deep: true, immediate: true})
onMsg5Change(newValue: string, oldValue: string): void {
console.log('msg5 數據變化了', newValue, oldValue);
}
// 我們在 Vue 類組件裏面使用裝飾器,都是相反設法的要把我們的屬性裝飾爲我們內部的成員,它可能是一個實例屬性,也可能是一個實例方法
// ref 引用元素 使用 Ref 裝飾器 我們是想讓它裝飾爲我們內部的屬性對不對,那我們起什麼名都可以, 我們拿到的是一個 dom 元素對不對
// 那我們的 bbox 就可以通過我們的 js 來訪問它了 this.bbox
@Ref("box") bbox!: HTMLDIVElement
}
// 定義 Person 的類型, 把 id 加 ? ,做成一個可選類型,這樣就不會報錯了
// readonly 只讀屬性,後期想改都改不了哦 ~
// 但是呢,這個類型可能不止這裏會用到,可能很多地方會用到,所以我們會在我們的文件夾裏面創建一個 types 目錄
/*
interface Person {
id?: number;
name: string;
age: number;
}
*/
</script>
<style>
</style>
那我們的擴展式組件裏面的 元數據 呢,其實跟我們一開始的用法是差不多的(data)啦 ~
擴展式組件
它的核心呢,就是用到了類上面的一個方法,extend
擴展形組件所有的定義跟我們之前學過的 vue 組件的定義是差不多的
ExtendComponent.vue
<template>
<div id="app">
<h3>這是一個擴展形的組件</h3>
// 擴展形裏面的元數據就出來啦 ~
<div>{{msg}}</div>
// 計算屬性
<h1>{{cptMsg}}</h1>
// 事件
<button @click="show(1, 'bingo')">事件</button>
// ref 跟我們之前學的 vue 是一樣的
<div ref="box">box</div>
</div>
</template>
// 擴展形組件裏面使用 ts ,其實跟原來的沒多大區別的
// 第一:加了語言標註 lang="ts"
// 第二, 使用 Vue.extend
// 所以我們在
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
// 元數據
data() {
return {
msg: '12'
}
},
// props
props: {
p1: {
type: string, // 設置類型
default: '默認值', // 設置默認值
required: false, // 設置爲必傳
validator: (val: string) => {val.length > 2}// 要求對參數做一個詳細的校驗
}
},
// 方法
methods: {
// 可以做成 public private 的方法
public show(arg1: number, arg2: string): void {
alert('擴展形組件 show' + arg1 + arg2);
}
},
// 計算屬性
computed: {
cptMsg(): string {
return '計算後的數據呢' + this.msg
}
},
mounted() {
console.log('這是一個擴展形的組件 mounted');
// this.$refs.box.style.background = 'steelblue';
},
// 屬性檢測
watch: {
msg(newValue: string, oldValue: string) {
console.log('擴展形組件裏面,他的 msg 變化了');
}
}
})
</script>
<style>
</style>
那我們要在函數式組件裏面寫我們的 元數據 嗎?不需要,因爲我媽們的函數式組件裏面根本就沒有狀態啊,它也沒有 this 上下文啊,它只是一個純 UI 組件,純渲染組件嘛,人家只是用來展示噠 ~
函數式組件裏面沒有 this,沒有我們的元數據,不可能通過 this 來拿到我們的方法,也就沒有我們的實例方法,函數式組件裏面只能拿到上方傳下來的 props,如果它想要拿到一個方法去調用的話,那需要上方給它傳下來
也木有計算屬性哦 ~
也木有鉤子函數哦 ~
但是我們可以好好聊一聊 props 啦 ~ 畢竟人家只有 props 啊
函數式組件
在模板上加上 functional ,這樣的話,它就會被標註成一個函數式組件
FunctionComponent.vue
那我們在 dev-tool 工具裏面查看它的屬性,發現沒有,但是本來就是這樣啊,函數式組件就是一個函數啊,一調就釋放了,一調就沒了,相對來說,函數式組件的性能是極高的,所以 react 推薦我們使用函數式組件啊 ~
<template functional>
<div id="app">
<h3>函數式組件</h3>
// 函數式組件裏面呢,默認會有一個 props 對象,我們可以直接去訪問它上面的屬性
<h4>{{props.p1}}</h4>
// 甚至我們可以直接去調用父組件中的實例方法
// 在函數式組件裏面,我們可以通過 parent 拿到父組件中的方法
<button @click="parent.show(1)"></button>
</div>
</template>
types 目錄哦 ~
index.ts
export interface Person {
id?: number;
name: string;
age: number;
}
Vue 生態圈
Vue + Ts二:
router.js 在 路由中的使用
import Vue from 'vue';
import VueRouter from 'vue-router';
import Home from '../pages/Home';
Vue.use(VueRouter); // 安裝插件
// 路由獨享守衛沒有後置,只有前置
// to from next 等需要類型,routes的類型是通過推論來的,那推論裏面的這個需要加類型了,需要引入,有時候編輯器會幫我們引入,如果沒有就自己引入
let routes = [
{path: '/home', component: Home},
{ path: '/user',
component: User,
beforeEnter: (to: Route, from: Route, next: Function) => {
console.log('獨享前置守衛');
next();
}
}
{path: '/Default', component: (r: any) => {require(['../pages/Default'], r)}},
{path: '/home', component: () => import('../pages/NoPage.vue')},
{path: '/', redirect: '/home'},
];
let router = new VueRouter({
mode: 'history',
routes
})
router.beforeEach((to, from, next) => {
console.log('全局前置守衛');
next();
})
router.afterEach((to, from) => {
console.log('全局後置守衛');
})
export.default = router;
組件假設:
Home.vue
<template>
<div>
<h3>首頁</h3>
</div>
</template>
<script>
import Vue from 'vue'
import { Component } from 'vue-property-decorator';
import { Route } from 'vue-router';
@Component({
beforeRouteEnter(to: Route, from: Route, next: Function) {
console.log('組件前置守衛,這裏組件還沒有實例化,還不能使用 this');
},
beforeRouteLeave(to: Route, from: Route, next: Function) {
console.log('組件後置守衛');
}
})
export default class Home extends Vue{
mounted():void {}
}
</script>
在 vue 中使用 axios ,只有聲明瞭類型聲明文件才能使用 ts,vue-router 中是自己攜帶了,像 axios 這樣的就沒有攜帶需要我們自己去裝
npm i axios @types/axios -S
axios.ts
對 axios 進行二次封裝,添加攔截器,統一添加 token
import axios, { AxiosRequestConfig } from 'axios';
import router from './router'
// 請求攔截
axios.interceptors.request.use((config: AxiosRequestConfig) => {
// 在這裏可以對請求進行統一處理
// 抓取 token
// 攜帶請求頭
// 顯示 loading
let user = window.localStorage.getItem('user');
user = user ? JSON.parse(user) : '';
config.headers = {token: user.token} // 攜帶到請求頭
return config;
}, (error) => {
return Promise.reject(error);
})
// 響應攔截
axios.interceptors.response.use((response: AxiosResponse<any>):AxiosResponse<any> => {
// 響應的攔截我們通常會做一些什麼事呢?
// 比如說,token 過期,跳轉 login,保留當前地址,登錄完了就可以回跳過來的
// 關閉 loading
if(response.data.error === 2 && !router.currentRoute.fullPath.includes('./login')) {
router.push({
path: '/login',
query: {
path: router.currentRoute.fullPath;
}
})
}
}, (error) => {
return Promise.reject(error);
})
// 對外暴露
window.axios = axios;
export default axios;
index.ts
interface User {
err: number,
msg: string
// 返回的數據的類型
}
// 所有的 User 都加上了可選,但是可能返回值是一個字符串,或者出錯了是 null
type TUser = Partial<IUser> & string | null
// 定義了一個全局變量,重定義了 window 接口
declare global {
interface Window {
axios(config: AxiosRequestConfig): AxiosPromise<any>
}
}
export { Person, TUser }
個人筆記,還沒完~