Vue3学习笔记
之前一直没有很系统性的去看过vue3的相关知识,刚好在入职后一个多月的时间,遇到了第一个春节假期,因此在这个期间看了vue3的相关知识。不然项目做起来太难受…
Vue3创建项目的两种方式
-
脚手架
-
通过vite安装
什么是vite? —新一代的前端构建工具(类似于webpack)
优势:无需打包,快速启动
初始化工程
attention: Vue3组建中的模板结构可以没有根标签
常用组合式api
setup函数
Vue3中一个新的配置项,值为一个函数
组件中用到的数据、方法等,均要配置在setup中
最后要将setup中的变量和函数写在return中
<template>
<h1>我是App组件</h1>
<h4>姓名:{{name}}</h4>
<h4>年龄:{{age}}</h4>
<button @click="sayHi">sayHi</button>
</template>
<script>
export default {
name: 'App',
setup(){
let name = 'joy';
let age = 23;
function sayHi() {
console.log('Hi')
};
return{
name,
age,
sayHi
}
},
}
</script>
attention:
-
Vue2配置(data,methods,computed)中可以访问到setup中的属性、方法
-
但在setup中不能访问Vue2配置(data、methods,computed)
-
如果有重名,setup优先
async不能用来修饰setup,因为返回值是一个promise对象,不能通过return方式返回并访问
ref函数
ref处理简单类型
ref函数,使变量转换成响应式
<template>
<h1>信息登记</h1>
<h4>姓名:{{name}}</h4>
<h4>年龄:{{age}}</h4>
<button @click="changeInfo">一键修改</button>
</template>
<script>
import {ref} from 'vue'
export default {
name: 'App',
setup(){
let name = ref('joy');
let age = ref(23);
function changeInfo() {
name.value = 'zoey'
age.value = 18
// console.log(name,age)
}
return{
name,
age,
changeInfo
}
},
}
</script>
Ref处理复杂类型
基本类型的数据:响应式依然是靠object.defineProperty()的get和set完成的
对象类型的数据:内部借助了Vue3中的一个新函数 ----- reactive函数
<template>
<h1>信息登记</h1>
<h4>姓名:{{name}}</h4>
<h4>年龄:{{age}}</h4>
<h4>职业:{{job.type}}</h4>
<h4>薪水:{{job.salary}}</h4>
<button @click="changeInfo">一键修改</button>
</template>
<script>
import {ref} from 'vue'
export default {
name: 'App',
setup(){
let name = ref('joy');
let age = ref(23);
let job = ref({
type:'前端开发工程师',
salary:'30K'
})
function changeInfo() {
job.value.type = '算法工程师'
job.value.salary = '50K'
}
return{
name,
age,
job,
changeInfo
}
},
}
</script>
reactive函数
处理对象类型的响应数据,基本类型不要用reactive(无法处理)
reactive定义的响应式数据是“深层次的”
内部基于ES6的Proxy实现,通过代理对象操作源对象内部数据进行操作
<template>
<h1>信息登记</h1>
<h4>姓名:{{person.name}}</h4>
<h4>年龄:{{person.age}}</h4>
<h4>职业:{{person.job}}</h4>
<h4>薪水:{{person.salary}}</h4>
<button @click="changeInfo">一键修改</button>
</template>
<script>
import {reactive} from 'vue'
export default {
name: 'App',
setup(){
const person = reactive({
name:'joy',
age : 23,
job :{
title:'前端开发工程师',
salary:'22.5K'
}
})
function changeInfo() {
person.title = '算法工程师',
person.salary = '50K'
}
return{
person,
changeInfo
}
},
}
</script>
Vue3中的响应式原理
Vue2的响应式原理
实现原理:
-
对象类型:通过Object.defineProperty() 对属性的读取、修改进行拦截(数据劫持)
-
数组类型:通过重写更新数组的一系列方法来实现拦截(对数组的变更方法进行了包裹)
Object.defineProperty(data,'count',{
get(){},
set(){}
})
存在问题:
-
新增属性、删除属性,界面不会更新
-
直接通过下标修改数组,界面不会自动更新
Vue3的响应式原理
reactive对比ref
-
从定义数据角度对比
ref用来定义:基本类型数据
reactive用来定义:对象(或数组)类型数据
attention:ref也可以用来定义对象(或数组)类型数据,它内部会自动通过reactive转为代理对象
-
从原理角度对比
ref通过Object.defineProperty() 的get 与 set来实现响应式(数据劫持)
reactive通过使用Proxy来实现响应式(数据劫持),并通过Reflect操作源对象内部的数据
- 从使用角度对比
ref定义的数据:操作数据需要.value,读取数据时模板中直接读取不需要.value
reactive定义的数据:操作数据与读取数据:均不需要.value
setup的两个注意点
- setup执行的时机
在beforeCreate之前执行一次,this是undefined
- setup的参数
props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性
context:上下文对象
attrs:值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性
emit:收到的插槽内容,相当于this.$slots
slots: 分发自定义事件的函数,相当于this.$emit
context
Demo组件
<template>
<h1>一个人的信息</h1>
<h4>姓名:{{person.name}}</h4>
<h4>年龄:{{person.age}}</h4>
<button @click="test">点击我进行测试</button>
</template>
<script>
import {reactive} from "vue";
export default {
name: "Demo",
props:{
name:{
type:String,
required:true
},
age:{
type:Number,
required: true
}
},
// 注册事件
emits:['hello'],
setup(props,context){
console.log('---context---',context)
let person = reactive({
name:'joy',
age:23
})
// methods
function test(){
// 组件间传值
context.emit('hello',66)
}
return{
person,
test
}
}
}
</script>
<style scoped>
</style>
App.vue
<template>
<Demo name="zoey" age=18
@hello="helloMsg"
></Demo>
</template>
<script>
import Demo from "./components/Demo.vue";
export default {
name: 'App',
components:{Demo},
setup(){
function helloMsg(value){
alert(`你好啊,父组件接收到了Demo的传值${value}`)
}
return{
helloMsg
}
}
}
</script>
slots
Demo组件
<template>
<h1>一个人的信息</h1>
<h4>姓名:{{person.name}}</h4>
<h4>年龄:{{person.age}}</h4>
<button @click="test">点击我进行测试</button>
<hr>
<slot name="god">我是默认值</slot>
</template>
<script>
import {reactive} from "vue";
export default {
name: "Demo",
props:{
name:{
type:String,
required:true
},
age:{
type:Number,
required: true
}
},
emits:['hello'],
setup(props,context){
// props 接收传参
// console.log('---props---',props)
// emit 触发自定义事件
// console.log('---context---',context.emit)
// slot插槽
console.log('---context---',context.slots)
let person = reactive({
name:'joy',
age:23
})
// methods
function test(){
context.emit('hello',66)
}
return{
person,
test
}
}
}
</script>
<style scoped>
</style>
App.vue
<template>
<Demo name="zoey" age=18 @hello="helloMsg">
<template v-slot:god>
<span>大神之路</span>
</template>
</Demo>
</template>
<script>
import Demo from "./components/Demo.vue";
export default {
name: 'App',
components:{Demo},
setup(){
function helloMsg(value){
alert(`你好啊,父组件接收到了Demo的传值${value}`)
}
return{
helloMsg
}
}
}
</script>
Computed计算属性
集成变成了computed()函数
使用时必须先引用,里面必须写回调函数(普通函数 or 箭头函数)
计算属性简写
计算属性全写
<template>
<h1>一个人的信息</h1>
姓:<input type="text" v-model="person.lastName">
<br>
名:<input type="text" v-model="person.firstName">
<br>
姓名:<span>{{person.fullName}}</span>
</template>
<script>
import {reactive,computed} from "vue";
export default {
name: "Demo",
setup(){
let person = reactive({
firstName:'joy',
lastName:'Zhang'
})
// 计算属性
// 计算属性-简写
// person.fullName = computed(()=>{
// return person.firstName + person.lastName
// })
person.fullName = computed({
get(){
return person.firstName +'-'+ person.lastName
},
set(value){
const nameArr = value.split('-')
person.firstName = nameArr[0]
person.lastName = nameArr[1]
}
})
return{
person,
}
}
}
</script>
<style scoped>
</style>
watch属性
situationI:监视ref定义的响应式数据
Demo
<template>
<h2>当前求和为:{{sum}}</h2>
<button @click="sum++">点我+1</button>
</template>
<script>
import {ref,watch} from "vue";
export default {
name: "Demo",
setup(){
let sum = ref(0)
//watch
watch(sum,(newValue,oldValue)=>{
console.log('sum changed',newValue,oldValue)
})
return{
sum
}
}
}
</script>
<style scoped>
</style>
situationII: watch监视多个ref响应式数据的改变
Demo
<template>
<h2>当前求和为:{{sum}}</h2>
<button @click="sum++">点我+1</button>
<h2>当前信息为{{msg}}</h2>
<button @click="msg+='!'">点击</button>
</template>
<script>
import {ref,watch} from "vue";
export default {
name: "Demo",
setup(){
let sum = ref(0)
let msg = ref('新年好')
watch([sum,msg],(newValue,oldValue)=>{
console.log('sum/msg changed',newValue,oldValue)
},{immediate:true})
return{
sum,
msg
}
}
}
</script>
<style scoped>
</style>
Situation III: watch监听reactive定义的数据
-
使用watch监听reactive定义的数据无法获取oldValue
-
在vue3中,不管对象的嵌套有多深,只要是用reactive定义的响应式数据,使用watch均可以开启深度监听
-
默认开启深度监听,并且无法通过{deep:false}的方式关闭
<template>
<h2>当前求和为:{{sum}}</h2>
<button @click="sum++">点我+1</button>
<hr>
<h2>当前信息为{{msg}}</h2>
<button @click="msg+='!'">点击</button>
<hr>
<h2>姓名:{{person.name}}</h2>
<h2>年龄:{{person.age}}</h2>
<h2>职业:{{person.job.job1.title}}</h2>
<h2>薪水:{{person.job.job1.salary}}K</h2>
<button @click="person.name+='~'">修改姓名</button>
<button @click="person.age++">增长年龄</button>
<button @click="person.job.job1.salary++">涨薪</button>
</template>
<script>
import {ref,watch,reactive} from "vue";
export default {
name: "Demo",
setup(){
let sum = ref(0)
let msg = ref('新年好')
let person = reactive({
name:'joy',
age:23,
job:{
job1:{
title:'前端开发工程师',
salary:30
}
}
})
watch(person,(newValue,oldValue)=>{
console.log('person changed',newValue,oldValue)
},{deep:true})
return{
sum,
msg,
person
}
}
}
</script>
<style scoped>
</style>
Situation IV:watch监听reactive定义的一个响应式数据中的一个属性
监听对象的某个属性,必须写成箭头函数的返回值形式,() => {}
<script>
watch(()=>person.job.job1.salary,(newValue,oldValue)=>{
console.log('person changed',newValue,oldValue)
})
}
}
</script>
Situation V:watch监视reactive定义的一个响应式数据中的某些属性
<script>
import {ref,watch,reactive} from "vue";
watch([()=>person.name,()=>person.age,()=>person.job.job1.salary],(newValue,oldValue)=>{
console.log('person changed',newValue,oldValue)
})
</script>
特殊情况
当监听的数据为reactive响应式数据中某个属性中的深层属性,需要添加deep属性
无法监听的情况
watch(()=>person.job,(newValue,oldValue)=>{
console.log('job changed',newValue,oldValue)
})
加上deep后可以被监听
watch(()=>person.job,(newValue,oldValue)=>{
console.log('job changed',newValue,oldValue)
},{deep:true})
注意:
-
监视reactive定义的响应式数据时,oldValue无法正确获取、强制开启了深度监听(deep配置失效)
-
监视reactive定义的响应式数据中某个对象属性时,deep配置有效
watchEffect函数
-
watch:既要指明监视的属性,也要指明监视的回调
-
watchEffect:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性
-
watchEffect有点像computed:
computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值
而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值
watchEffect(()=>{
// 只要被用到的值,都会开启自动监听
const x1 = sum.value
const x2 = person.job.job1.salary
console.log('watchEffect被触发了')
})
Vue3的生命周期
beforeCreate
created
beforeMount
mounted
beforeUpdate
updated
beforeUnmount
unmounted
Demo组件
<template>
<h2>当前求和为:{{sum}}</h2>
<button @click="sum++">点我+1</button>
</template>
<script>
import {ref,watchEffect,reactive} from "vue";
export default {
name: "Demo",
setup(){
let sum = ref(0)
return{
sum,
}
},
beforeCreate() {
console.log('---beforeCreate---')
},
created() {
console.log('---created---')
},
beforeMount() {
console.log('---beforeMount---')
},
mounted() {
console.log('---mounted---')
},
beforeUpdate() {
console.log('---beforeUpdate---')
},
updated() {
console.log('---updated---')
},
beforeUnmount() {
console.log('---beforeUnmount---')
},
unmounted() {
console.log('---unmounted---')
}
}
</script>
<style scoped>
</style>
App.vue
<template>
<Demo v-if="isShowDemo"></Demo>
<br>
<br>
<button @click="isShowDemo = !isShowDemo">点击显示/隐藏</button>
</template>
<script>
import {ref} from 'vue'
import Demo from "./components/Demo.vue";
export default {
name: 'App',
components:{Demo},
setup(){
let isShowDemo = ref(true)
return{
isShowDemo
}
}
}
</script>
组合式api的生命周期钩子函数的写法
Vue3也提供了组合api形式的生命周期钩子,与Vue2中钩子对应关系如下:
说明:组合式api的方式,就是把这些生命周期钩子函数全部集成到setup()中
beforeCreated ====> setup()
created ====> setup()
beforeMount =====> onBeforeMount
mounted ====> onMounted
beforeUpdate ====> onBeforeUpdate
updated ====> onUpdated
beforeUnmount ====> onBeforeUnmount
unmounted ====> onUnmounted
Demo组件
<template>
<h2>当前求和为:{{sum}}</h2>
<button @click="sum++">点我+1</button>
</template>
<script>
import {ref,watchEffect,reactive,onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted} from "vue";
export default {
name: "Demo",
setup(){
let sum = ref(0)
console.log('---setup---')
onBeforeMount(()=>{
console.log('---onBeforeMount---')
})
onMounted(()=>{
console.log('---onMounted---')
})
onBeforeUpdate(()=>{
console.log('---onBeforeUpdate---')
})
onUpdated(()=>{
console.log('---onUpdated---')
})
onBeforeUnmount(()=>{
console.log('---onBeforeUnmount---')
})
onUnmounted(()=>{
console.log('---onUnmounted---')
})
return{sum,}
},
// beforeCreate() {
// console.log('---beforeCreate---')
// },
// created() {
// console.log('---created---')
// },
// beforeMount() {
// console.log('---beforeMount---')
// },
// mounted() {
// console.log('---mounted---')
// },
// beforeUpdate() {
// console.log('---beforeUpdate---')
// },
// updated() {
// console.log('---updated---')
// },
// beforeUnmount() {
// console.log('---beforeUnmount---')
// },
// unmounted() {
// console.log('---unmounted---')
// }
}
</script>
<style scoped>
</style>
attention:
使用组合api中的钩子函数,触发比配置项快
自定义hook函数
-
什么是hook?——本质是一个函数,把setup函数中使用的组合式api进行了封装
-
类似于vue2中的mixin
-
自定义hook的优势:复用代码,让setup中的逻辑更清楚易懂
案例:鼠标点击获取鼠标所点击的坐标
Demo组件
<template>
<h2>当前求和为:{{sum}}</h2>
<button @click="sum++">点我+1</button>
<hr>
<h2>当前的坐标为x:{{position.x}} y:{{position.y}}</h2>
</template>
<script>
import {ref, reactive, onMounted, onBeforeUnmount} from "vue";
export default {
name: "Demo",
setup(){
let sum = ref(0)
let position = reactive({
x:0,
y:0
})
const getPosition = (e)=>{
position.x = e.pageX
position.y =e.pageY
console.log(e.pageX,e.pageY)
}
onMounted(()=>{
window.addEventListener('click',getPosition)
})
onBeforeUnmount(()=>{
window.removeEventListener('click',getPosition)
})
return{sum,position}
},
}
</script>
<style scoped>
</style>
使用hooks
在src目录下新建一个hooks文件夹,里面定义一个js文件
import {ref, reactive, onMounted, onBeforeUnmount} from "vue";
export function usePosition() {
let position = reactive({
x:0,
y:0
})
const getPosition = (e)=>{
position.x = e.pageX
position.y =e.pageY
console.log(e.pageX,e.pageY)
}
onMounted(()=>{
window.addEventListener('click',getPosition)
})
onBeforeUnmount(()=>{
window.removeEventListener('click',getPosition)
})
//这里一定要有return值
return position
}
在组件中,对hooks进行引用,之后使用值去接收函数传递的值
<template>
<h2>当前求和为:{{sum}}</h2>
<button @click="sum++">点我+1</button>
<hr>
<h2>当前的坐标为x:{{position.x}} y:{{position.y}}</h2>
</template>
<script>
import {ref, reactive, onMounted, onBeforeUnmount} from "vue";
import {usePosition} from "../hooks/usePosition";
export default {
name: "Demo",
setup(){
let sum = ref(0)
const position = usePosition()
return{sum,position}
},
}
</script>
<style scoped>
</style>
toRef
将一个对象中的某个属性,变成响应式数据
Demo组件
<template>
<h2>姓名:{{person.name}}</h2>
<h2>年龄:{{person.age}}</h2>
<h2>职业:{{person.job.job1.title}}</h2>
<h2>薪水:{{person.job.job1.salary}}K</h2>
<button @click="person.name+='~'">修改姓名</button>
<br>
<br>
<button @click="person.age++">增长年龄</button>
<br>
<br>
<button @click="person.job.job1.salary++">涨薪</button>
<br>
<br>
</template>
<script>
import {ref,toRef,reactive} from "vue";
export default {
name: "Demo",
setup(){
let person = reactive({
name:'joy',
age:23,
job:{
job1:{
title:'前端开发工程师',
salary:30
}
}
})
return{
person,
name:toRef(person,'name'),
age:toRef(person,'age'),
title: toRef(person.job.job1,'title'),
salary: toRef(person.job.job1,'salary')
}
}
}
</script>
<style scoped>
</style>
toRefs
把一个对象中的所有属性,都变成响应式数据
<template>
<h2>姓名:{{person.name}}</h2>
<h2>年龄:{{person.age}}</h2>
<h2>职业:{{person.job.job1.title}}</h2>
<h2>薪水:{{person.job.job1.salary}}K</h2>
<button @click="person.name+='~'">修改姓名</button>
<br>
<br>
<button @click="person.age++">增长年龄</button>
<br>
<br>
<button @click="person.job.job1.salary++">涨薪</button>
<br>
<br>
</template>
<script>
import {ref,toRef,reactive,toRefs} from "vue";
export default {
name: "Demo",
setup(){
let person = reactive({
name:'joy',
age:23,
job:{
job1:{
title:'前端开发工程师',
salary:30
}
}
})
const x = toRefs(person)
console.log('x=======',x)
return{
person,
name:toRef(person,'name'),
age:toRef(person,'age'),
title: toRef(person.job.job1,'title'),
salary: toRef(person.job.job1,'salary')
}
}
}
</script>
<style scoped>
</style>
其他组合式api
shallowReactive和shallowRef
readonly与shallowReadonly
toRaw与markRaw
customRef
provide与inject
响应式数据的判断
isRef:检查一个值是否为一个ref对象
isReactive:检查一个对象是否是由reactive创建的响应式代理
isReadonly:检查一个对象是否是由readonly创建的只读代理
isProxy:检查一个对象是否是由reactive或者readonly方法创建的代理
<template>
<Demo v-if="isShowDemo"></Demo>
<br>
<br>
<button @click="isShowDemo = !isShowDemo">点击显示/隐藏</button>
</template>
<script>
import {ref,reactive,readonly,isRef,isReactive,isReadonly,isProxy} from 'vue'
import Demo from "./components/Demo.vue";
export default {
name: 'App',
components:{Demo},
setup(){
let isShowDemo = ref(true)
let car = reactive({name:'nio',price:'40w'})
let carCopy = readonly(car)
console.log(isRef(isShowDemo)) //true
console.log(isReactive(car)) //true
console.log(isProxy(car)) //true
console.log(isReadonly(carCopy)) //true
console.log(isProxy(carCopy)) //true
//readonly只设置成只读,并不改变car的Proxy属性
return{isShowDemo,car,carCopy}
}
}
</script>
Composition api的优势
option api存在的问题
在传统OptionAPI中,新增或者修改一个需求,就需要分别在data,methods,computed里修改
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3bNbfkGr-1644217195480)(file:///Users/joy.zhang1/Library/Application%20Support/marktext/images/2022-02-02-11-20-47-image.png)]
Composition API的优势
我们可以更加优雅的组织我们的代码,函数。让相关功能的代码更加有序的组织在一起
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fL9kDCQ7-1644217195481)(file:///Users/joy.zhang1/Library/Application%20Support/marktext/images/2022-02-02-11-19-48-image.png)]
新的组件
Fragment
-
在vue2中:组件必须有一个根标签
-
在Vue3中,组件可以没有根标签,内部会将多个标签包含在一个Fragment虚拟元素中
-
好处:减少标签层级,减小内存占用
Teleport
Suspense
目前还处于实验阶段