Vue3
1.Proxy代理原理
这个proxy是ES6新增的属性,我们可以利用这个为对象新增属性,Vue3也是实现了这种代理机制对数据进行代理。比起Vue2中Object.defineProperty函数功能更加丰富和强大。
我们利用一个例子来演示proxy的原理
let user = {
name: 'zhangsan'
}
const userProxy = new Proxy(user, {
/*
target 表示目标对象
propertyName 表示目标对象上的属性名,是一个字符串
*/
get(target, propertyName) {
console.log('@@@@get执行了...');
return target[propertyName]
},
set(target, propertyName, value) {
console.log('@@@@set执行了...');
target[propertyName] = value
},
deleteProperty(target, propertyName) {
console.log('@@@@deleteProperty执行了...');
return target[propertyName]
}
})
我们定义了一个user对象,然后对user对象进行代理,这里面有三个函数,get() set()以及deleteProperty()。
get()函数表示当我们访问代理对象的属性时,get方法会被调用,返回访问属性的值。
set()函数表示当我们修改或者添加代理对象上的属性时,set()方法会被调用,返回修改或添加的属性的值。
deleteProperty()函数表示当我们对代理对象属性进行删除时,会被自动调用,返回true或false,true表示修改成功,false表示修改失败。
运行结果
2.setup函数
在Vue2中我们都知道数据都写在data里面,方法写在methods里面…但是在Vue3中这些都写在setup函数里面
注意:setup 中的 this 是 undefined。所以setup 中 this 是不可用的
示例
setup可以返回一个对象,该对象的属性、方法等均可以在模板语法中使用,例如插值语法。
APP.vue组件
<template>
<h1>姓名:{{ name }}</h1>
<h1>年龄:{{ age }}</h1>
<button @click="sayHi">SAY HI</button>
</template>
<script>
export default {
name: 'App',
setup() {
let name = '张三'
let age = 19
function sayHi(){
alert('hello Vue3')
}
return {name,age,sayHi}
}
</script>
setup也可以返回一个渲染函数,从而执行渲染函数,渲染页面,原来页面中的标签会被覆盖。
<template>
<h1>hello</h1>
</template>
<script>
import {h} from 'vue'
export default {
name: 'App',
setup() {
return function () {
return h('h2','欢迎来学习Vue3')
}
/* 简写 */
//return ()=>h('h2','欢迎来学习Vue3')
}
}
</script>
3.ref 对基本数据类型完成响应式
底层用的是Object.defineProperty实现的数据代理。
示例代码
<template>
<h1>姓名:{{ namerefIml }}</h1>
<h1>年龄:{{ agerefIml }}</h1>
<button @click="upDateData">更新数据(ref响应式)</button>
</template>
<script>
import { ref } from 'vue'
export default {
name: 'App',
setup() {
let namerefIml = ref('张三')
console.log(namerefIml);
let agerefIml = ref(19)
function upDateData() {
namerefIml.value = '李四'
agerefIml.value=20
}
return {namerefIml,agerefIml,upDateData}
}
}
</script>
console.log(namerefIml);后,我们可以在控制台观察到,RefImpl(引用对象中的value有响应式)
上述代码中,当我们点击按钮,页面的数据也随之改变,实现了响应式。
4.ref对 对象实现响应式
示例代码
<template>
<h1>姓名:{{ user.name }}</h1>
<h1>年龄:{{ user.age }}</h1>
<!-- <button @click="upDateData">更新数据(ref响应式)</button> -->
</template>
<script>
import { ref } from 'vue'
export default {
name: 'App',
setup() {
let user = ref({
name: 'jack',
age:19
})
console.log(user);
return {user}
}
}
</script>
结果:
从上图可以观察到,Vue3底层对对象进行代理时,外层先用Object.defineProxy进行代理,如果是对象类型的话,此对象用Proxy进行代理。
如何改变ref代理的对象内属性的值?
示例代码
对引用对象userRefImpl上的value中代理的对象的属性的值进行修改,从而实现响应式,页面随之更新。
<template>
<h1>姓名:{{ userRefImpl.name }}</h1>
<h1>年龄:{{ userRefImpl.age }}</h1>
<button @click="upDateData">更新对象数据(ref响应式)</button>
</template>
<script>
import { ref } from 'vue'
export default {
name: 'App',
setup() {
let userRefImpl = ref({
name: 'jack',
age:19
})
console.log(userRefImpl)
function upDateData() {
userRefImpl.value.name = 'smith'
userRefImpl.value.age=21
}
return {userRefImpl,upDateData}
}
}
</script>
5.reactive添加响应式
注意:reactive不支持给基本数据类型添加响应式,只能给对象添加响应式,对象属性的添加,删除,修改都具有响应式。
示例代码
<template>
<h1>姓名:{{ userProxy.name }}</h1>
<h1>年龄:{{ userProxy.age }}</h1>
<button @click="upDateData">更新对象数据(reactive响应式)</button>
</template>
<script>
import { reactive} from 'vue'
export default {
name: 'App',
/*
注意:reactive不支持给基本数据类型添加响应式,只能给对象添加响应式
*/
setup() {
let userProxy = reactive({
name: 'jack',
age:19
})
console.log(userProxy)
function upDateData() {
userProxy.name = 'smith'
userProxy.age=22
}
return {userProxy,upDateData}
}
}
</script>
console.log(userProxy),我们可以看到底层是一个代理对象,用代理对象实现了为对象添加响应式
6.props父传子数据
示例代码
App.vue(父组件)
<template>
<User name="张三" age="20" :sex="sex"></User>
</template>
<script>
import {ref} from 'vue'
import User from './components/User.vue'
export default {
name: 'App',
components:{User},
setup() {
let sex = ref('男')
return {sex}
}
}
</script>
User.vue(子组件)
<template>
<h1>演示props父传子数据</h1>
<h2>姓名:{{ name }}</h2>
<h2>年龄:{{ age }}</h2>
<h2>性别:{{ sex }}</h2>
</template>
<script>
export default {
name: 'User',
props:['name','age','sex'],
setup(props) {
console.log(props.name);
console.log(props.age);
console.log(props.sex);
}
}
</script>
运行结果:
props传递过来的数据都具有响应式。
7.Vue3生命周期
演示代码
生命周期可以分为两个形式演示,一种是选项式API(写在setup外面的),另一种是组合式API(写在setup内的)
父组件APP.vue
<template>
<button @click="isDisplay = !isDisplay">显示或隐藏</button>
<User v-if="isDisplay"></User>
</template>
<script>
import {ref} from 'vue'
import User from './components/User.vue'
export default {
name : 'App',
components : {User},
setup(){
//console.log('setup:安装组合式API')
// data
let isDisplay = ref(true)
// 返回对象
return {isDisplay}
},
/* beforeCreate(){
console.log('beforeCreate')
console.log('正在初始化选项式API')
},
created(){
console.log('选项式API初始化完成')
console.log('created')
} */
}
</script>
子组件User.vue
<template>
<h1>User组件</h1>
<h2>计数器:{{counter}}</h2>
<button @click="counter++">计数</button>
</template>
<script>
import {ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted} from 'vue'
export default {
name : 'User',
setup(){
console.log('setup...')
// 采用组合式API:生命周期钩子函数
/* onBeforeMount(() => {
console.log('onBeforeMount')
})
onMounted(() => {
console.log('onMounted')
})
onBeforeUpdate(() => {
console.log('onBeforeUpdate')
})
onUpdated(() => {
console.log('onUpdated')
})
onBeforeUnmount(() => {
console.log('onBeforeUnmount')
})
onUnmounted(() => {
console.log('onUnmounted')
}) */
// data
let counter = ref(0)
// 返回一个对象
return {counter}
},
// 选项式API
/* beforeCreate(){
console.log('beforeCreate')
},
created(){
console.log('created')
}, */
// 在Vue3当中,生命周期钩子函数有两种写法:
// 第一种写法:选项式API(以下这种方式就是选项式API)
// 第二种写法:组合式API
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>
运行结果:
9.自定义事件
自定义事件可以用于子组件向父组件传递数据,在父组件中自定义事件,然后子组件触发事件,触发的同时传递数据给父组件,父组件接收到子组件传递的数据。
示例代码
App.vue 父组件
语法: 这里的函数用于接收子组件传来的数据
<组件 @自定义事件名=“函数”></组件>
<template>
<User @event1="showInfo"></User>
</template>
<script>
import User from './components/User.vue'
export default {
components: { User },
name: 'App',
setup() {
function showInfo(name, age) {
alert('姓名:'+name+" 年龄:"+age)
}
return {showInfo}
}
}
</script>
User.vue 子组件
语法:
context.emit(‘事件名字’,数据)
<template>
<button @click="triggerEvent">子传父</button>
</template>
<script>
export default {
name: 'User',
setup(props,context) {
function triggerEvent() {
context.emit('event1','张三',19)
}
return {triggerEvent}
}
}
</script>
<style>
</style>
10.全局事件总线
10.1绑定事件传递数据
首先我们要先安装mitt这个插件
npm i mitt
通常我们会在src下建立utils文件夹(工具),在utils文件夹下引入mitt插件
event-bus.js
import mitt from 'mitt'
export default mitt()
emitter是引入的mitt插件 import emitter from ‘./utils/event-bus.js’
emitter.on(‘事件名’,函数处理数据)用来绑定事件,emitter.emit(‘触发事件名’,数据(可以是对象,也可以是基本数据类型))
App.vue
<template>
<User></User>
</template>
<script>
import emitter from './utils/event-bus.js'
import User from './components/User.vue'
import { onMounted } from 'vue'
export default {
components: { User },
name: 'App',
setup() {
onMounted(() => {
emitter.on('event1',showInfo)
})
function showInfo(user) {
alert('姓名:'+user.name+" 年龄:"+user.age)
}
return {showInfo}
}
}
</script>
User.vue
<template>
<button @click="triggerEvent">全局事件总线</button>
</template>
<script>
import emitter from '../utils/event-bus.js'
export default {
name: 'User',
setup() {
function triggerEvent() {
emitter.emit('event1',{name:'张三',age:21})
}
return {triggerEvent}
}
}
</script>
<style>
</style>
10.2解绑事件
//引入插件
import emitter from '../utils/event-bus.js'
//解绑所有绑定的事件
emitter.all.clear()
//解绑指定事件
emitter.off('事件名')
11.计算属性
计算属性顾名思义就是通过一系列计算(处理)后所得到的一个属性,重点是属性,可以直接在插值语法中使用。
语法:
/* 简写形式 当我们不需要set方法时,可以使用简写形式。*/
let reversedName = computed(() => {
return data.name.split('').reverse().join('')
})
/* 完整写法 */
let reversedName = computed({
get() {
....
return 处理过后的值
},
//当计算属性的值发生改变,set方法就会执行
set(val) {
....
}
})
我们通过一个小案例来讲解vue3中的计算属性
反转字符串案例
App.vue
<template>
输入字符串:<input type="text" v-model="data.name"><br><br>
反转之后的:<input type="text" v-model="reversedName">
</template>
<script>
import { computed, reactive } from 'vue'
export default {
name: 'App',
setup() {
let data = reactive({
name:''
})
/* 简写形式 */
/* let reversedName = computed(() => {
return data.name.split('').reverse().join('')
}) */
/* 完整写法 */
let reversedName = computed({
get() {
return data.name.split('').reverse().join('')
},
set(val) {
console.log(val);
}
})
return {data,reversedName}
}
}
</script>
12.ref监视和reactive监视
12.1ref监视
对ref包裹的数据(基本数据类型)进行监视
//对ref包裹的数据进行监视
watch(监视的数据, (newValue, oldValue) => {
console.log(newValue,oldValue);
})
//如果我们发现监视的多个数据具有相同的业务,我们可以把多个需要监视的数据放到数组中
watch([数据1,数据2,...], (newValue, oldValue) => {
console.log(newValue,oldValue);
})
点我加一案例
<template>
<h1>数字:{{ counter }}</h1>
<h1>数字2:{{ counter2 }}</h1>
<button @click="counter++">监视counter 点我加一</button>
<br><br>
<button @click="counter2++">监视counter2 点我加一</button>
</template>
<script>
import { watch,ref } from 'vue'
export default {
name: 'App',
setup() {
let counter = ref(1)
let counter2= ref(100)
/* watch(counter, (newValue, oldValue) => {
console.log(newValue,oldValue);
})
watch(counter2, (newValue, oldValue) => {
console.log(newValue,oldValue);
}) */
watch([counter,counter2], (newValue, oldValue) => {
console.log(newValue,oldValue);
})
return {counter,counter2}
}
}
</script>
12.2reactive监视
下面的案例我们演示了对reactive包裹的对象进行监视时的几种情况。
<template>
<h1>数字:{{ data.counter }}</h1>
<button @click="data.counter++">监视counter 点我加一</button>
<h1>数字:{{ data.a.b.number }}</h1>
<button @click="data.a.b.number++">监视counter 点我加一</button>
</template>
<script>
import { watch,reactive } from 'vue'
export default {
name: 'App',
setup() {
let data = reactive({
counter: 1,
a: {
b: {
number:3
}
}
})
/* 对data对象进行监听,无法对旧值进行监听。 */
/* watch(data, (newValue, oldVue) => {
console.log(newValue,oldVue);
}) */
/* 如果只想监听一个属性 */
/* watch(()=>data.counter, (newValue, oldVue) => {
console.log(newValue,oldVue);
}) */
/* deep生效 */
/* watch(()=>data.a.b, (newValue, oldVue) => {
console.log(newValue,oldVue);
}, { deep: false }) */
/* deep失效 */
watch(data.a.b, (newValue, oldVue) => {
console.log(newValue,oldVue);
},{deep:false})
return {data}
}
}
</script>
13.watchEffect
可以监视属性的变化,值一旦改变,watchEffect就会执行,注意:浏览器打开时会自动执行一次。
示例代码:
counter1和counter2值一旦改变,watchEffect会自动执行回调函数
<template>
<h2>counter1:{{ data.counter1 }}</h2>
<button @click="data.counter1++">点我加1</button>
<hr>
<h2>counter2:{{ data.counter2 }}</h2>
<button @click="data.counter2++">点我加1</button>
<hr>
<h2>counter3:{{ data.counter3 }}</h2>
<button @click="data.counter3++">点我加1</button>
</template>
<script>
import { reactive, watchEffect } from 'vue'
export default {
name: 'App',
setup() {
let data = reactive({
counter1: 1,
counter2: 2,
counter3:3
})
//这里监视两个属性,counter1和counter2值一旦改变,watchEffect会自动执行回调函数
watchEffect(() => {
let a = data.counter1
let b = data.counter2
console.log(a,b);
})
return {data}
}
}
</script>
13.hooks实现代码复用
当一段程序需要多个使用时,我们可以把这一段程序提取出来,然后再用到的组件中引入。
示例:
App.vue组件
<template>
数字1:<input type="number" v-model="data.number1"><br><br>
数字2:<input type="number" v-model="data.number2"><br><br>
求和结果:{{data.result}}<br><br>
<button @click="sum">求和</button>
</template>
<script>
//引入公共复用的代码
import sum from './hooks/sum'
export default {
name: 'App',
setup() {
//调用引入的js程序
return sum()
}
}
</script>
sum.js(公共代码)代码一般提取到一个单独的js文件,放在hooks文件夹下
import { reactive } from 'vue'
export default function () {
let data = reactive({
number1: 0,
number2: 0,
result: 0
})
function sum() {
data.result = data.number1 + data.number2
}
return { data, sum }
}
14.浅层次shallowRef
shallowRef包裹对象没有响应式 ,只是浅层次响应式,对于直接包裹基本数据类型有响应式,但是包裹对象没有响应式
一般用于对系统性能优化,如果值不需要修改,那么可以放在shallowRef里面,提高性能。
示例
//具有响应式
let data = shallowRef(1)
//没有响应式
let data = shallowRef({
counter:1
})
15.浅层次shallowReactive
浅层次shallowReactive 对象的第一层具有响应式,其他不具有响应式。
示例
let data = shallowReactive({
counter:1,//具有响应式
a: {
counter2:10 //没有响应式
}
})
16.响应式数据判断
isRef:检查某个值是否为 ref。
isReactive:检查一个对象是否是由 reactive() 或 shallowReactive() 创建的代理。
isProxy:检查一个对象是否是由 reactive()、readonly()、shallowReactive() 或 shallowReadonly() 创建的代理。
isReadonly:检查传入的值是否为只读对象。
17.深只读和浅只读
深只读:所有数据都不能修改
浅只读:只有被包裹对象的第一层只读
/* 深只读 */
data = readonly(data)
/* 浅只读,只有第一层只读 */
data = shallowReadonly(data)
18.toRef和toRefs
当我们进行了多层的嵌套,出现的问题在于插值语法中多次重复的写data.
let data = reactive({
counter1 : 1,
counter2 : 100,
a : {
b : {
counter3 : 1000
}
}
})
插值语法的问题,我们会发现每次都要进行data.
<h2>{{data.counter1}}</h2>
<h2>{{data.a.b.counter3}}</h2>
我们可不可以经过优化写成如下这样?去掉data.
<h2>{{counter1}}</h2>
<h2>{{a.b.counter3}}</h2>
解决方法
// toRef
// toRef语法格式:toRef(对象, '该对象中的属性名')
// toRef函数执行之后会生成一个全新的对象:ObjectRefImpl(引用对象)
// 只要有引用对象,就有value属性,并且value属性是响应式的。(value有对应的set和get。)
//第一种toref,vue3中返回时这样写
return {
counter1 : toRef(data, 'counter1'),
counter3 : toRef(data.a.b, 'counter3')
}
//第二种toRefs
return {
data,
...toRefs(data)
}
19.转换为原始&标记为原始
toRaw 与 markRaw
toRaw:将响应式对象转换为普通对象。只适用于 reactive 生成的响应式对象。
markRaw:标记某个对象,让这个对象永远都不具备响应式。比如在集成一些第三方库的时候,比如有一个巨大
的只读列表,不让其具备响应式是一种性能优化。
用法:
//如果data对象具有响应式,但是由于项目需求,现在data不需要响应式了,那么可以写下面这个代码
let rawObj = toRaw(data)
//标记某个对象,让这个对象永远都不具备响应式
markRaw(对象)
20.provide和inject
这两个是用于组件之间数据的传递的,一般建议用在隔代组件之间数据的传递
用法:
provide('名字', 数据) //发送数据方
inject('名字')//接收数据方
//注意provide和inject的名字一定要保持一致
21.语法糖
//在script标签中 加人setup data,methods等不需要再写在setup函数中
<script setup>
import {ref} from 'vue'
import User from './components/User.vue'//组件可直接引入后使用,不需要再注册
// data
let counter = ref(0)
// methods
function add(){
...
}
// watch
// computed
// ....
</script>