Vue3
Vue3
setup
vue3 中一个新配置项,值为一个函数。在vue3中组件所有用到的数据、方法都要配置在setup中。
setup 两种返回值:
- 若返回一个对象,则对象中属性、方法,在模板中都可以使用
- 若返回一个渲染函数,则可以自定义渲染内容(很少用)
注意:
- 尽量不要与vue2配置混用
- vue2配中配置(data、methods、computed)中可以访问到setup中属性、方法
- setup中访问不到vue2的配置
- 如果有重名,setup优先
- setup不能是一个async函数,因为返回值不再是return的对象,而是promise,模板看不到return对象中的属性
<template>
<button @click="fun">{{str}}</button>
</template>
<script>
export default {
name: 'Test',
setup(){
let str = 'xxx'
function fun() {
console.log('fun')
}
return {
str,
fun,
}
},
}
</script>
ref 函数
- 定义一个响应式数据
- 语法:
const xxx = ref(initValue)
- 创建一个包含响应式数据的引用对象
- js中操作数据:
xxx.value
- 模板中读取数据:不需要.value,直接:
<div>{{xxx}}</div>
- 备注:
- 接收数据可以是:基本类型、对象类型
- 基本类型数据:响应式依然依靠
Object.defineProperty()
的 get set 完成 - 对象类型数据:内部使用了Vue3的新函数——reactive函数,底层使用了Proxy技术
reactive 函数
- 定义一个对象类型的响应式数据(基本类型数据不用它,用 ref 函数)
- 语法:const 代理对象 = reactive(源对象) 接收一个对象(或数组),返回一个代理对象(Proxy的实例对象)
- reactive定义的响应式数据是“深层次”的
- 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据进行操作
<template>
<ul>
<li>{{str}}</li>
<li>{{person.name}}</li>
<li>{{person.age}}</li>
<li>{{person.a.b.c}}</li>
</ul>
<button @click="fun">修改</button>
</template>
<script>
import { reactive, ref } from '@vue/reactivity'
export default {
name: 'Test',
setup() {
// 使用 ref 包装基本类型
let str = ref('xxx')
// 使用 reactive 包装对象类型
let person = reactive({
name: 'tom',
age: 18,
a: {
b: {
c: 0,
},
},
})
function fun() {
console.log(str)
console.log(person)
// 使用 xxx.value 访问基本类型
str.value = '1234'
// 正常操作对象类型
person.name = 'xxx'
person.age = 20
person.a.b.c = 3
}
return {
str,
person,
fun,
}
},
}
</script>
Vue 响应式原理
Vue2 响应式原理
- 实现原理:
- 对象类型:通过 Object.defineProperty() 对属性的读取、修改进行拦截(数据劫持)
- 数组重写:通过重写更新数组的一系列方法实现拦截(对数组更新方法进行了包裹)
Object.defineProperty(data, 'count', { get() {}, set() {}, })
- 存在问题:
- 新增属性、删除属性,界面不会更新
- 直接通过下标修改数组,界面不会自动更新
- 解决方法:
- 新增属性 this.$set(var, key, value)
- 删除属性 this.$delete(var, key)
- 数组的 push shift 等api被重写,可以实现更新
Vue3 响应式原理
- 实现原理:
- 通过 Proxy:拦截对象中任意属性的变化、包括:属性值的读写、属性的添加、属性的删除
- 通过 Reflect:对源对象的属性操作
let person = {
name: 'tom',
age: 18,
}
// Vue2 中数据代理原理
let pp = {}
Object.defineProperty(pp, 'name', {
get(){
return person.name
},
set(val) {
// beforeUpdate
person.age = val
// updated
},
})
Object.defineProperty(pp, 'age', {
get(){
return person.age
},
set(val) {
// beforeUpdate
person.age = val
// updated
},
})
// Vue3 中数据代理原理
const p = new Proxy(person, {
get(target, propName) {
console.log('before getter')
return Reflect.get(target, propName)
},
// 在修改、新增属性时调用
set(target, propName, value) {
console.log('before setter')
Reflect.set(target, propName, value)
},
deleteProperty(target, propName) {
console.log('before delete')
return Reflect.deleteProperty(target, propName)
},
})
reactive 对比 ref
- 定义数据角度:
- ref 用于定义 基本数据类型
- reactive 用于定义:对象(数组)类型数据
- 备注:ref也可以用于定义对象(或数组)类型数据,它内部会自动通过 reactive 转为代理
- 原理角度:
- ref 通过 Object.defineProperty() 的 get 和 set 来实现响应式(数据劫持)
- reactive 通过使用 Proxy 实现响应式,并通过 Reflect 操作内部源对象数据
- 使用角度:
- ref 定义的数据:操作数据需要 .value ,读取数据时模板直接读取不需要 .value
- reactive 定义的数据:操作数据与读取数据均不需要 .value
setup 两个注意点
- setup 执行时机:
- 在 beforeCreate 前执行一次,this 是 undefined
- setup 的参数:
- props:值为对象,包含:组件外部传递过来,且组件内部使用props声明接收了的属性
- context:上下文对象(用于解决 this 为 undefined 的问题)
- attrs:值为对象,包含:组件外传递过来,但没有在props配置中声明的属性,相当于 this.$attrs
- slots:收到的插槽内容,相当于 this.$slots
- emit:分发自定义事件的函数,相当于 this.$emit
computed 计算属性
<template>
姓:<input type="text" v-model="person.lastName"/>
<br/>
名:<input type="text" v-model="person.firstName"/>
<br/>
全名:{{person.fullName}}
</template>
<script>
import { computed, reactive } from 'vue'
export default {
name: 'Test',
setup() {
let person = reactive({
firstName: '',
lastName: '',
})
// 这种方式只能用于读取,不能修改
person.fullName = computed(() => {
return person.lastName + person.firstName
})
person.fullName = computed({
get() {
return person.lastName + person.firstName
},
set(value){
person.lastName = value[0]
person.firstName = value.substring(1)
},
})
return {
person
}
},
}
</script>
watch 函数
两个坑:
- 监视reactive定义的响应式数据时,oldValue无法正确获取、强制开启了深度监视无法关闭。(使用ref定义的基本属性正常)
- 监视reactive定义的响应式数据中某个属性时,deep配置有效
let sum = 0
const person = reactive({
name: 'xxx',
age: 18,
job: {
salary: 1,
}
})
// 情况一:监视ref定义的响应式基本类型数据
watch(sum, (newValue, oldValue) => {
})
// 情况二:监视多个ref定义的基本数据类型
watch([sum, msg], (newValue, oldValue) => {
})
// 情况三:监视reactive定义的响应式对象
// 这种情况下无法正确获得oldValue,强制开启深度监视
watch(person, (newValue, oldValue) => {
})
// 情况四:监视reactive定义的响应式数据中的某个属性
watch(() => person.name, (newValue, oldValue) => {
})
// 情况五:监视reactive定义的响应式数据中的某些属性
watch([() => person.name, () => person.age], (newValue, oldValue) => {
})
// 此时监视的是对象中的对象属性,deep配置有效
watch(() => person.job, (newValue, oldValue) => {
}, {deep: true})
watchEffect 函数
和computed类似,但computed注重计算结果必须有返回值,watchEffect注重逻辑无需返回值。
// 只要回调函数内部引用的数据被修改,函数就会触发
watchEffect(() => {
// 当 sum 和 person.job.salary 改变时函数执行
const x1 = sum.value
const x2 = person.job.salary
console.log('watchEffect 指定的回调函数执行了')
})