vue3+ts笔记

创建项目:

npm create vue@latest

npm install

页面渲染原理:

  • vite项目中,index.html是项目的入口文件,在项目最外层。
  • 加载index.html后,vite解析 <script type="module" src="xxx"> 指向js。
  • vue3中是通过createApp函数创建一个应用实例。

选项 API 和组合 API:

vue3中开始弱化this;

setup将数据和方法交出去,模板中才可以使用,

setup的返回值也可以是一个渲染函数,

setup中不能使用this.,

setup和data,methods可以并存,一般不同时写,

但是setup读不到data中的数据,data可以读到setup的数据。

语法糖:

<script lang= "ts" setup>let a = '1'
</script>

(如果想修改组件名可以下载一个插件npm i vite-plugin-vue-setup-extend -D )

定义响应式数据:

<script lang= "ts" setup>
import {ref,reactive,toRefs,toRef} from 'vue'
// 使用【ref】可以定义基本类型和对象类型的响应式数据 
// 获取及赋值时记得用.value 也可以使用volar插件自动添加.value
let a = ref('1') 
// 使用【reactive】只能定义对象类型的响应式数据 
// 1,不过它无法替换整个对象 修改数据时如果重新分配了一个新的对象 则这个数据不再是响应式数据了  除非使用Object.assign(obj,{name:'姓名2',age:"182"})  如果是使用ref定义的对象类型数据就可以重新分配一个新的对象 因为有.value
// 2,reactive定义的对象类型的数据不适合解构  如果要解构 要使用【toRefs】包裹对象
let obj = reactive({name:'姓名',age:"18"}) 
// 解构
let {name,age} = toRefs(obj)
let newname = toRef(obj,'name')
// 方法
function changeName(){obj.name = '123' //  使用reactivename.value= '123' // 使用reactive和toRefsnewname.value = '456' // 使用reactive和toRef
}
</script>

使用规则:

若需要一个基本类型的响应式数据,必须使用【ref】

若需要一个响应式对象,层级不深,使用ref reactive都可以

若需要一个响应式对象,且层级较深,推荐使用reactive

响应式的底层原理还是getter 和 setter,可以将 ref 视为:

const myRef = {_value: 0,get value() {track()return this._value},set value(newValue) {this._value = newValuetrigger()}
}

computed计算属性:

// 可读可写
<script setup>
import { ref, computed } from 'vue'const firstName = ref('John')
const lastName = ref('Doe')const fullName = computed({// getterget() {return firstName.value + ' ' + lastName.value},// setterset(newValue) {// Note: we are using destructuring assignment syntax here.[firstName.value, lastName.value] = newValue.split(' ')}
})
</script>

watch:

可以监听ref定义的基本类型的数据和对象类型的数据

可以监听reactive定义的对象数据类型的数据,且默认是开启深度监视并且无法 关闭的。

可以监听ref定义或者reactive定义的对象数据类型的数据中的某个属性,若该属性值不是对象类型,需要写成函数形式,若该属性值依然是对象类型,可直接编辑,也可写成函数形式(()=>obj.name)。

<script setup>
import { ref, watch } from 'vue'const sum = ref(0)
const obj = ref({name:'123',age:18})
watch(sum,(newValue,oldValue)=>{console.log(newValue,oldValue)
})
watch(obj,(newValue,oldValue)=>{console.log(newValue,oldValue)
},{deep:true,immediate:true})watch(()=>obj.name,(newValue,oldValue)=>{console.log(newValue,oldValue)
},{deep:true,immediate:true})</script>

watchEffect:

立即运行一个函数,同时响应式地追踪其依赖,并在依赖更新时重新执行该函数,

与watch相比,watchEffect不用明确指出监视的数据(自动监听函数中用到的属性),

<script setup>
import { ref, watchEffect } from 'vue'const height = ref(0)
const width = ref(0)watchEffect(()=>{if(height.value>20 || width.value>30){console.log(1234)}
})</script>

defineExpose:

子组件抛出数据给父组件,

ts:

// 定义一个接口 用于限制person对象的具体属性
export interface PersonInter{id:string,name:string,age:number,x?:number // 可有可无的字段
}// 一个自定义类型
export type Persons = Array<PersonInter>
<script lang="ts" setup name="name">
import {reactive} from 'vue'
import { type PersonInter ,type Persons} from '@/types'let person:PersonInter = {id:'123',name:'张三',age:18}
let personList:Persons = [{id:'123',name:'张三',age:18}]// 给响应式数据加泛型
let personList2 = reactive<Persons>([{id:'123',name:'张三',age:18}])
</script>

生命周期:

