环境准备
npm init vue@latest 基于vite
Vue2和Vue3的区别
vue2 选项式api
缺点:功能分散;项目大不易于维护和阅读
vue3 组合式api
优点:功能组合在一起
Vue2 0bject.defineProperty
Vue3 Vue基础语法+Vue-router+vuex(pinia)+组件库
语法不同(组合式api)、体积更小、按需引入、proxy实现响应式diff优化,vue3与ts配合等
入口函数
setup
执行时机:beforeCreate
内部没有this
定义的数据和方法在模板中通过使用return{}
script setup
在vue3.2使用
语法:在script标签上添加setup
不用return 和 export default
<script setup>
import { ref } from 'vue'
// 组合式api
// 创建响应式数据age,初始值是10
const age = ref(10)
const sname = ref('zs')
// 修改年龄的方法
const increase = () => {
age.value++
}
</script>
ref函数
ref函数创建响应式数据,返回值是一个对象
模版中使用ref数据,省略.value,js代码中不能省略
使用步骤:
1.从vue中导入ref
2.在 setup 函数中,使用 ref 函数,传入数据,返回一个响应式数据
3.使用 ref 创建的数据,js 中需要 .value来取值 ,template模板 中可省略
<script setup>
import { ref } from 'vue'
// 组合式api
// 创建响应式数据age,初始值是10
const age = ref(10) // age是对象
console.log(age);
const sname = ref('zs')
const obj =ref({
name: '北京',
desc: '政治文化中心'
})
// 修改年龄的方法
const increase = () => {
age.value++ // ref数据通过.value获取其值
}
// 修改城市的方法
const change = () => {
obj.value.name = '上海'
}
</script>
<template>
<div>Hello Vue3</div>
<p>年龄 {{ age }}</p>
<p>姓名 {{ sname }}</p>
<p>城市 {{ obj.name }}</p>
<p @click="increase">年龄+1</p>
<button @click="change">修改城市</button>
</template>
<style scoped></style>
reactive函数
reactive函数创建响应式数据,只支持引用数据类型
作用:定义复杂数据类型的响应式数据,不能定义简单数据类型
使用步骤:
1.从vue中导入 reactive
2.在 setup 函数中,使用 reactive 函数,传入复杂数据类型数据,返回一个响应式数据
3.使用 reactive 创建的数据,js 和 template 模板中都可以直接使用
<script setup>
import { reactive } from 'vue'
const user = reactive({
name: 'admin',
pwd: '123456'
})
const changeUserName = () => {
user.name = 'admin666'
}
</script>
<template>
<div>Hello Vue3</div>
<p>姓名 {{ user.name }}</p>
<button @click="changeUserName">修改姓名</button>
</template>
<style scoped></style>
reactive对比ref
相同点 都可以创建响应式数据
不同点 reactive只支持引用数据类型,ref支持基本和引用数据类型
ref通过.value获取数据,reactive不需要.value
ref创建响应式引用数据类型低层依赖reactive
computed函数
作用:计算属性
使用步骤:
1.从vue中导入 computed
2.在 setup 函数中,使用 computed 函数,参数是一个函数,函数返回计算好的数据
3.计算好的数据在 js 中需要.value取值,template 模板中可以直接使用
<script setup>
import { ref, computed } from 'vue'
// 创建一个响应式数据
const num = ref(1)
const doubleNum = computed(()=> {
return num.value*2
})
const goods = ref([
{
id: 1001,
price: 5000,
name: '小米手机'
},
{
id: 1002,
price: 4000,
name: '魅族手机'
},
{
id: 1003,
price: 6000,
name: '三星手机'
}
])
// 筛选出价格大等于5000的商品
const filterGoods = computed(()=> {
return goods.value.filter(item => item.price >= 5000)
})
</script>
<template>
<div>Hello Vue3</div>
<p>{{ num }} - {{ doubleNum }}</p>
<p>{{ filterGoods }}</p>
</template>
watch函数
作用:监听数据的变化
使用步骤:
1.从vue中导入 watch
2.在 setup 函数中,使用 watch 函数,参数有以下几种情况:
(1)侦听一个数据:第一个参数监听的数据 第二个回调函数
(2) 侦听多个数据第一个参数监听的数据构成的数组 第二参数回调函数
(3)立刻调用 immediate:是否立即侦听 默认是false 如果是 true 代表页面一加载 会先执行一 次处理程序
(4) 深度监听:如果是true 代表深度侦听 不仅会侦听地址的变化 , 还会在侦听对象内部的属性变化
<script setup>
import { ref, watch } from 'vue'
// 创建一个响应式数据
const num = ref(1)
const age = ref(10)
const obj = ref({
id: 1,
name: '电视',
price: 3000
})
// 1 侦听一个数据
// 第一个参数监听的数据 第二个回调函数
// watch(num,(newV, oldV)=> {
// console.log(newV, oldV);
// })
// 2 侦听多个数据
// 第一个参数监听的数据构成的数组
// 第二参数回调函数
// watch([num,age],([newNum, newAge],[oldNum, oldAge])=>{
// console.log(newNum, newAge);
// })
// 3 立刻调用 immediate
// watch(num,(newV, oldV)=>{
// console.log('立刻调用')
// console.log(newV, oldV);
// },{
// immediate:true
// })
// 4 深度监听
watch(obj,(newV, oldV)=>{
console.log(newV,oldV);
},{
deep:true
})
const changeObj = () =>{
obj.value.price -= 200
}
</script>
<template>
<div>Hello Vue3</div>
<p>{{ num }} </p>
<button @click="changeObj">修改obj</button>
</template>
<style scoped></style>
计算属性完整写法
语法:
const 自定义计算属性名 = computed: ({
get() { return 结果 },
set(val) {}
}
})
(1)get函数就等同于简单写法的函数 计算属性必须要有 get 而且需要返回结果
(2)set方法第一个参数 可以监听用户输入 新值 与 旧值
注意:
修改计算属性时需要用完整写法
修改时会自动执行get函数
获取数据时会自动化执行get函数
<script setup>
import { ref, computed } from 'vue'
const firstName = ref('')
const lastName = ref('')
// 简写 只提供get
// const fullName = computed(()=>{
// return firstName.value + lastName.value
// })
const fullName = computed({
get() {
return firstName.value + lastName.value
},
set(newV) {
firstName.value = newV.substring(0,1)
lastName.value = newV.substring(1)
}
})
</script>
<template>
<div>Hello Vue3</div>
<input type="text" v-model="firstName">
<input type="text" v-model="lastName">
<input type="text" v-model="fullName">
</template>
<style scoped></style>
watch监听对象具体的属性
默认情况下 watch 只能监听到简单类型的数据变化 如果侦听的是复杂类型 只会侦听地址是否发生变化 不会侦听对象内部属性的变化
watch:{
data属性名:{
deep:true,
immediate: true
handler(newVal,oldVal){
}
}
}
watch监听reactive
侦听的是reactive数据,默认对第一层属性开启deep: true,此时无论有没有传入deep选项
侦听的是ref引用数据 默认deep: false,监控的对象属性发生改变不会被监控到
<template>
<div>
<p>{{ user }}</p>
<button @click="changeAge">修改age</button>
</div>
</template>
<script setup>
import { reactive, watch } from 'vue'
const user = reactive({
name: 'admin',
age: 18,
job: {
jobName: 'web前端工程师',
salary: 6000
}
})
// 侦听的是reactive数据,默认对第一层属性开启deep: true,此时无论有没有传入deep选项
// 侦听的是ref引用数据 默认deep: false,监控的对象属性发生改变不会被监控到
watch(user,(newV, oldV)=>{
console.log(newV);
},{
deep: false // 关闭深度监控无效
})
const changeAge = () => {
// user.age++
// 假如需要侦听深层次数据 需要手动开启deep:true
user.job.salary += 2000
}
</script>
<style lang="scss" scoped>
</style>
reactive丢失响应式
场景
解构赋值会丢失响应式
场景:
1.你定义了一个数据:let data=reactive({
name:"",
age:""
})
2.然后你解构了
let {name}=data; //解构赋值
重新赋值会丢失响应式
场景:
1.你定义了一个数据:let data=reactive({
name:"",
age:""
})
2.然后你请求了接口,赋值给data
let res=await getUserApi(); //请求接口
data=res.data; //将返回的结果赋值给data
解决办法
-
避开直接赋值和结构,reactive直接包裹一个对象。
let data=reactive({ userData:{} //里面定义一个对象,这样赋值就不会丢失响应式了。 }) //获取接口数据 let res=await getUserApi(); //请求接口 data.userData=res.data; //将返回的结果赋值给data
-
简单数据类型使用ref()来进行定义
watchEffect函数
1回调函数立即调用
2 回调函数依赖的的数据都会被监控 深度监控
watch vs watchEffect
1 相同点 都可以对数据进行侦听
2 watch 和 watchEffect 都能监听响应式数据的变化,不同的是它们监听数据变化的方式不同。
(1)watch 会明确显式监听某一个响应数据,而 watchEffect 则是隐式的监听回调函数中响应数据。
(2) watch 在响应数据初始化时是不会执行回调函数的,watchEffect 在响应数据初始化时就会立即执行回调函数。
<template>
<div>
<button @click="num++">num++</button>
</div>
</template>
<script setup>
import { watchEffect, ref, reactive } from 'vue'
// watchEffort监控数据
const num = ref(10)
const obj = reactive({
name: 'zs',
age: 18,
boyFriend: {
name: 'lisi',
age: 19
}
})
watchEffect(()=> {
console.log(num.value);
console.log(obj.boyFriend.name);
})
</script>
<style lang="scss" scoped>
</style>
onInvalidate副作用清理函数
watchEffect接收一个副作用函数
执行时机: 下一次副作用执行前
副作用-发网络请求、开启定时器、控制台输出
onInvalidate清除副作用函数注意点
1.该函数总是在watchEffect执行的时候再次执行
2.当组件被销毁的时候该函数再次执行
3.该函数总是优先于watchEffect中的同步/异步代码执行
4.Promize函数的执行应该在该函数下面
flush:'post'
onInvalidate清除副作用函数的执行时机由flush控制
通过 flush:post可以避免副作用,在DOM更新后运行副作用,确保模板引用与DOM保持同步,并引入正确的元素。
watchPostEffect
watchEffect 的别名,带有 flush: ‘post’ 选项。
watchSyncEffect
watchEffect 的别名,带有 flush: ‘sync’ 选项。
nextTick使用
nextTick
是Vue3中的一个非常有用的函数,它可以在下一次DOM更新循环结束后执行回调函数。这个函数可以用来解决一些异步更新视图的问题,例如在修改数据后立即获取更新后的DOM节点
<template>
<div>
<p>{{ message }}</p>
<button @click="changeMessage">changeMessage</button>
</div>
</template>
<script setup lang="ts">
import { ref, nextTick } from "vue";
const message = ref("Hello, world!");
const changeMessage = () => {
message.value = "Hello, nextTick!";
nextTick(() => {
console.log(document.querySelector("p")!.textContent); // 'Hello, nextTick!'
});
};
</script>
<style scoped></style>
在这个示例中,我们定义了一个名为message的响应式变量,并创建了一个名为changeMessage的函数来修改这个变量的值。在函数中,我们调用了nextTick函数,并传入一个回调函数。这个回调函数将在DOM更新循环结束后执行,并且我们可以在回调函数中获取到更新后的DOM节点。
生命周期
1 选项式api
beforeCreate/created
beforeMount/mounted
beforeUpdate/updated
beforeUnmount/unmounted
2 组合式api
组合式 api生命周期函数可以调用多次
发网络请求可以在onMounted发送或者直接调用
获取dom在onMounted使用
setup->组合式api
onBeforeMount/onMounted
onBeforeUpdate/onUpdated
onBeforeUnmount/onUnmounted
在vue3.0中可以继续使用Vue2.x中的生命周期钩子,但有两个被更名:
beforeDestroy改名为beforeUnmount
destoryed改名为unmounted
vue3.0也提供了Composition形式的生命周期钩子与Vue2.x中钩子对应关系如下:
beforeCreate ====> setup()
created ====> setup()
beforeMount ====> onBeforeMount
mounted ====> onMounted
beforeUpdate====>onBeoforeUpdate
updated ====>onUpdated
beforeUnmount ==>onBeforeUnmounnt
unmounted ===> onUnmounted
onMounted的优先级会比mounted高
父子组件间通信
一、父传子 defineProps
父组件传值给子组件主要是由父组件为子组件通过v-bind绑定数值,而后传给子组件;子组件则通过defineProps接收使用。
如下为父组件Father.vue
<template>
<div class="fa">
<div style="margin: 10px;">我是父组件</div>
<Son :fatherMessage="fatherMessage"></Son>
</div>
</template>
<script setup lang="ts">
import Son from './Son.vue'
import {ref} from "vue";
const fatherMessage = ref<string>("我是父组件传过来的值")
</script>
<style scoped>
.fa{
border: 3px solid cornflowerblue;
width: 400px;
text-align: center;
}
</style>
如下为子组件Son.vue
<template>
<div style="margin: 10px;border: 2px solid red">
我是子组件
<div style="margin: 5px;border: 2px solid gold">
父组件传值接收区:{{fatherMessage}}
</div>
</div>
</template>
<script setup lang="ts">
interface Props {
fatherMessage?: string,
}
defineProps<Props>()
</script>
父组件Father.vue
中在调用Son.vue
这个子组件时,使用v-bind
绑定参数fatherMessage
,并传给Son.vue
子组件Son.vue
使用defineProps
接收fatherMessage
这个参数,而后就可以正常使用该参数。
二、子传父 defineEmits
子组件传值给父组件主要是子组件通过defineEmits
注册一个自定义事件,而后触发emit
去调用该自定义事件,并传递参数给父组件。
在父组件中调用子组件时,通过v-on
绑定一个函数,通过该函数获取传过来的值。
如下为子组件Son.vue
<template>
<div style="margin: 10px;border: 2px solid red">
我是子组件
<button @click="transValue" style="margin: 5px">传值给父组件</button>
</div>
</template>
<script setup lang="ts">
import {ref} from "vue";
// 定义所要传给父组件的值
const value = ref<String>("我是子组件传给父组件的值")
// 使用defineEmits注册一个自定义事件
const emit = defineEmits(["getValue"])
// 点击事件触发emit,去调用我们注册的自定义事件getValue,并传递value参数至父组件
const transValue = () => {
emit("getValue", value.value)
}
</script>
如下为父组件Father.vue
<template>
<div class="fa">
<div style="margin: 10px;">我是父组件</div>
父组件接收子组件传的值:{{sonMessage}}
<Son @getValue="getSonValue"></Son>
</div>
</template>
<script setup lang="ts">
import Son from './Son.vue'
import {ref} from "vue";
const sonMessage = ref<string>("")
const getSonValue = (value: string) => {
sonMessage.value = value
}
</script>
<style scoped>
.fa{
border: 3px solid cornflowerblue;
width: 400px;
text-align: center;
}
</style>
使用defineEmits注册一个事件getValue,而后设置点击事件transValue去触发emit,去调用我们注册的自定义事件getValue,并传递value参数至父组件。
父组件Father.vue在获取子组件Son.vue传过来的值时,通过在子组件上使用v-on设置响应函数getValue(该函数与子组件中的注册自定义事件getValue名称需一致),并绑定一个函数getSonValue来获取传过来的值。
三、子组件暴露属性给父组件 defineExpose
当父组件想直接调用父组件的属性或者方法时,子组件可以使用defineExpose
暴露自身的属性或者方法,父组件中使用ref
调用子组件暴露的属性或方法。
如下为子组件Son.vue
<template>
<div style="margin: 10px;border: 2px solid red">
我是子组件
</div>
</template>
<script setup lang="ts">
import {ref, defineExpose} from "vue";
// 暴露给父组件的值
const toFatherValue = ref<string>("我是要暴露给父组件的值")
// 暴露给父组件的方法
const toFatherMethod = () => {
console.log("我是要暴露给父组件的方法")
}
// 暴露方法和属性给父组件
defineExpose({toFatherMethod, toFatherValue})
</script>
如下为父组件Father.vue
<template>
<div class="fa">
<div style="margin: 10px;">我是父组件</div>
<button @click="getSonMethod">获取子组件的方法</button>
<Son ref="sonMethodRef"></Son>
</div>
</template>
<script setup lang="ts">
import Son from './Son.vue'
import {ref} from "vue";
const sonMethodRef = ref()
const getSonMethod = () => {
sonMethodRef.value.toFatherMethod()
console.log(sonMethodRef.value.toFatherValue)
}
</script>
<style scoped>
.fa{
border: 3px solid cornflowerblue;
width: 400px;
text-align: center;
}
</style>
在子组件中定义属性toFatherValue
和方法toFatherMethod
,而后通过defineExpose
暴露出来。
父组件调用时,为子组件绑定一个ref
,并定义一个ref
变量sonMethodRef
,通过调用sonMethodRef
,来获取子组件暴露出来的属性和方法。 【】
四、依赖注入Provide / Inject
一个父组件相对于其所有的后代组件,会作为依赖提供者。任何后代的组件树,无论层级有多深,都可以注入由父组件提供给整条链路的依赖。
如下为父组件Root.vue
<template>
<div>
我是root组件
<Footer></Footer>
</div>
</template>
<script setup lang="ts">
import { provide, ref } from 'vue'
import Footer from './Footer.vue'
const toChildValue= ref<string>("我是给所有子组件的值")
// 将toChildValue注入到所有子组件中
provide(/* 注入名 */ 'toChildValue', /* 值 */ toChildValue)
</script>
如下为子组件Footer.vue
<template>
<div>
我是footer组件
<div>
接收父组件的值:{{getFatherValue}}
</div>
<DeepChild></DeepChild>
</div>
</template>
<script setup lang="ts">
import DeepChild from "./DeepChild.vue"
import {ref,inject,Ref} from "vue";
// 获取父组件提供的值
// 如果没有祖先组件提供 "toChildValue"
// ref("") 会是 "这是默认值"
const getFatherValue = inject<Ref<string>>(/* 注入名 */"toChildValue",/* 默认值 */ ref(""))
</script>
如下为孙子组件DeepChild.vue
<template>
<div>
我是deepChild组件
<div>
接收爷爷组件的值:{{getGrandFatherValue}}
</div>
</div>
</template>
<script setup lang="ts">
import {inject, ref, Ref} from "vue";
// 获取爷爷组件提供的值
// 如果没有爷爷组件提供 "toChildValue"
// value 会是 ""
const getGrandFatherValue = inject<Ref<string>>(/* 注入名 */"toChildValue",/* 默认值 */ ref(""))
</script>
当最顶层的组件Root.vue
传值给所有子组件时,使用provide
进行注入
provide(/* 注入名 */ 'toChildValue', /* 值 */ toChildValue)
而后无论哪个子组件想要获取toChildValue
的值,只需使用inject
即可
inject<Ref<string>>(/* 注入名 */"toChildValue",/* 默认值 */ ref(""))
toRef与toRefs
toRef
toRef
将对象中的属性变成响应式数据,修改响应式数据是会影响到原始数据的
<template>
<div>
<p>{{ name }}-{{ obj.name }}</p>
<p>{{ job }}</p>
<p>{{ city }}</p>
</div>
</template>
<script setup>
import { ref, toRef,reactive} from 'vue'
const obj = reactive({
name: 'zs',
sex:'男',
job: {
jobname: 'h5工程师',
city: 'shanghai'
}
})
const { name } = obj
const name = toRef(obj, 'name') // 返回值是一个ref对象
const city = toRef(obj.job, 'city')
console.log(name.value);
</script>
<style lang="scss" scoped>
</style>
toRefs
使用toRefs(可以批量创建多个ref对象)
<template>
<div>
<p>{{ name }}-{{ obj.name }}</p>
<p>{{ job }}</p>
<p>{{ city }}</p>
</div>
</template>
<script setup>
import { ref, toRefs,reactive} from 'vue'
const obj = reactive({
name: 'zs',
sex:'男',
job: {
jobname: 'h5工程师',
city: 'shanghai'
}
})
const { name, job} = toRefs(obj)
const { city } = toRefs(obj.job)
</script>
<style lang="scss" scoped>
</style>
总结
作用: 创建一个ref对象,其value值指向另一个对象中的某个属性
语法: const name = toRef(person, 'name')
应用: 要将响应式对象中的某个属性单独供应给外部使用时
扩展: toRefs与toRef功能一致,但可以批量创建多个ref对象,
语法:toRefs(person)
shallowReactive与shallowRef
shallowReactive
shallowReactive:只处理对象最外层属性的响应式(浅响应式)
相对于reactive
,shallowReactive
只有最外层的属性是响应的
const state = shallowReactive({
foo: 1,
nested: {
bar: 2
}
})
// 改变 state 本身的性质是响应式的
state.foo++
// ...但是不转换嵌套对象
isReactive(state.nested) // false
state.nested.bar++ // 非响应式
shallowRef
1.shallowRef:只处理基本数据类型的响应式,不进行对象的响应式处理。
2.当使用shallowRef
为一个基础类型数据创建响应性时,行为是和ref
一样的。
3.不同的是,当使用shallowRef
为复杂类型创建响应性时,修改深层属性,不具备响应性
const a = shallowRef({b:1})
a.value.b = 2 //视图不会更新
console.log(a.value) //{b : 2} 但是能追踪到值得变化
a.value={b:2} //一整个替换时,视图会变化
toRow与markRow
toRow
是将一个响应式的对象完全转化为一个普通的对象
普通对象的改变不会引起页面的变化,当我们改变数据不影响页面的时候一般会使用他
<script setup>
import {reactive, readonly, shallowReadonly, toRaw } from 'vue';
let person = reactive({
name: "sunWuKong",
age: 18
})
const personToRow = () => {
person = toRaw(person) // 转化成了普通对象,不再是响应式对象
person.age++ // 页面不会更新
console.log(person);
}
</script>
<template>
<h2>person</h2><span>{{ person }}</span>
<button @click="personToRow">toRow</button>
</template>
markRow
标记一个对象,不会被vue的响应式函数转化为响应式对象
有些值不应该被设置成响应式的,比如第三方的类库
当有一些比较大的列表的时候,跳过响应式的转化会增加性能
<script setup>
import { markRaw, reactive, readonly, shallowReadonly } from 'vue';
let person = reactive({
name: "sunWuKong",
age: 18
})
const personMarkRow = () => {
{
name:"sunWuKong",
age:18,
otherMsg:{
sex:"male",
children:2
}
}
// 标记otherMsg不要转化成响应式对象
person.otherMsg = markRaw({
sex: "male",
children: 0
})
person.otherMsg.children++
console.log(person)
}
</script>
<template>
<h2>person</h2><span>{{ person }}</span>
<button @click="personToRow">toRow</button>
<button @click="personMarkRow">MarkRow</button>
</template>
customRef
ref:普通数据转成响应式数据
customRef : 自定义ref,创建响应式数据
hooks函数
一、hooks 是什么
vue3 中的 hooks 就是函数的一种写法,就是将文件的一些单独功能的 js 代码进行抽离出来进行封装使用。
二、hooks 和 utils 区别
相同点:
通过 hooks 和 utils 函数封装, 可以实现组件间共享和复用,提高代码的可重用性和可维护性。
异同点:
表现形式不同:
hooks 是在 utils 的基础上再包一层组件级别的东西(钩子函数等);utils 一般用于封装相应的逻辑函数,没有组件的东西;
数据是否具有响应式:
hooks 中如果涉及到 ref,reactive,computed 这些 api 的数据,是具有响应式的;而 utils 只是单纯提取公共方法就不具备响应式;
作用范围不同:
hooks 封装,可以将组件的状态和生命周期方法提取出来,并在多个组件之间共享和重用;utils 通常是指一些辅助函数或工具方法,用于实现一些常见的操作或提供特定功能。
总结:
utils 是通用的工具函数,而 hooks 是对 utils 的一种封装,用于在组件中共享状态逻辑和副作用。
通过使用 hooks,您可以简化代码,并使其更具可读性和可维护性。
Vue3中的防抖和节流
customRef 接收一个函数作为参数,这个函数接收两个函数作为参数
track (通知vue需要追踪后续内容的变化)
trigger (通知vue重新解析模板)
防抖函数(debounce):
import { customRef, ref } from 'vue';
const debounceRef = (data, delay = 500) => {
// 创建定时器
let timer = null;
// customRef 中会返回两个函数参数。一个是:track 在获取数据时收集依赖的;一个是:trigger 在修改数据时进行通知派发更新的。
return customRef((track, trigger) => {
return {
get() {
// 收集依赖
track();
// 返回当前数据
return data;
},
set(val) {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
// 修改数据
data = val;
// 派发更新
trigger();
}, delay);
}
}
});
}
const val = debounceRef('', 1000);
节流函数(throttle)
const throttleRef = (data, delay = 500) => {
// 创建定时器
let timer = null;
// customRef 中会返回两个函数参数。一个是:track 在获取数据时收集依赖的;一个是:trigger 在修改数据时进行通知派发更新的。
return customRef((track, trigger) => {
return {
get() {
// 收集依赖
track();
// 返回当前数据
return data;
},
set(val) {
if (timer) {
return;
}
timer = setTimeout(() => {
timer = null;
}, delay);
// 修改数据
data = val;
// 派发更新
trigger();
}
}
});
};
const inputVal = throttleRef('', 1000);
Vue3 全局注册组件
全局注册组件分为三个文件
一、组件本身,在components文件夹下面,需要自己来写
二、main.js文件,用于注册文件,调用 component 第一个参数组件名称 第二个参数组件实例
三、App.vue用于挂载所有的组件
例:我这儿封装一个Card组件想在任何地方去使用
<template>
<div class="card">
<div class="card-header">
<div>标题</div>
<div>副标题</div>
</div>
<div v-if='content' class="card-content">
{{content}}
</div>
</div>
</template>
<script setup lang="ts">
type Props = {
content:string
}
defineProps<Props>()
</script>
<style scoped lang='less'>
@border:#ccc;
.card{
width: 300px;
border: 1px solid @border;
border-radius: 3px;
&:hover{
box-shadow:0 0 10px @border;
}
&-content{
padding: 10px;
}
&-header{
display: flex;
justify-content: space-between;
padding: 10px;
border-bottom: 1px solid @border;
}
}
</style>
在main.js 引入我们的组件跟随在createApp(App) 后面 切记不能放到mount 后面这是一个链式调用用
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')
直接在其他vue页面 立即使用即可 无需引入
<template>
<Card></Card>
</template>
vue3自定义指令
两种作用域
自定义指令可以定义全局的,也可以定义局部的。
在正式开搞之前,小伙伴们需要先明白,自定义指令有两种作用域,一种是局部的自定义指令,还有一种是全局的自定义指令。局部的自定义指令就只能在当前 .vue
文件中使用,全局的则可以在所有的 .vue
文件中使用。
局部指令
直接在当前 .vue 文件中定义即可,如下
<template>
<div>
<button v-onceClick="10000" @click="btnClick">ClickMe</button>
</div>
</template>
<script>
import {ref} from 'vue';
export default {
name: "MyVue01",
setup() {
const a = ref(1);
const btnClick = () => {
a.value++;
}
return {a, btnClick}
},
directives: {
onceClick: {
mounted(el, binding, vnode) {
el.addEventListener('click', () => {
if (!el.disabled) {
el.disabled = true;
setTimeout(() => {
el.disabled = false;
}, binding.value || 1000);
}
});
}
}
}
}
</script>
这里我自定义了一个名叫 onceClick 的指令,给一个 button 按钮加上这个指令之后,可以设置这个 button 按钮在点击多久之后,处于禁用状态,防止用户重复点击。
全局指令
全局指令我们一般写在 main.js 中,或者写一个单独的 js 文件然后在 main.js 中引入,下面的例子1.src/ 新建文件夹 directives,新建index.js
export default (app) => {
//自定义组件
app.directive('demo', (el, binding) => {
el.addEventListener('click', () => {
console.log(binding.value.color) // => "white"
console.log(binding.value.text) // => "hello!"
})
})
}
2.main.js中
import directives from './directives/index.js'
const app = createApp(App)
directives(app)
3.页面使用
<el-button v-demo="{ color: 'white', text: 'hello!' }">自定义指令</el-button>
参数
el:指令所绑定的元素,可以用来直接操作 DOM,我们松哥说想实现一个可以自动判断组件显示还是隐藏的指令,那么就可以通过 el 对象来操作 DOM 节点,进而实现组件的隐藏。
binding:我们通过自定义指令传递的各种参数,主要存在于这个对象中,该对象属性较多,如下属性是我们日常开发使用较多的几个:
name:指令名,不包括 v- 前缀。
value:指令的绑定值,例如:v-hasPermission="['user:delete']" 中,绑定值为 'user:delete',不过需要小伙伴们注意的是,这个绑定值可以是数组也可以是普通对象,关键是看你具体绑定的是什么,在 2.1 小节的案例中,我们的 value 就是一个数字。
expression:字符串形式的指令表达式。例如 v-my-directive=“1 + 1” 中,表达式为 “1 + 1”。
arg:传给指令的参数,可选。例如 v-hasPermission:[name]="'zhangsan'" 中,参数为 “name”。
vue3路由
vue2路由与vue3路由的区别
路由模式不同
vue2 中使用 new Router()
创建 router
vue3 中不再使用 new Router()
创建 router,而是调用 createRouter
方法:
路由的使用
1、路由的安装
npm install vue-router@4
2、路由的模式
createWebHashHistory():Hash模式
它在内部传递的实际URL之前使用了一个哈希字符#,如 https://example.com/#/user/id
由于这部分 URL 从未被发送到服务器,所以它不需要在服务器层面上进行任何特殊处理
createWebHistory():history模式
当使用这种历史模式时,URL会看起来很“正常”,如 https://example.com/user/id,(推荐使用)
3、创建路由模块
- 在项目中的src文件夹中创建一个router文件夹,在其中创建index.js模块
- 采用createRouter()创建路由,并暴露出去
- 在main.js文件中初始化路由模块app.use(router)
在router/index.js文件下
import { createRouter, createWebHistory } from "vue-router";
import HomeView from '@/views/HomeView.vue'
// 创建路由规则
let routes = [
{
path: '/', // URL 地址
name: 'home', // 名称
component: HomeView // 渲染该组件
},
]
// 创建路由
const router = createRouter({
// 使用 history 模式
history: createWebHistory(),
// 路由规则
routes
})
// 将路由对象暴露出去
export default router
在main.js文件下
import { createApp } from 'vue'
import App from './App.vue'
import router from './router' // 引入路由模块
let app = createApp(App)
app.use(router) // 初始化路由插件
app.mount('#app')
pinia
Pinia 是 Vue 的专属状态管理库,它可以跨组件或页面共享状态,类似 Vuex, 是 Vue 的另一种状态管理方案,支持 Vue2 和 Vue3。
1.pinia是什么?
我们都知道Vuex在Vue2中主要充当状态管理的角色,所谓状态管理,简单来说就是一个存储数据的地方,存放在Vuex中的数据在各个组件中都能访问到,它是Vue生态中重要的组成部分。
在Vue3中,可以使用传统的Vuex来实现状态管理,也可以使用最新的pinia来实现状态管理。
2.Pinia 的优势
1 Vue2和Vue3都支持
2 pinia中action支持同步和异步,Vuex不支持
3 良好的Typescript支持
4 体积非常小,只有1KB左右
5 pinia支持插件来扩展自身功能
6 pinia中只有state、getter、action,抛弃了Vuex中的Mutation
7 pinia分模块不需要modules(之前vuex分模块需要modules)
3.安装 Pinia
# 使用 npm
npm install pinia
# 使用 yarn
yarn add pinia
4. pinia的基本使用与概念
4.1准备工作
在main.js,引入pinia提供的createPinia方法,创建根存储。
// main.js
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
//引入pinia中的createPinia方法
import {createPinia} from "pinia"
//创建根储存
const pinia = createPinia();
const app = createApp(App);
app.use(pinia);
app.mount('#app')
4.2 store
store就是数据仓库的意思,我们数据都放在store里面,可以读写操作
//src/store/goods.js
//引入函数
import { defineStore } from "pinia";
// 调用defineStore函数创建Store 实体
export const goodsStore = defineStore('users', {
})
创建store实体的defineStore函数有两个参数值
- name:字符串,必传项,该store的唯一id。
- options:对象,store的配置项,比如state等。
4.3添加state
我们需要存放的数据就放在state属性内
在src/store/goods.js下
//src/store/goods.js
import { defineStore } from "pinia";
export const goodsStore = defineStore('users', {
// state
state: () => {
return {
goods:["商品1","商品2"]
}
},
})
4.4使用store
这时我们store里面的配置项state就存在数据了,我们就能够在App.vue去使用它
//App.vue
<script setup>
import {goodsStore} from "./store/store";
const store = goodsStore()
console.log(store)
</script>
5.getters属性
getter就相当于Vue中的计算属性,它的作用就是返回一个新的结果
在src/store/goods.js
//src/store/goods.js
export const goodsStore = defineStore('users', {
// state
state: () => {
return {
goods:["商品1","商品2"],
list : ["手机","电脑"]
}
},
getters:{
getGoods(state){
return state.goods[0] + state.list[0]
}
}
})
在App.vue中使用getters
//app.vue
<template>
<div>
<ul>
<li v-for="(item,index) in goods" :key="index">
{{ item }}
</li>
//使用使用这样的方式
{{ store.getGoods }}
</ul>
</div>
</template>
getters传参
//src/store/goods.js
export const goodsStore = defineStore('users', {
// state
state: () => {
return {
goods:["商品1","商品2"],
list : ["手机","电脑"]
}
},
getters:{
getGoods(state){
return state.goods[0] + state.list[0]
},
//返回一个函数在调用的时候直接传递参数
getass(state){
return (num)=>{
return num + state.goods[0]
}
}
}
})
//app.js 中调用
{{ store.getass("1:") }}
6.Actions属性
Action 相当于组件中的 method,用来放置一些处理业务逻辑的方法,我们有业务代码的话最好是写在Actions中。action支持同步和异步
在src/store/goods.js
export const goodsStore = defineStore('users', {
// state
state: () => {
return {
goods:["商品1","商品2"],
list : ["手机","电脑"]
}
},
getters:{
getGoods(state){
return state.goods[0] + state.list[0]
},
getass(state){
return (num)=>{
return num + state.goods[0]
}
}
},
actions:{
setList(){
//通过this.xx 访问相应状态
this.list = [
"麻将",
"扑克"
]
}
}
})
在app.vue中
//app.vue
<script setup>
import {goodsStore} from "./store/store";
import { reactive } from "vue";
import {storeToRefs} from 'pinia'
const store = goodsStore()
const {goods} = storeToRefs(store)
store.setList()
</script>
vue2和vue3中vuex的区别
创建 Store不同
vue2: new Vuex.Store
vue3: import { createStore } from 'vuex'
获取 Vuex 的状态和方法
vue 2中,组件中需要使用 $store 方法来获取 Vuex 的状态和方法
vue3是使用 import { useStore } from 'vuex' 在组件中引入 Vuex 的 store 对象,并通过调用 useStore() 来访问 store 的状态和方法
ts
ts的基本介绍
ts的全称type script
TS是JS的超集,JS所拥有的,TS都有。
JS是弱类型语言,天生存在很多缺陷。而TS是强类型语言,有了TS加持,JS应用会变得更加稳定且强大。
浏览器只能识别JS,TS只能通过编译成JS才能被浏览器运行
TS有在编译时有错误提示,便于维护
许多新项目,如果选择VUE3或React进行开发时,大多都会带上TS。
TS是前端领域大势所趋
类型注解
约束变量的类型 语法 :类型
基础类型
变量声明时,加上一个类型
使用变量时,类型必须是前面定义好的
let name: string
let age: number
let isVip: boolean
//变量声明时,加上一个类型
name = '234'
age = 234
isVip = true
//使用变量时,类型必须是前面定义好的
数组类型
数组类型两种写法 :类型[] 或: Array<类型>
// 写法一
let arr: number[] = [234, 15, 1]
let arr2: boolean[] = [true, false]
// 写法二
let arra: Array<string> = ['234', 'sdges']
let arrb: Array<number> = [234, 124, 7456]
let arrc: Array<boolean> = [false, false, true]
元组-数组的类型和长度都限定
let arr: [number,string] = [1,'3']
联合类型
数组存放数字或字符串
let arr5: (number | string)[] = [1,2,'3']
let timer: number | null = null
timer = setTimeout(()=>{},1000)
arr6类型是数字或字符串数组
let arr6: number | string[] = ['j']
类型别名
type 类型别名 = 具体类型 类型别名起名用大驼峰
type CustomType = (number | string)[]
let arr7:CustomType = [1,'2']
函数类型
分别指定参数和返回值的类型
function getSum(a: number,b:number):number {
return a + b
}
const result:number = getSum(8,2)
const getSum = function(a:number,b:number):number {
return a + b
}
const getSum = (a:number,b:number):number => {
return a + b
}
同时指定 (参数类型列表)=>返回值类型 ()=> number
type Fn = (a:number,b:number) => number
const getSum: Fn = (a,b) => {
return a + b
}
const getSum2: Fn = (a,b) => {
return a + b
}
void 无返回值
函数没有返回值(没有写return),可以把返回值类型定义为void或省略
const fn = ():void => {
console.log('hello world');
// return undefined
}
console.log(fn());
可选参数 ?
const fn2 = ( b:number,num?: number) => {
}
fn2(10)
fn2(10,20)
对象类型
描述对象的属性和方法类型
let person: {name: string, age: number, eat():void}
= {name: 'zs',age: 20, eat() {}}
:{
属性名:类型
属性名:类型
方法名(参数类型): 返回值类型 或 方法名: (参数类型)=>返回值类型
}
let person2: {
name: string
age?: number // 对象属性可选
eat:(a:string,b:string)=>void
}
= {name: 'zs', eat(a,b) {}}
person2.eat('h','k')
例:
学生对象 (必选属性) 内容包括 学号、 姓名、 性别、 成绩
体重 (可选属性)
方法 包括 学习、 打游戏(打游戏为可选属性)
let student: {
sid: string
name: string
sex: string
score: number
weight?: number
study():void
playGame?: () => void
} = {
sid: '20240918',
name: 'zs',
sex: 'nan',
score: 90,
study: ()=>{}
}
接口interface 描述对象类型
作用:用于类型封装复用
语法:1.定义关键字 interface
2.关键字后面 跟 接口名称,接口名称建议I开头
3. 接口名称 定义 对象的具体类型
interface Person {
eat():void
sleep():void
}
interface Person {
drink: ()=> void
}
interface可以继承
extends关键字
interface Student extends Person {
name: string
age: number
study: ()=> void
}
type交叉类型
type Student = {
name: string
age: number
study: ()=> void
} & Person
interface 与 type
1 很多时候,可以自由切换
2 interface支持对象类型,可以继承
3 type可以支持任何类型,通过交叉类型&实现复用
4 type不能重复定义,interface可以重复定义会合并
字面量类型
所有的字面量数据都可以作为类型
// let a:1 = 1
// const a = 1 => const a:1 = 1
let a =1 // let a:number = 1
a = 2
字面量配合联合类型,表示一组可选值
let degree: '专科' | '本科' | '研究生' = '专科'
let direction: 'UP' | 'DOWN' | 'LEFT' | 'RIGHT'
any(不推荐使用)
let o1:any = 10
// o1 = {}
// o1 = []
o1()
类型断言
作用:当ts类型推断和我们预期的不一样,使用类型断言
断言语法:as
as 后是什么类型就是什么类型
查找dom元素 <a id="link" href="">...</a> <p id="link"></p>
ts把alink的类型推断为HTMLElement(普通的标签类型)
const alink = document.getElementById('link') as HTMLAnchorElement
console.log(alink.href);
Vue3项目中使用TypeScript
script标签上增加lang="ts" 才能写ts
<script setup lang="ts">
</script>
一 ref会根据数据自动推断类型
1 假如是简单数据类型、利用类型推导
const msg = ref('hi')
2 假如是引用数据类型,利用泛型
type Todo = {
id: number
name: string
done: boolean
}
const task = ref<Todo[]>()
task.value = [{id:1,name:'coding',done: false}]
二 reactive的TS写法
1 默认值属性固定,可以使用类型推导
const book = reactive({title: 'TS从入门到放弃',price: 59})
2 类型推导的类型不符合需求,手动给变量或常量指定类型
type Book = {
title: string
price?: number
}
const book:Book = reactive({title: 'TS从入门到放弃', price: 999})
console.log(book);
三 计算属性的TS-可以用类型推断或泛型
类型推断
const upperMsg = computed(() => {
return msg.value.toUpperCase()
})
泛型
const upperMsg = computed<string>(() => {
return msg.value.toUpperCase()
})
四 事件处理的TS
函数参数e默认推断any类型
const handleClick = (e: Event) => {
const ele = e.target as HTMLButtonElement
}
五 ref模板引用的TS写法
const iptRef = ref<HTMLInputElement | null>(null)
onMounted(() => {
// 类型守卫
// if(iptRef.value) {
// iptRef.value.focus()
// }
// iptRef.value?.focus()
// iptRef.value&& iptRef.value.focus()
})
// 修改msg
const changeMsg = (v: string) => {
msg.value = v
}
const changeMsg2 = () =>{
msg.value = 'fe666'
}
六 类型声明文件
d.ts->TS类型声明文件,描述is模块内所有导出成员的类型信息
*.ts 与*.d.ts比较
1*.ts 既可以放类型信息也可以放执行代码,*.d.ts只包括类型信息
2 *.ts -> *.js才能执行,*.d.ts不会生成is文件,尽提供类型信息
3 *.ts->写代码的地方,*.d.ts提供类型信息(类型复用时候,可以用类型声明文件)
类型声明文件
1 下载第三方包,有的自带*.d.ts,有的包没有*.d.ts(安装对应的模块 @types/包)
2 内置类型声明文件
3 自定义类型声明文件