TypeScript+Vue3
TypeScript
Any 類型 和 unknown 頂級類型
1.沒有強制限定哪種類型,隨時切換類型都可以 我們可以對 any 進行任何操作,不需要檢查類型
2.聲明變量的時候沒有指定任意類型默認為any
3.弊端如果使用any 就失去了TS類型檢測的作用
4.TypeScript 3.0中引入的 unknown 類型也被認為是 top type ,但它更安全。與 any 一樣,所有類型都可以分配給unknown
//unknown 可以定義任何類型的值
let value: unknown;
value = true; // OK
value = 42; // OK
value = "Hello World"; // OK
value = []; // OK
value = {}; // OK
value = null; // OK
value = undefined; // OK
value = Symbol("type"); // OK
//這樣寫會報錯unknow類型不能作為子類型只能作為父類型 any可以作為父類型和子類型
//unknown類型不能賦值給其他類型
let names:unknown = '123'
let names2:string = names
//這樣就沒問題 any類型是可以的
let names:any = '123'
let names2:string = names
//unknown可賦值對象只有unknown 和 any
let bbb:unknown = '123'
let aaa:any= '456'
aaa = bbb
接口和對象類型
//這樣寫是會報錯的 因為我們在person定義了a,b但是對象里面缺少b屬性
//使用接口約束的時候不能多一個屬性也不能少一個屬性
//必須與接口保持一致
interface Person {
b:string,
a:string
}
const person:Person = {
a:"213"
}
對象的類型
在typescript中,我們定義對象的方式要用關鍵字interface(接口),我的理解是使用interface來定義一種約束,讓數據的結構滿足約束的格式。定義方式如下:
//重名interface 可以合并
interface A{name:string}
interface A{age:number}
var x:A={name:'xx',age:20}
//繼承
interface A{
name:string
}
interface B extends A{
age:number
}
let obj:B = {
age:18,
name:"string"
}
可選屬性 使用?操作符
//可選屬性的含義是該屬性可以不存在
//所以說這樣寫也是沒問題的
interface Person {
b?:string,
a:string
}
const person:Person = {
a:"213"
}
任意屬性 [propName: string]
//在這個例子當中我們看到接口中并沒有定義C但是并沒有報錯
//應為我們定義了[propName: string]: any;
//允許添加新的任意屬性
interface Person {
b?:string,
a:string,
[propName: string]: any;
}
const person:Person = {
a:"213",
c:"123"
}
只讀屬性 readonly
//這樣寫是會報錯的
//應為a是只讀的不允許重新賦值
interface Person {
b?: string,
readonly a: string,
[propName: string]: any;
}
const person: Person = {
a: "213",
c: "123"
}
person.a = 123
添加函數
interface Person {
b?: string,
readonly a: string,
[propName: string]: any;
cb():void
}
const person: Person = {
a: "213",
c: "123",
cb:()=>{
console.log(123)
}
}
數組類型
//類型加中括號
let arr:number[] = [123]
//這樣會報錯定義了數字類型出現字符串是不允許的
let arr:number[] = [1,2,3,'1']
//操作方法添加也是不允許的
let arr:number[] = [1,2,3,]
arr.unshift('1')
var arr: number[] = [1, 2, 3]; //數字類型的數組
var arr2: string[] = ["1", "2"]; //字符串類型的數組
var arr3: any[] = [1, "2", true]; //任意類型的數組
數組泛型
let arr:Array<number> = [1,2,3,4,5]
arguments類數組
function Arr(...args:any): void {
console.log(arguments)
//錯誤的arguments 是類數組不能這樣定義
let arr:number[] = arguments
}
Arr(111, 222, 333)
function Arr(...args:any): void {
console.log(arguments)
//ts內置對象IArguments 定義
let arr:IArguments = arguments
}
Arr(111, 222, 333)
//其中 IArguments 是 TypeScript 中定義好了的類型,它實際上就是:
interface IArguments {
[index: number]: any;
length: number;
callee: Function;
}
函數的類型
//注意,參數不能多傳,也不能少傳 必須按照約定的類型來
const fn = (name: string, age:number): string => {
return name + age
}
fn('張三',18)
函數的可選參數?
//通過?表示該參數為可選參數
const fn = (name: string, age?:number): string => {
return name + age
}
fn('張三')
函數參數的默認值
定義剩余參數
const fn = (array:number[],...items:any[]):any[] => {
console.log(array,items)
return items
}
let a:number[] = [1,2,3]
fn(a,'4','5','6')
函數重載
重載是方法名字相同,而參數不同,返回類型可以相同也可以不同。
如果參數類型不同,則參數類型應設置為 any。
參數數量不同你可以將不同的參數設置為可選。
function fn(params: number): void
function fn(params: string, params2: number): void
function fn(params: any, params2?: any): void {
console.log(params)
console.log(params2)
}
fn(123)
fn('123',456)
類型斷言 | 聯合類型 | 交叉類型
聯合類型
//例如我們的手機號通常是13XXXXXXX 為數字類型 這時候產品說需要支持座機
//所以我們就可以使用聯合類型支持座機字符串
let myPhone: number | string = '010-820'
//這樣寫是會報錯的應為我們的聯合類型只有數字和字符串并沒有布爾值
let myPhone: number | string = true
const fn = (something:number | boolean):boolean => {
return !!something
}
交叉類型
interface People {
age: number,
height: number
}
interface Man{
sex: string
}
const xiaoman = (man: People & Man) => {
console.log(man.age)
console.log(man.height)
console.log(man.sex)
}
xiaoman({age: 18,height: 180,sex: 'male'});
類型斷言
語法:值 as 類型 或 <類型>值 value as string <string>value
interface A {
run: string
}
interface B {
build: string
}
const fn = (type: A | B): string => {
return type.run
}
//這樣寫是有警告的應為B的接口上面是沒有定義run這個屬性的
interface A {
run: string
}
interface B {
build: string
}
const fn = (type: A | B): string => {
return (type as A).run
}
//可以使用類型斷言來推斷他傳入的是A接口的值
需要注意的是,類型斷言只能夠「欺騙」TypeScript 編譯器,無法避免運行時的錯誤,反而濫用類型斷言可能會導致運行時錯誤:
使用any臨時斷言
window.abc = 123
//這樣寫會報錯因為window沒有abc這個東西
(window as any).abc = 123
//可以使用any臨時斷言在 any 類型的變量上,訪問任何屬性都是允許的。
as const
是對字面值的斷言,與const直接定義常量是有區別的
如果是普通類型跟直接const 聲明是一樣的
const names = '小滿'
names = 'aa' //無法修改
let names2 = '小滿' as const
names2 = 'aa' //無法修改
// 數組
let a1 = [10, 20] as const;
const a2 = [10, 20];
a1.unshift(30); // 錯誤,此時已經斷言字面量為[10, 20],數據無法做任何修改
a2.unshift(30); // 通過,沒有修改指針
//unshift頭部插入
類型斷言是不具影響力的
function toBoolean(something: any): boolean {
return something as boolean;
}
toBoolean(1);
// 返回值為 1
//
Class類
//定義類
class Person {
constructor () {
}
run () {
}
}
類的修飾符
總共有三個 public private protected
使用public 修飾符 可以讓你定義的變量 內部訪問 也可以外部訪問 如果不寫默認就是public
使用 protected 修飾符 代表定義的變量私有的只能在內部和繼承的子類中訪問 不能在外部訪問
class Person {
public name:string
private age:number
protected some:any
constructor (name:string,ages:number,some:any) {
this.name = name
this.age = ages
this.some = some
}
run () {
}
}
class Man extends Person{
constructor () {
super("張三",18,1)
console.log(this.some)
}
create () {
console.log(this.some)
}
}
let xiaoman = new Person('小小',18,1)
let man = new Man()
man.some
ts interface 定義類 使用關鍵字 implements 后面跟interface的名字多個用逗號隔開 繼承還是用extends
interface PersonClass {
get(type: boolean): boolean
}
interface PersonClass2{
set():void,
asd:string
}
class A {
name: string
constructor() {
this.name = "123"
}
}
class Person extends A implements PersonClass,PersonClass2 {
asd: string
constructor() {
super()
this.asd = '123'
}
get(type:boolean) {
return type
}
set () {
}
}
元組類型
如果需要一個固定大小的不同類型值的集合,我們需要使用元組。
元組就是數組的變種
元組(Tuple)是固定數量的不同類型的元素的組合。
元組與集合的不同之處在于,元組中的元素類型可以是不同的,而且數量固定。元組的好處在于可以把多個元素作為一個單元傳遞。如果一個方法需要返回多個值,可以把這多個值作為元組返回,而不需要創建額外的類來表示。
let excel: [string, string, number, string][] = [
['title', 'name', 1, '123'],
['title', 'name', 1, '123'],
['title', 'name', 1, '123'],
['title', 'name', 1, '123'],
['title', 'name', 1, '123'],
]
枚舉類型
在javaScript中是沒有枚舉的概念的TS幫我們定義了枚舉這個類型(字典)
使用枚舉 通過enum關鍵字定義我們的枚舉
數字枚舉
例如 紅綠藍 Red = 0 Green = 1 Blue= 2 分別代表紅色0 綠色為1 藍色為2
enum Types{
Red,
Green,
BLue
}
字符串枚舉
由于字符串枚舉沒有自增長的行為,字符串枚舉可以很好的序列化。 換句話說,如果你正在調試并且必須要讀一個數字枚舉的運行時的值,這個值通常是很難讀的 - 它并不能表達有用的信息,字符串枚舉允許你提供一個運行時有意義的并且可讀的值,獨立于枚舉成員的名字。
enum Types{
Red = 'red',
Green = 'green',
BLue = 'blue'
}
異構枚舉
enum Types{
No = "No",
Yes = 1,
}
反向映射
它包含了正向映射( name -> value)和反向映射( value -> name)
要注意的是 不會為字符串枚舉成員生成反向映射。
enum Enum {
fall
}
let a = Enum.fall;
console.log(a); //0
let nameOfA = Enum[a];
console.log(nameOfA); //fall
類型別名
type str = string | number
let s:str = "我"
console.log(s);
type str = () => string
let s: str = () => "我"
console.log(s);
type value = boolean | 0 | '213'
let s:value = true
//變量s的值 只能是上面value定義的值
never類型
// 返回never的函數必須存在無法達到的終點
// 因為必定拋出異常,所以 error 將不會有返回值
function error(message: string): never {
throw new Error(message);
}
// 因為存在死循環,所以 loop 將不會有返回值
function loop(): never {
while (true) {
}
}
never 與 void 的差異
//void類型只是沒有返回值 但本身不會出錯
function Void():void {
console.log();
}
//只會拋出異常沒有返回值
function Never():never {
throw new Error('aaa')
}
never 類型的一個應用場景
interface A {
type: "foo"
}
interface B {
type: "bar"
}
type All = A | B ;
function handleValue(val: All) {
switch (val.type) {
case 'foo':
break;
case 'bar':
break
default:
//兜底邏輯 一般是不會進入這兒如果進來了就是程序異常了
const exhaustiveCheck:never = val;
break
}
}
比如長時間后新增了一個C接口,我們必須手動找到所有 switch 代碼并處理,否則將有可能引入 BUG 。
而且這將是一個“隱蔽型”的BUG,如果回歸面不夠廣,很難發現此類BUG。
interface A {
type: "foo"
}
interface B {
type: "bar"
}
interface C {
type: "bizz"
}
type All = A | B | C;
function handleValue(val: All) {
switch (val.type) {
case 'foo':
break;
case 'bar':
break
default:
//兜底邏輯 一般是不會進入這兒如果進來了就是程序異常了
const exhaustiveCheck: never = val;
break
}
}
由于任何類型都不能賦值給 never 類型的變量,所以當存在進入 default 分支的可能性時,TS的類型檢查會及時幫我們發現這個問題
symbol類型
自ECMAScript 2015起,symbol成為了一種新的原生類型,就像number和string一樣。
symbol類型的值是通過Symbol構造函數創建的。
可以傳遞參做為唯一標識 只支持 string 和 number類型的參數
let sym1 = Symbol();
let sym2 = Symbol("key"); // 可選的字符串key
Symbol的值是唯一的
const s1 = Symbol()
const s2 = Symbol()
// s1 === s2 =>false
用作對象屬性的鍵
let sym = Symbol();
let obj = {
[sym]: "value"
};
console.log(obj[sym]); // "value"
Symbol.iterator 迭代器 和 生成器 for of
for of 可以讀到value
for in 讀key
var arr = [1,2,3,4];
let iterator = arr[Symbol.iterator]();
console.log(iterator.next()); //{ value: 1, done: false }
console.log(iterator.next()); //{ value: 2, done: false }
console.log(iterator.next()); //{ value: 3, done: false }
console.log(iterator.next()); //{ value: 4, done: false }
console.log(iterator.next()); //{ value: undefined, done: true }
interface Item {
age: number,
name: string
}
const array: Array<Item> = [{ age: 123, name: "1" }, { age: 123, name: "2" }, { age: 123, name: "3" }]
type mapTypes = string | number
const map:Map<mapTypes,mapTypes> = new Map()
map.set('1','王爺')
map.set('2','陸北')
const obj = {
aaa:123,
bbb:456
}
let set:Set<number> = new Set([1,2,3,4,5,6])
// let it:Iterator<Item> = array[Symbol.iterator]()
const gen = (erg:any): void => {
let it: Iterator<any> = erg[Symbol.iterator]()
let next:any= { done: false }
while (!next.done) {
next = it.next()
if (!next.done) {
console.log(next.value)
}
}
}
gen(array)
泛型
函數泛型
function num (a:number,b:number) : Array<number> {
return [a ,b];
}
num(1,2)
function str (a:string,b:string) : Array<string> {
return [a ,b];
}
str('獨孤','求敗')
泛型優化
function Add<T>(a: T, b: T): Array<T> {
return [a,b]
}
Add<number>(1,2)
Add<string>('1','2')
我們也可以使用不同的泛型參數名,只要在數量上和使用方式上能對應上就可以。
function Sub<T,U>(a:T,b:U):Array<T|U> {
const params:Array<T|U> = [a,b]
return params
}
Sub<Boolean,number>(false,1)
定義泛型接口
聲明接口的時候 在名字后面加一個<參數>
使用的時候傳遞類型
interface MyInter<T> {
(arg: T): T
}
function fn<T>(arg: T): T {
return arg
}
let result: MyInter<number> = fn
result(123)
泛型約束
我們期望在一個泛型的變量上面,獲取其length參數,但是,有的數據類型是沒有length屬性的
function getLegnth<T>(arg:T) {
return arg.length
}
于是,我們就得對使用的泛型進行約束,我們約束其為具有length屬性的類型,這里我們會用到interface,代碼如下
interface Len {
length:number
}
function getLegnth<T extends Len>(arg:T) {
return arg.length
}
getLegnth<string>('123')
使用keyof 約束對象
其中使用了TS泛型和泛型約束。首先定義了T類型并使用extends關鍵字繼承object類型的子類型,然后使用keyof操作符獲取T類型的所有鍵,它的返回 類型是聯合 類型,最后利用extends關鍵字約束 K類型必須為keyof T聯合類型的子類型
function prop<T, K extends keyof T>(obj: T, key: K) {
return obj[key]
}
let o = { a: 1, b: 2, c: 3 }
prop(o, 'a')
prop(o, 'd') //此時就會報錯發現找不到
泛型類
聲明方法跟函數類似名稱后面定義<類型>
使用的時候確定類型new Sub()
class Sub<T>{
attr: T[] = [];
add (a:T):T[] {
return [a]
}
}
let s = new Sub<number>()
s.attr = [1,2,3]
s.add(123)
let str = new Sub<string>()
str.attr = ['1','2','3']
str.add('123')
Vue3
第一章、簡單介紹
回顧vue2 對比 vue3
? 發現傳統的vue2 邏輯比較分散 可讀性差 可維護性差
? 對比vue3 邏輯分明 可維護性 高
Vue3 新特性介紹:
重寫雙向綁定
Vue3 優化Vdomvue3
允許我們支持多個根節點
第二章、配置環境
構建vite項目
npm init vite@latest
npm install 安裝依賴包
npm run dev 啟動
第三章、模板語法
<template>
<div>{{ message }}</div>
</template>
<script setup lang="ts">
const message = "我是小滿"
</script>
<style>
</style>
<template>
<div>{{ message == 0 ? '我是小滿0' : '我不是小滿other' }}</div>
</template>
<script setup lang="ts">
const message:number = 1
</script>
<style>
</style>
<template>
<div>{{ message.split(',') }}</div>
</template>
<script setup lang="ts">
const message:string = "我,是,小,滿"
</script>
<style>
</style>
指令
v- 開頭都是vue 的指令
v-text 用來顯示文本
v-html 用來展示富文本
v-if 用來控制元素的顯示隱藏(切換真假DOM)
? (v-if會觸發組件的創建和銷毀鉤子)
v-else-if 表示 v-if 的“else if 塊”。可以鏈式調用
v-else v-if條件收尾語句
v-show 用來控制元素的顯示隱藏(display none block Css切換)
v-on 簡寫@ 用來給元素添加事件
v-bind 簡寫: 用來綁定元素的屬性Attr
v-model 雙向綁定
v-for 用來遍歷元素
v-on修飾符 冒泡案例
阻止表單提交案例
<template>
<form action="/">
<button @click.prevent="submit" type="submit">submit</button>
</form>
</template>
<script setup lang="ts">
const submit = () => {
console.log('child');
}
</script>
<style>
</style>
.stop阻止冒泡
<template>
<div @click="parent">
<div @click.stop="child">child</div>
</div>
</template>
<script setup lang="ts">
const child = () => {
console.log('child');
}
const parent = () => {
console.log('parent');
}
</script>
第四章、響應式對象
ref
接受一個內部值并返回一個響應式且可變的 ref 對象。ref 對象僅有一個 .value property,指向該內部值。
reactive
用來綁定復雜的數據類型 例如 對象 數組,使用reactive 去修改值無須.value
import { reactive } from 'vue'
let person = reactive({
name:"小"
})
person.name = "大"
toRef
如果原始對象是非響應式的就不會更新視圖 數據是會變的
toRaw
將響應式對象轉化為普通對象
第五章、計算屬性
computed用法
計算屬性就是當依賴的屬性的值發生變化的時候,才會觸發他的更改,如果依賴的值,不發生變化的時候,使用的是緩存中的屬性值。
函數形式
import { computed, reactive, ref } from 'vue'
let price = ref(0) //$ 0
let m = computed<string>(()=>{
return `$` + price.value
})
price.value = 500
對象形式
<template>
<div>{{ mul }}</div>
<div @click="mul = 100">click</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
let price = ref<number | string>(1)//$ 0
let mul = computed({
get: () => {
return price.value
},
set: (value) => {
price.value = 'set' + value
}
})
</script>
<style>
</style>
購物車案例
<template>
<div>
<table style="width: 800px" border>
<thead>
<tr>
<th>名稱</th>
<th>數量</th>
<th>價格</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr :key="index" v-for="(item, index) in data">
<td align="center">{{ item.name }}</td>
<td align="center">
<button @click="AddAnbSub(item, false)">-</button>
{{ item.num }}
<button @click="AddAnbSub(item, true)">+</button>
</td>
<td align="center">{{ item.num * item.price }}</td>
<td align="center">
<button @click="del(index)">刪除</button>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td></td>
<td></td>
<td></td>
<td align="center">總價:{{ $total }}</td>
</tr>
</tfoot>
</table>
</div>
</template>
<script setup lang="ts">
import { computed, reactive, ref } from "vue";
type Shop = {
name: string;
num: number;
price: number;
};
let $total = ref<number>(0);
const data = reactive<Shop[]>([
{
name: "襪子",
num: 1,
price: 100,
},
{
name: "褲子",
num: 1,
price: 200,
},
{
name: "衣服",
num: 1,
price: 300,
},
{
name: "毛巾",
num: 1,
price: 400,
},
]);
const AddAnbSub = (item: Shop, type: boolean = false) => {
if (item.num > 1 && !type) {
item.num--;
total()
}
if (item.num <= 99 && type) {
item.num++;
total()
}
};
const del = (index: number) => {
data.splice(index, 1);
};
// $total = computed<number>(() => {
// return data.reduce((prev, next) => {
// return prev + next.num * next.price;
// },0);
// });
const total = () => {
$total.value = data.reduce((pre, cur) => {
return pre + cur.num * cur.price;
}, 0);
};
total()
</script>
<style>
</style>
第六章、Watch監聽
watch
watch 需要偵聽特定的數據源,并在單獨的回調函數中執行副作用
watch第一個參數監聽源
watch第二個參數回調函數cb(newVal,oldVal)
watch第三個參數一個options配置項是一個對象{
immediate:true //是否立即調用一次
deep:true //是否開啟深度監聽
}
import { ref, watch ,reactive} from 'vue'
let message = reactive({
nav:{
bar:{
name:""
}
}
})
watch(message, (newVal, oldVal) => {
console.log('新的值----', newVal);
console.log('舊的值----', oldVal);
})
watchEffect
立即執行傳入的一個函數,同時響應式追蹤其依賴,并在其依賴變更時重新運行該函數。
如果用到message 就只會監聽message 就是用到幾個監聽幾個 而且是非惰性 會默認調用一次
let message = ref<string>('')
let message2 = ref<string>('')
watchEffect(() => {
//console.log('message', message.value);
console.log('message2', message2.value);
})
清除副作用
就是在觸發監聽之前會調用一個函數可以處理你的邏輯例如防抖
oninvalidate
import { watchEffect, ref } from 'vue'
let message = ref<string>('')
let message2 = ref<string>('')
watchEffect((oninvalidate) => {
//console.log('message', message.value);
oninvalidate(()=>{
})
console.log('message2', message2.value);
})
停止跟蹤 watchEffect
返回一個函數 調用之后將停止更新
const stop = watchEffect((oninvalidate) => {
//console.log('message', message.value);
oninvalidate(()=>{
})
console.log('message2', message2.value);
},{
flush:"post",
onTrigger () {
}
})
stop()
第七章、生命周期
nBeforeMount()
在組件DOM實際渲染安裝之前調用。在這一步中,根元素還不存在。
onMounted()
在組件的第一次渲染后調用,該元素現在可用,允許直接DOM訪問
onBeforeUpdate()
數據更新時調用,發生在虛擬 DOM 打補丁之前。
onUpdated()
DOM更新后,updated的方法即會調用。
onBeforeUnmount()
在卸載組件實例之前調用。在這個階段,實例仍然是完全正常的。
onUnmounted()
卸載組件實例后調用。調用此鉤子時,組件實例的所有指令都被解除綁定,所有事件偵聽器都被移除,所有子組件實例被卸載。
第八章、組件
父組件通過v-bind綁定一個數據,然后子組件通過defineProps接受傳過來的值,
給Menu組件 傳遞了一個title 字符串類型是不需要v-bind
<template>
<div class="layout">
<Menu title="我是標題"></Menu>
<div class="layout-right">
<Header></Header>
<Content></Content>
</div>
</div>
</template>
傳遞非字符串類型需要加v-bind 簡寫 冒號
<template>
<div class="layout">
<Menu v-bind:data="data" title="我是標題"></Menu>
<div class="layout-right">
<Header></Header>
<Content></Content>
</div>
</div>
</template>
<script setup lang="ts">
import Menu from './Menu/index.vue'
import Header from './Header/index.vue'
import Content from './Content/index.vue'
import { reactive } from 'vue';
const data = reactive<number[]>([1, 2, 3])
</script>
子組件接受值
通過defineProps 來接受
defineProps
如果我們使用的TypeScript,可以使用傳遞字面量類型的純類型語法做為參數
如 這是TS特有的
<template>
<div class="menu">
菜單區域 {{ title }}
<div>{{ data }}</div>
</div>
</template>
<script setup lang="ts">
defineProps<{
title:string,
data:number[]
}>()
</script>
withDefaults
是個函數也是無須引入開箱即用接受一個props函數第二個參數是一個對象設置默認值
type Props = {
title?: string,
data?: number[]
}
withDefaults(defineProps<Props>(), {
title: "張三",
data: () => [1, 2, 3]
})
子組件給父組件傳參
defineEmits
派發一個事件
<template>
<div class="menu">
<button @click="clickTap">派發給父組件</button>
</div>
</template>
<script setup lang="ts">
import { reactive } from 'vue'
const list = reactive<number[]>([4, 5, 6])
const emit = defineEmits(['on-click'])
const clickTap = () => {
emit('on-click', list)
}
</script>
我們在子組件綁定了一個click 事件 然后通過defineEmits 注冊了一個自定義事件
點擊click 觸發 emit 去調用我們注冊的事件 然后傳遞參數
父組件接受子組件的事件
<template>
<div class="layout">
<Menu @on-click="getList"></Menu>
<div class="layout-right">
<Header></Header>
<Content></Content>
</div>
</div>
</template>
<script setup lang="ts">
import Menu from './Menu/index.vue'
import Header from './Header/index.vue'
import Content from './Content/index.vue'
import { reactive } from 'vue';
const data = reactive<number[]>([1, 2, 3])
const getList = (list: number[]) => {
console.log(list,'父組件接受子組件');
}
</script>
defineExpose
子組件暴露給父組件內部屬性
<Menu ref="menus"></Menu>
const menus = ref(null)
然后打印menus.value 發現沒有任何屬性
這時候父組件想要讀到子組件的屬性可以通過 defineExpose暴露
const list = reactive<number[]>([4, 5, 6])
defineExpose({
list
})
全局組件
組件使用頻率非常高(table,Input,button,等)這些組件 幾乎每個頁面都在使用便可以封裝成全局組件
import { createApp } from 'vue'
import App from './App.vue'
import './assets/css/reset/index.less'
import Card from './components/Card/index.vue'
createApp(App).component('Card',Card).mount('#app')
遞歸組件
原理跟我們寫js遞歸是一樣的 自己調用自己 通過一個條件來結束遞歸 否則導致內存泄漏
在父組件配置數據結構 數組對象格式 傳給子組件
type TreeList = {
name: string;
icon?: string;
children?: TreeList[] | [];
};
const data = reactive<TreeList[]>([
{
name: "no.1",
children: [
{
name: "no.1-1",
children: [
{
name: "no.1-1-1",
},
],
},
],
},
{
name: "no.2",
children: [
{
name: "no.2-1",
},
],
},
{
name: "no.3",
},
]);
子組件接收值 第一個script
type TreeList = {
name: string;
icon?: string;
children?: TreeList[] | [];
};
type Props<T> = {
data?: T[] | [];
};
defineProps<Props<TreeList>>();
const clickItem = (item: TreeList) => {
console.log(item)
}
子組件增加一個script 定義組件名稱為了 遞歸用
<script lang="ts">
export default {
name:"TreeItem"
}
</script>
template
TreeItem 其實就是當前組件 通過import 把自身又引入了一遍 如果他沒有children 了就結束
<div style="margin-left:10px;" class="tree">
<div :key="index" v-for="(item,index) in data">
<div @click='clickItem(item)'>{{item.name}}
</div>
<TreeItem @on-click='clickItem' v-if='item?.children?.length' :data="item.children"></TreeItem>
</div>
</div>
動態組件
動態組件就是:讓多個組件使用同一個掛載點,并動態切換,這就是動態組件。
在掛載點使用component標簽,然后使用v-bind:is=”組件”
import A from './A.vue'
import B from './B.vue'
<component :is="A"></component>
注意事項
1.在Vue2 的時候is 是通過組件名稱切換的 在Vue3 setup 是通過組件實例切換的
2.如果你把組件實例放到Reactive Vue會給你一個警告runtime-core.esm-bundler.js:38 [Vue warn]: Vue received a Component which was made a reactive object. This can lead to unnecessary performance overhead, and should be avoided by marking the component with markRaw or using shallowRef instead of ref.
Component that was made reactive:
這是因為reactive 會進行proxy 代理 而我們組件代理之后毫無用處 節省性能開銷 推薦我們使用shallowRef 或者 markRaw 跳過proxy 代理
修改為如下
const tab = reactive<Com[]>([{
name: "A組件",
comName: markRaw(A)
}, {
name: "B組件",
comName: markRaw(B)
}])
第九章、插槽
插槽就是子組件中的提供給父組件使用的一個占位符,用 表示,父組件可以在這個占位符中填充任何模板代碼,如 HTML、組件等,填充的內容會替換子組件的標簽。
匿名插槽
在子組件放置一個插槽
<template>
<div>
<slot></slot>
</div>
</template>
父組件使用插槽
在父組件給這個插槽填充內容
<Dialog>
<template v-slot>
<div>2132</div>
</template>
</Dialog>
具名插槽
具名插槽其實就是給插槽取個名字。一個子組件可以放多個插槽,而且可以放在不同的地方,而父組件填充內容時,可以根據這個名字把內容填充到對應插槽中
<div>
<slot name="header"></slot>
<slot></slot>
<slot name="footer"></slot>
</div>
父組件使用需對應名稱
<Dialog>
<template v-slot:header>
<div>1</div>
</template>
<template v-slot>
<div>2</div>
</template>
<template v-slot:footer>
<div>3</div>
</template>
</Dialog>
插槽簡寫
<Dialog>
<template #header>
<div>1</div>
</template>
<template #default>
<div>2</div>
</template>
<template #footer>
<div>3</div>
</template>
</Dialog>
作用域插槽
在子組件動態綁定參數 派發給父組件的slot去使用
<div>
<slot name="header"></slot>
<div>
<div v-for="item in 100">
<slot :data="item"></slot>
</div>
</div>
<slot name="footer"></slot>
</div>
通過解構方式取值
<Dialog>
<template #header>
<div>1</div>
</template>
<template #default="{ data }">
<div>{{ data }}</div>
</template>
<template #footer>
<div>3</div>
</template>
</Dialog>
動態插槽
插槽可以是一個變量名
<Dialog>
<template #[name]>
<div>
23
</div>
</template>
</Dialog>
const name = ref('header')
(附加)異步組件&代碼分包&suspense
(附加)Teleport傳送組件
第十章、keep-alive緩存組件
有時候我們不希望組件被重新渲染影響使用體驗;或者處于性能考慮,避免多次重復渲染降低性能。而是希望組件可以緩存下來,維持當前的狀態。這時候就需要用到keep-alive組件。
開啟keep-alive 生命周期的變化
初次進入時: onMounted> onActivated
退出后觸發 deactivated
再次進入:只會觸發 onActivated
事件掛載的方法等,只執行一次的放在 onMounted中;組件每次進去執行的方法放在 onActivated中
<!-- 基本 -->
<keep-alive>
<component :is="view"></component>
</keep-alive>
<!-- 多個條件判斷的子組件 -->
<keep-alive>
<comp-a v-if="a > 1"></comp-a>
<comp-b v-else></comp-b>
</keep-alive>
<!-- 和 `<transition>` 一起使用 -->
<transition>
<keep-alive>
<component :is="view"></component>
</keep-alive>
</transition>
include 和 exclude
<keep-alive :include="" :exclude="" :max=""></keep-alive>
max
<keep-alive :max="10">
<component :is="view"></component>
</keep-alive>
第十一章、transition動畫組件
Vue 提供了 transition 的封裝組件,在下列情形中,可以給任何元素和組件添加進入/離開過渡:
條件渲染 (使用 v-if)
條件展示 (使用 v-show)
動態組件
組件根節點
自定義 transition 過度效果,你需要對transition組件的name屬性自定義。并在css中寫入對應的樣式
過渡 class
在進入/離開的過渡中,會有 6 個 class 切換。
v-enter-from:定義進入過渡的開始狀態。在元素被插入之前生效,在元素被插入之后的下一幀移除。
v-enter-active:定義進入過渡生效時的狀態。在整個進入過渡的階段中應用,在元素被插入之前生效,在過渡/動畫完成之后移除。這個類可以被用來定義進入過渡的過程時間,延遲和曲線函數。
v-enter-to:定義進入過渡的結束狀態。在元素被插入之后下一幀生效 (與此同時 v-enter-from 被移除),在過渡/動畫完成之后移除。
v-leave-from:定義離開過渡的開始狀態。在離開過渡被觸發時立刻生效,下一幀被移除。
v-leave-active:定義離開過渡生效時的狀態。在整個離開過渡的階段中應用,在離開過渡被觸發時立刻生效,在過渡/動畫完成之后移除。這個類可以被用來定義離開過渡的過程時間,延遲和曲線函數。
v-leave-to:離開過渡的結束狀態。在離開過渡被觸發之后下一幀生效 (與此同時 v-leave-from 被移除),在過渡/動畫完成之后移除。
<button @click='flag = !flag'>切換</button>
<transition name='fade'>
<div v-if='flag' class="box"></div>
</transition>
//開始過度
.fade-enter-from{
background:red;
width:0px;
height:0px;
transform:rotate(360deg)
}
//開始過度了
.fade-enter-active{
transition: all 2.5s linear;
}
//過度完成
.fade-enter-to{
background:yellow;
width:200px;
height:200px;
}
//離開的過度
.fade-leave-from{
width:200px;
height:200px;
transform:rotate(360deg)
}
//離開中過度
.fade-leave-active{
transition: all 1s linear;
}
//離開完成
.fade-leave-to{
width:0px;
height:0px;
}
自定義過渡 class 類名
trasnsition props
enter-from-classenter-active-classenter-to-classleave-from-classleave-active-classleave-to-class
自定義過度時間 單位毫秒
你也可以分別指定進入和離開的持續時間:
<transition :duration="1000">...</transition>
<transition :duration="{ enter: 500, leave: 800 }">...</transition>
通過自定義class 結合css動畫庫
animate css
安裝庫 npm install animate.css
引入 import 'animate.css'
使用方法
官方文檔 Animate.css | A cross-browser library of CSS animations.
<transition
leave-active-class="animate__animated animate__bounceInLeft"
enter-active-class="animate__animated animate__bounceInRight"
>
<div v-if="flag" class="box"></div>
</transition>
transition 生命周期8個
@before-enter="beforeEnter" //對應enter-from
@enter="enter"http://對應enter-active
@after-enter="afterEnter"http://對應enter-to
@enter-cancelled="enterCancelled"http://顯示過度打斷
@before-leave="beforeLeave"http://對應leave-from
@leave="leave"http://對應enter-active
@after-leave="afterLeave"http://對應leave-to
@leave-cancelled="leaveCancelled"http://離開過度打斷
當只用 JavaScript 過渡的時候,在 enter 和 leave 鉤子中必須使用 done 進行回調
結合**gsap **動畫庫使用 GreenSock
const beforeEnter = (el: Element) => {
console.log('進入之前from', el);
}
const Enter = (el: Element,done:Function) => {
console.log('過度曲線');
setTimeout(()=>{
done()
},3000)
}
const AfterEnter = (el: Element) => {
console.log('to');
}
appear
通過這個屬性可以設置初始節點過度 就是頁面加載完成就開始動畫 對應三個狀態
appear-active-class=""
appear-from-class=""
appear-to-class=""
appear
transition-group過度列表
單個節點
多個節點,每次只渲染一個
那么怎么同時渲染整個列表,比如使用 v-for?在這種場景下,我們會使用 組件。
默認情況下,它不會渲染一個包裹元素,但是你可以通過 tag attribute 指定渲染一個元素。
過渡模式不可用,因為我們不再相互切換特有的元素。
內部元素總是需要提供唯一的 key attribute 值。
CSS 過渡的類將會應用在內部的元素中,而不是這個組/容器本身
<transition-group>
<div style="margin: 10px;" :key="item" v-for="item in list">{{ item }</div>
</transition-group>
const list = reactive<number[]>([1, 2, 4, 5, 6, 7, 8, 9])
const Push = () => {
list.push(123)
}
const Pop = () => {
list.pop()
}
transition-group列表的移動過渡
組件還有一個特殊之處。除了進入和離開,它還可以為定位的改變添加動畫。只需了解新增的 v-move 類就可以使用這個新功能,它會應用在元素改變定位的過程中。像之前的類名一樣,它的前綴可以通過 name attribute 來自定義,也可以通過 move-class attribute 手動設置
<template>
<div>
<button @click="shuffle">Shuffle</button>
<transition-group class="wraps" name="mmm" tag="ul">
<li class="cell" v-for="item in items" :key="item.id">{{ item.number }}</li>
</transition-group>
</div>
</template>
<script setup lang='ts'>
import _ from 'lodash'
import { ref } from 'vue'
let items = ref(Array.apply(null, { length: 81 } as number[]).map((_, index) => {
return {
id: index,
number: (index % 9) + 1
}
}))
const shuffle = () => {
items.value = _.shuffle(items.value)
}
</script>
<style scoped lang="less">
.wraps {
display: flex;
flex-wrap: wrap;
width: calc(25px * 10 + 9px);
.cell {
width: 25px;
height: 25px;
border: 1px solid #ccc;
list-style-type: none;
display: flex;
justify-content: center;
align-items: center;
}
}
.mmm-move {
transition: transform 0.8s ease;
}
</style>
transition-group狀態過渡
Vue 也同樣可以給數字 Svg 背景顏色等添加過度動畫 ,下面演示數字變化
<template>
<div>
<input step="20" v-model="num.current" type="number" />
<div>{{ num.tweenedNumber.toFixed(0) }}</div>
</div>
</template>
<script setup lang='ts'>
import { reactive, watch } from 'vue'
import gsap from 'gsap'
const num = reactive({
tweenedNumber: 0,
current:0
})
watch(()=>num.current, (newVal) => {
gsap.to(num, {
duration: 1,
tweenedNumber: newVal
})
})
</script>
<style>
</style>
第十二章、組件傳參
Provide / Inject
通常,當我們需要從父組件向子組件傳遞數據時,我們使用 props。想象一下這樣的結構:有一些深度嵌套的組件,而深層的子組件只需要父組件的部分內容。在這種情況下,如果仍然將 prop 沿著組件鏈逐級傳遞下去,可能會很麻煩。
provide 可以在祖先組件中指定我們想要提供給后代組件的數據或方法,而在任何后代組件中,我們都可以使用 inject 來接收 provide 提供的數據或方法。
父組件傳遞數據
<template>
<div class="App">
<button>我是App</button>
<A></A>
</div>
</template>
<script setup lang='ts'>
import { provide, ref } from 'vue'
import A from './components/A.vue'
let flag = ref<number>(1)
provide('flag', flag)
</script>
<style>
.App {
background: blue;
color: #fff;
}
</style>
子組件接受
<template>
<div style="background-color: green;">
我是B
<button @click="change">change falg</button>
<div>{{ flag }}</div>
</div>
</template>
<script setup lang='ts'>
import { inject, Ref, ref } from 'vue'
const flag = inject<Ref<number>>('flag', ref(1))
const change = () => {
flag.value = 2
}
</script>
<style>
</style>
兄弟組件傳參和Bus
借助父組件傳參
例如父組件為App 子組件為A 和 B他兩個是同級的
A 組件派發事件通過App.vue 接受A組件派發的事件然后在Props 傳給B組件 也是可以實現的
缺點就是比較麻煩 ,無法直接通信,只能充當橋梁
<template>
<div>
<A @on-click="getFalg"></A>
<B :flag="Flag"></B>
</div>
</template>
<script setup lang='ts'>
import A from './components/A.vue'
import B from './components/B.vue'
import { ref } from 'vue'
let Flag = ref<boolean>(false)
const getFalg = (flag: boolean) => {
Flag.value = flag;
}
</script>
<style>
</style>
Event Bus(方法)
我們在Vue2 可以使用$emit 傳遞 $on監聽 emit傳遞過來的事件
這個原理其實是運用了JS設計模式之發布訂閱模式
type BusClass<T> = {
emit: (name: T) => void
on: (name: T, callback: Function) => void
}
type BusParams = string | number | symbol
type List = {
[key: BusParams]: Array<Function>
}
class Bus<T extends BusParams> implements BusClass<T> {
list: List
constructor() {
this.list = {}
}
emit(name: T, ...args: Array<any>) {
let eventName: Array<Function> = this.list[name]
eventName.forEach(ev => {
ev.apply(this, args)
})
}
on(name: T, callback: Function) {
let fn: Array<Function> = this.list[name] || [];
fn.push(callback)
this.list[name] = fn
}
}
export default new Bus<number>()
然后掛載到Vue config 全局就可以使用
Mitt
在vue3中$on,$off 和 $once 實例方法已被移除,組件實例不再實現事件觸發接口,因此熟悉的EventBus便無法使用了。然而我們習慣了使用EventBus,對于這種情況我們可以使用Mitt庫
npm install mitt -S
main.ts 初始化
全局總線,vue 入口文件 main.js 中掛載全局屬性
import { createApp } from 'vue'
import App from './App.vue'
import mitt from 'mitt'
const Mit = mitt()
//TypeScript注冊
// 由于必須要拓展ComponentCustomProperties類型才能獲得類型提示
declare module "vue" {
export interface ComponentCustomProperties {
$Bus: typeof Mit
}
}
const app = createApp(App)
//Vue3掛載全局API
app.config.globalProperties.$Bus = Mit
app.mount('#app')
使用方法通過emit派發, on 方法添加事件,off 方法移除,clear 清空所有
A組件派發(emit)
<template>
<div>
<h1>我是A</h1>
<button @click="emit1">emit1</button>
<button @click="emit2">emit2</button>
</div>
</template>
<script setup lang='ts'>
import { getCurrentInstance } from 'vue'
const instance = getCurrentInstance();
const emit1 = () => {
instance?.proxy?.$Bus.emit('on-num', 100)
}
const emit2 = () => {
instance?.proxy?.$Bus.emit('*****', 500)
}
</script>
<style>
</style>
B組件監聽(on)
<template>
<div>
<h1>我是B</h1>
</div>
</template>
<script setup lang='ts'>
import { getCurrentInstance } from 'vue'
const instance = getCurrentInstance()
instance?.proxy?.$Bus.on('on-num', (num) => {
console.log(num,'===========>B')
})
</script>
<style>
</style>
監聽所有事件( on("*") )
instance?.proxy?.$Bus.on('*',(type,num)=>{
console.log(type,num,'===========>B')
})
移除監聽事件(off)
const Fn = (num: any) => {
console.log(num, '===========>B')
}
instance?.proxy?.$Bus.on('on-num',Fn)//listen
instance?.proxy?.$Bus.off('on-num',Fn)//unListen
清空所有監聽(clear)
instance?.proxy?.$Bus.all.clear()
Pinia
Store 是一個保存狀態和業務邏輯的實體,可以自由讀取和寫入,并通過導入后在 setup 中使用Getters 作用與 Vuex 中的 Getters 相同,但使用略有差異,其直接在 Store 上讀取,形似 Store.xx,就和一般的屬性讀取一樣Pinia 沒有 Mutations,統一在 actions 中操作 state,通過this.xx 訪問相應狀態,雖然可以直接操作 Store,但還是推薦在 actions 中操作,保證狀態不被意外改變
總結
以上是生活随笔為你收集整理的TypeScript+Vue3的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【记录】IDA|Ollydbg|两种软件
- 下一篇: 【MOOC】华中科技大学操作系统慕课答案