<script setup>
import { onBeforeMounted,onMounted,onBeforeUpdate,onUpdate } from 'vue'
// setup就是创建了
onBeforeMounted(() => {console.log('挂载前')
})
onMounted(() => {console.log('挂载完毕*')
})
onBeforeUpdate(() => {console.log('更新前')
})    
onUpdate(() => {console.log('更新完毕*')
})  
onBeforeUnmounted(() => {console.log('卸载前*')
})
onUnmounted(() => {console.log('卸载完毕')
})  
</script>

hooks: 模块化

建一个hooks文件,文件里可以建多个use开头的ts文件,

每个hooks里都有自己的数据,方法,钩子函数等,不同的业务数据可以建不同的hooks文件,这样就实现了数据分离,

例如,建一个useDog.ts文件,

import { reactive,onMounted} from 'vue'export default function(){// 数据let dogList = reactive(['img 地址'])// 方法async function getDog(){// 请求数据dogList.push()}// 钩子onMounted(()=>{})// 向外界提供数据return {dogList,getDog}
}

组件引入hooks:

<script lang="ts" setup>
import useDog from '@/hooks/useDog'const {dogList,getDog} = useDog()</script>

路由:

新建router文件,写好router抛出来,在main.ts中使用路由,同时要在App.vue中使用路由导航来占位内容。

分为history和hash模式

pinia:集中式(数据)状态管理

多个组件共享数据,

安装: npm install pinia

引入:

import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'const pinia = createPinia()
const app = createApp(App)app.use(pinia)
app.mount('#app')

定义:

在store文件夹里新建一个业务名称的ts的文件

例如:用选项式写count.ts

import { defineStore } from 'pinia'export const useCountStore = defineStore('count', {// 放置方法,用于响应组件中的actionsactions:{increment(value:number){// this是当前的storethis.sum + = value}},// 真正存储数据的地方state(){return {sum:6}},getters:{bigSum(state){// return state.sum*10 或者return this.sum*10}}
})

在页面中使用:

<template><div>{{sum}}</div><button @click="add">+</button>
</template>
<script lang="ts" setup>
import {useCountStore} from '@/store/count'
import { storeToRefs } from 'pinia'// 使用
const countStore = useCountStore()
// *解构 使用storeToRefs 会把countStore中的数据转为ref数据(响应式数据)
const { sum } = storeToRefs(countStore)
// 订阅这个属性的变化
sum.$subscribe((mutate,state)=>{console.log(state.sum)
})function add(){countStore.increment(1)
}
</script>

*改为用组合式写count.ts

  • ref()成为state属性
  • computed()成为getters
  • function()成为actions
import { ref,computed } from 'vue'
import { defineStore } from 'pinia'export const useCountStore = defineStore('count', ()=>{const sum = ref(0)const doubleCount = computed(() => sum.value * 2)function increment(value:number){sum.value + = value}return {sum,doubleCount,increment}
})

defineProps:父子组件传参

父组件:

<template><div></div><Child :list="list" :house="house" @send="onSend"></Child>
</template><script setup lang="ts">import {ref} from 'vue'let childdata = ref('')let house = ref('别墅')function onSend(value:string){childdata.value = value}
</script>

子组件:list是父组件传过来的数据

<template><div>{{name}}{{list}}{{house}}</div><button @click="send(name)"></button>
</template><script setup lang="ts">
import {ref} from 'vue'
interface Props {foo: stringbar?: number
}
interface HouseProps {house?:string
}
let name = ref('sansan')
const props = defineProps(['list','send']) // 不限制类型
const props = defineProps<{list:Props}>() // 接收list+限制类型
const props = withDefaults(defineProps<{list?:Props}>(),{list:()=>[]} )// 接收list+限制类型+限制必要性+指定默认值
const props = defineProps<HouseProps>()  // 接收house+限制类型</script>

自定义事件:子传父

父组件:

<template><div></div><Child @send-data="onSend"></Child>
</template><script setup lang="ts">import Child from './child.vue'import {ref} from 'vue'let childdata = ref('')// 取到传过来的数据function onSend(value:string){childdata.value = value}
</script>

子组件:

<template><div>{{name}}</div><button @click="emit('send-data',name)"></button>
</template><script setup lang="ts">import {ref} from 'vue'let name = ref('sansan')const emit = defineEmits(['send-data'])// 使用emit('send-data',value)就可以给父级传参数和事件了
</script>

mitt:不管多么深度或者多么远的组件之间都可以通信;

安装:npm i mitt

在util中引入mitt;

emitter.emit('on-send',name)
emitter.on('on-send',(value:string)=>{// 操作传过来的数据
})
// 销毁组件时解绑emitt的事件
onUnmounted(()=>{emitter.off('on-send')
})

v-model

v-model用在html标签上:

v-model 的本质是input框的value和@input事件

v-model用在组件标签上:

$event到底是什么,

对于原生事件,$event就是事件对象,这时候能用.target,

