Vue +Ts

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 }

個人筆記,還沒完~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章