首页 > 其他分享 >TypeScript+Vue3

TypeScript+Vue3

时间:2023-07-07 20:44:26浏览次数:49  
标签:TypeScript const string value let Vue3 组件 name

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成为了一种新的原生类型,就像numberstring一样。

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 逻辑分明 可维护性 高

img

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-class
  • enter-active-class
  • enter-to-class
  • leave-from-class
  • leave-active-class
  • leave-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"//对应enter-active
  @after-enter="afterEnter"//对应enter-to
  @enter-cancelled="enterCancelled"//显示过度打断
  @before-leave="beforeLeave"//对应leave-from
  @leave="leave"//对应enter-active
  @after-leave="afterLeave"//对应leave-to
  @leave-cancelled="leaveCancelled"//离开过度打断

当只用 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 提供的数据或方法。img

父组件传递数据

<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,const,string,value,let,Vue3,组件,name
From: https://www.cnblogs.com/ggonekim/p/17536012.html

相关文章

  • vue3.0 外部配置文件
    vue3.0外部配置文件,重点是打包后也可以修改配置参数 一、在public中创建static文件夹,在static文件夹中创建config.js文件config.js{"webSocketUrl":"http://192.168.1.5:5011/httpCli"}二、在man.js中使用 axios来读取存在app.config.globalProperties.$......
  • vue3使用表格el-table-infinite-scroll.js:18 Uncaught (in promise) Error: [el-tabl
    先看下表格里面有没有这个el-scrollbar__wrapclass类 没有的话升级一下element-plus到最新的就行你可以先查看element-plus的版本npmview element-plus下载完之后 就有了......
  • 13:vue3 插槽 slot
    1、插槽基础知识用Parent.vue,导入SlotBase.vue组件1<template>2<SlotBase>3<div>4<h3>插槽标题</h3>5<p>插槽内容</p>6</div>7</SlotBase>8</template>......
  • Vue3主要的响应类方法(ref、reactive、toRef、toRefs)
    1.Vue.ref()1.1.可以为单个属性添加响应式状态。1.2.可以为对象添加响应式状态,当给对象的某个属性单独ref后,改变后的属性值不会同时改变原始值。1.3.访问数据值,需要添加.value。1.4.原始数据的拷贝。(原始数据不被改变)2.Vue.reactive()2.1.给对象添加响应式状态2.2.访问数据值......
  • vue3--setUp以及ref的使用
    <template><h1>计数统计:{{countRef}}</h1><h2><button@click="inCreate">数量加1</button></h2><h2><button@click="inDelete">数量-1</button></h2></template><!--......
  • vue3 ref全家桶(小满zs vue3 笔记六)
    tip1:vue3无响应式数据(控制台数据已经变化,但是页面无刷新)<template><div>认识Ref全家桶</div><div>{{message}}</div><button@click="change">改变</button></template><scriptsetuplang="ts">letmessage:s......
  • TypeScript面向对象
    TypeScript面向对象面向对象是程序中一个非常重要的思想。面向对象很简单,简而言之就是程序之中所有的操作都需要通过对象来完成。一切皆对象接口TypeScript中的接口跟传统语言(比如Java)的接口有点差别对象可以隐式实现接口概念描述一个类型一个接口里面可以有:字段方法......
  • TypeScript基本介绍与开发环境
    TypeScript基本介绍与开发环境TypeScript官网TypeScript简介TypeScript是JavaScript的超集它对JS进行了扩展,向JS中引入了类型的概念,并添加了许多新的特性TS代码需要通过编译器编译为JS,然后再交由JS解析器执行TS完全兼容JS,换言之,任何的JS代码都可以直接当成JS使用相较于J......
  • vue3 安装 3d-force-graph
    1.首先创建vue3的项目2.创建好后通过开发工具打开项目并打开命令行,输入指令npminstall 3d-force-graph安装即可3.在使用的页面中引入 3d-force-graph<!--官网的basic案例--><template><!--ref用于在组件中引用当前的DOM元素。--><divref="container"></div><......
  • vue3 虚拟dom与diff算法
    diff算法vue3diff算法原码地址:  https://github.com/vuejs/core1.diff算法主要是说renderer.ts中patchChildren这段代码逻辑,如下:  2.diff算法排序分为无key时diff算法排序逻辑和有key时diff算法排序逻辑2.1无key时diff算法排序逻辑,分为三步如下,如图1中无key......