对于自定义事件,$event就是触发事件时,所传递的数据,不能.target,

举个例子:

父组件:

<child v-model="name"></child>
// 优化
<child v-model:qwe="name"></child>
<child :qwe="name"></child>
// 本质是
<child :modelValue="name" @update:modelValue="name=$event"></child>

子组件:

<template><input :value="modelValue" @input="emit('update:modelValue',$event.target.value)"><input :value="qwe" @input="emit('update:qwe',$event.target.value)">
</template><script setup lang="ts">defineProps(['modelValue','qwe')const emit = defineEmits(['update:modelValue','update:qwe'])
</script>

爷孙组件传参:

v-bind="$attrs" v-on="$listeners"

组件通信:$refs 和

$parent

$refs值为对象,能取到所有被ref属性标识的DOM元素或组件实例,用于父级获取子集数据并且可修改子集数据。

$parent值为对象,能取到当前组件的父组件实例对象,用于子级获取父级数据并修改父级数据。

子级:

<template><div>{{name}}</div><button @click="changeParent($parent)">修改父级的数据</button>
</template><script setup lang="ts">import {ref} from 'vue'let name = ref('sansan')// 改动父组件的数据function changeParent(parent:any){// 因为parent是响应式对象了,当访问parent.parentData的时候,底层会自动读取value属性,所以里面的parentData不用再.value了parent.parentData = 10}// 使用defineExpose把数据交给外部defineExpose({name})  
</script>

父级:

<template><Child ref="child"></Child><button @click="changeName($refs)">修改Child组件的数据</button>
</template><script setup lang="ts">import Child from './child.vue'import {ref} from 'vue'let child = ref()let parentData = ref(4) // 父级数据// 改动子组件的数据function changeName(refs:object){// child.value.name="xxx"// 因为refs是响应式对象了,所以里面的name不用再.value了refs.child.name="xxx"}// 使用defineExpose把数据交给外部defineExpose({parentData})  
</script>

祖孙之间通信:provide 和inject

爷级:

<template><Child ></Child>
</template><script setup lang="ts">import Child from './child.vue'import {ref,provide} from 'vue'const location = ref('North Pole')function updateLocation() {location.value = 'South Pole'}// 向后代提供数据 所有后代都能收到provide('location',{location,updateLocation})</script>

孙级:

<template><button click="updateLocation">{{location }}</button>
</template><script setup lang="ts">import {inject} from 'vue'// 注入上层组件提供的数据const { location, updateLocation } = inject('location')
</script>

插槽

默认插槽,

具名插槽,

作用域插槽:数据在孩子那边,但根据数据生成的结构,由父亲决定,

父级:

<template>// <Child /><Child>// *默认插槽<div>{{name}}</div>// *默认插槽语法糖<div #default>{{name}}</div>// *具名插槽<template v-slot:s2><div>{{name}}</div></template><template v-slot:s1><h2>标题</h2></template><template #s1><h2>标题</h2></template> // 具名插槽语法糖// *作用域插槽 控制子组件的数据展示的结构<template v-slot="params"><div>{{params.game}} {{params.x1}}</div></template>// 作用域插槽结合具名插槽<template v-slot:slotname="params"><div>{{params.game}} {{params.x1}}</div></template></Child>
</template><script setup lang="ts">import Child from './child.vue'import {ref} from 'vue'let name= ref('里面是默认插槽的内容')
</script>

子级:

<template><div>...<slot>默认内容</slot> // 插槽占位<slot name="s1">s1内容</slot>   // *具名插槽<slot name="s2">s2内容</slot>   // *具名插槽<slot :game="game" :x1="x1"></slot>   // *作用域插槽<slot name="slotname" :game="game" :x1="x1"></slot>   // *作用域插槽也可以具名</div>
</template>

其他api:

shallowRef() 和shallowReactive() 绕开深度响应,只作用与顶层属性,避免了对每一个内部属性做响应式所带来的性能成本,提高性能。

readonly:用于创建一个对象的深只读副本,对象的所有嵌套属性都将变为只读,

shallowReadonly:只作用于对象的顶层属性,

toRaw:用于获取一个响应式对象的原始对象,toRaw返回的对象不再是响应式,不会触发试图更新,何时使用:在需要将响应式对象传递给非vue的库或外部系统时,使用toRaw可以确保得到的是普通对象。

markRaw:标记一个对象,使其永远不会变成响应式的。

customRef:创建一个自定义ref,并对其依赖的

回调函数有两个参数,track(跟踪),trigger(触发),

track()在get中调用,告诉vue数据msg很重要 你要对数据msg进行持续关注,

trigger()在set中调用,通知vue一下数据msg变化了

teleport :

小知识:样式:filter:saturate(200%) 就可以修改内容的颜色,可以整体置灰,

Vue 2和Vue 3 的区别(非兼容性改变):

全局API应用实例:

2.x 全局 API3.x 实例 API ( app)
Vue.config应用程序配置
Vue.config.生产提示移除(见下方)
Vue.config.ignoredElementsapp.config.compilerOptions.isCustomElement(见底部)
Vue.组件应用程序组件
Vue.指令应用程序指令
Vue.mixin应用程序.mixin
Vue.useapp.use(见下方)
Vue.原型app.config.globalProperties(见底部)
Vue.扩展移除(见下方)

项目搭建:

1,环境准备:

node > v16.

pnpm

2,初始化项目:

使用vite进行构建,

安装pnpm:npm i pnpm -g

项目初始化命令:pnpm create vite

进入项目根目录安装依赖: pnpm install

运行:pnpm run dev

引入图标:

<template>
<!-- <use xlink:href="#icon-home" fill="red"></use> -->
<!-- 封装成组件 -->
<use :xlink:href="name" :fill="color"></use>
</template>
<script setup lang="ts">// 接受父组件传递过来的参数defineProps({name:String,color:String})
</script>

api封装,用户信息存储,router,

TS

ts是js的超集,js是一种弱类型动态语言

类型声明:

// 类型断言 用来告诉解析器 变量的实际类型
s=a as string 
// 复合类型 
let b:number | boolean 
// 表示空 没有返回值
function fn():void{
}
// 永远不会返回结果
function fn2():never{
}
// 枚举
enum Gender = {male=0,female=1,name,age
}
let i:Gender
// 类型的别名 声明一个某类型的字段
type myType = 1|2|3
let k:myType
// 数组
let arr:number[] = [1,2,3]
let arr:Array<string> = ['a','s','d']
// 元组
let t1:[number,string,number?] = [1,'a']

面向对象

类:对象的模型,程序中可以根据类创建指定类型的对象,

class 类名{属性名:类型;
}// 例子:
class Person{// 实例属性name:string='124'// 静态属性 可以不创建实例直接访问类static age:number=18// 只读属性readonly id:string='1111'// 可以在任意位置访问(修改)public _name:string// 私有属性 只能在类内部进行访问  可以通过在类中添加方法使得私有属性可以被外部访问private _name:string// 受包含的属性 只能在当前类和当前类的子类中使用protected _name:string// 定义方法sayHollow(){ }// 构造函数 会在对象创建时调用constructor(name:string,age:number){// 给属性赋值console.log(this,'this表示当前的实例,可以通过this向新建的对象中添加属性')this.name= namethis.age = age}
}
// 创建实例对象
const per = new Person()
const per = new Person(name:'shanshan',age:12)
console.log(per.name)
console.log(Person.age)
per.sayHollow() // 调用方法

继承:

子类将会拥有父类所有的方法和属性,可以将多个类中共有的代码写在一个父类中,

如果想在子类中加一些自己的方法也可以直接加

class Cat extends Person{// 子类自己的方法run(){}// 子类也可以重写从父类继承过来的方法sayHollow(){}// 使用supermyMethod(){// super就表示当前类的父类super.sayHollow()}// 如果在子类中也写了构造函数,必须要再掉一次父类的构造函数constructor(name:string,age:number){super(name)this.age = age}}

抽象类:专门用来被子类继承 被子类继承时 子类不能创建实例对象 constructor

可以添加抽象方法,

abstract class Animal{name:stringconstructor(name:string,age:number){this.name= name}// 定义抽象方法 子类必须对抽象方法进行重写abstract sayHollow():void
}class Cat extends Animal{sayHollow(){// 必须要实现}
}let cat = new Cat(name:'shanshan')
cat.sayHollow()

接口:

// 用来定义一个类结构
// 接口可以在定义类的时候去限制类的结构
// 接口中的所有属性都不能有实际的值
// 接口只定义对象的结构 不考虑实际值
// 在接口中所有的方法都是抽象方法
interface myInter{name:string;sayHellow():void;
}
const obj:myInter={name:'sansan',
}
// 定义类时,可以使类去实现一个接口,实现接口就是使类满足接口的要求
class MyClass implements myInter {name:string;constructor(name:string){this.name=name}sayHellow(){console.log(1)}
}

泛型:

// 在定义函数或是类的时候,如果遇到类行不明确就可以使用泛型
function fn<T>(a:T):T{return a
}
// 使用
fn(a:10) // 不指定泛型
fn<number>(a:1) // 指定泛型
fn<string>(a:'123') // 指定泛型
// 声明接口
interface Inter {b:number
}
// T extends Inter表示泛型T必须是Inter 实现类(子类)
function fn3<T extends Inter>(a:T):number{return a.b
}
class Myclass<T>{name:T
}
const mc = new Myclass<string>(name:'shanshan')