Vue3 响应式专题
Vue3是使用Proxy来创建响应式对象,Proxy可以劫持整个data对象,然后递归返回属性的值的代理。Vue3还引入了新的Reactivity API,包括ref和reactive等新的API,使得响应式系统更加灵活和易用。ref用于对单个值进行响应式处理,reactive用于对对象进行响应式处理
Proxy是什么
Proxy是一种代理,它可以代表其他对象或函数进行操作。在JavaScript中,Proxy是一个构造函数,它可以创建一个对象,这个对象可以拦截对目标对象的访问和修改。Proxy的作用是可以自定义一些基本操作的行为,比如属性读取、赋值、枚举、函数调用等。
toRefs()
toRefs() 可以把一个响应式对象转换成一个普通对象,其中每个属性都是一个指向原始对象相应属性的 ref。这在处理可选属性或者把一个对象解构成单独的变量时很有用。
例如,如果你想在 script setup 中使用 props,你可以这样写:
<script setup>
import { toRefs } from 'vue'
const props = defineProps({
name: String,
age: Number
})
const { name, age } = toRefs(props)
</script>
这样就可以保持 props 的响应性,而不需要每次都写 props.name 或者 props.age。
Ref()
ref 是一个特殊的属性,类似于 v-for 章节中讨论的 key 属性。它可以让我们在组件挂载后获取一个指向特定 DOM 元素或子组件实例的直接引用。这在你想要,比如,聚焦一个输入框或者在一个元素上初始化一个第三方库时很有用。
例如,如果你想在模板中引用一个 input 元素,你可以这样写:
<template>
<div>
<input ref="usernameInput" />
<button @click="focus">Focus</button>
</div>
</template>
<script>
export default {
setup() {
const usernameInput = ref(null)
function focus() {
usernameInput.value.focus()
}
return {
usernameInput,
focus
}
}
}
</script>
这样就可以通过 usernameInput.value 来访问 input 元素,并调用它的 focus 方法。
ref 还有另一个用法,就是把基本类型(String, Boolean, Number 等)转换成响应式对象。这时候 ref 返回的是一个包含 value 属性的对象,而不是直接返回基本类型的值。
例如,如果你想创建一个响应式的字符串变量,你可以这样写:
const message = ref('Hello')
console.log(message.value) // 'Hello'
message.value = 'World'
console.log(message.value) // 'World'
reactive
reactive 是一个函数,它可以把一个普通的 JavaScript 对象转换成一个响应式的代理对象。这意味着当对象的属性发生变化时,Vue 会自动更新依赖于这些属性的视图层。
例如,如果你想创建一个响应式的用户对象,你可以这样写:
const user = reactive({
name: 'Alice',
age: 25
})
这样就可以在模板中使用 user.name 和 user.age,并且当它们改变时,视图也会重新渲染。
reactive 和 ref 的区别是,reactive 只能用于对象,而 ref 可以用于基本类型。另外,reactive 返回的是原始对象的代理,而 ref 返回的是包含 value 属性的对象。
computed
computed 是一个函数,它可以创建一个计算属性,用于根据其他值来修改、操作和显示数据。这个特性可以让我们在模板中复用这些计算和转换的结果,并且提高代码的可读性和效率。
例如,如果你想创建一个计算属性,用于根据用户的年龄显示不同的信息,你可以这样写:
const user = reactive({
name: 'Alice',
age: 25
})
const message = computed(() => {
if (user.age < 18) {
return 'You are too young.'
} else if (user.age >= 18 && user.age < 30) {
return 'You are in your prime.'
} else {
return 'You are wise and experienced.'
}
})
这样就可以在模板中使用 message.value 来显示不同的信息,并且当 user.age 改变时,message.value 也会自动更新。
computed 还可以接受一个对象作为参数,包含 get 和 set 方法3。这样就可以自定义计算属性的读取和写入逻辑。
例如,如果你想创建一个计算属性,用于设置用户的全名,你可以这样写:
const user = reactive({
firstName: 'Alice',
lastName: 'Smith'
})
const fullName = computed({
get() {
return `${user.firstName} ${user.lastName}`
},
set(value) {
const [firstName, lastName] = value.split(' ')
user.firstName = firstName
user.lastName = lastName
}
})
这样就可以在模板中使用 fullName.value 来获取或设置用户的全名,并且当 user.firstName 或 user.lastName 改变时,fullName.value 也会自动更新。
watch
watch 是一个函数,它可以让我们观察一个或多个响应式数据,并且当它们发生变化时,执行一些副作用。它相当于一个事件监听器,用于响应数据的变化。
例如,如果你想观察用户的年龄,并且当它改变时,发送一个 API 请求,你可以这样写:
const user = reactive({
name: 'Alice',
age: 25
})
watch(() => user.age, async (newAge) => {
const response = await fetch(`https://example.com/api/user/${user.name}/age/${newAge}`)
const data = await response.json()
// do something with data
})
这样就可以在每次 user.age 改变时,调用 watch 的回调函数,并传入新的值和旧的值。
watch 还可以接受一个对象作为参数,包含 source、handler 和其他选项。source 可以是一个 ref、reactive 或者一个返回值的函数。handler 是一个回调函数,用于处理数据变化。其他选项包括 deep、immediate 和 flush 等。
例如,如果你想深度观察用户对象,并且立即执行回调函数,你可以这样写:
const user = reactive({
name: 'Alice',
age: 25
})
watch(user, async (newUser) => {
const response = await fetch(`https://example.com/api/user/${newUser.name}/age/${newUser.age}`)
const data = await response.json()
// do something with data
}, { deep: true, immediate: true })
这样就可以在每次 user 对象或其属性改变时,调用 watch 的回调函数,并传入新的对象和旧的对象。
watchEffect
watchEffect 是一个函数,它可以让我们执行一个副作用函数,并且自动追踪它内部使用的响应式数据当这些数据发生变化时,watchEffect 会重新执行这个副作用函数。
例如,如果你想执行一个副作用函数,并且根据用户的年龄显示不同的信息,你可以这样写:
const user = reactive({
name: 'Alice',
age: 25
})
watchEffect(() => {
if (user.age < 18) {
console.log('You are too young.')
} else if (user.age >= 18 && user.age < 30) {
console.log('You are in your prime.')
} else {
console.log('You are wise and experienced.')
}
})
这样就可以在每次 user.age 改变时,重新执行 watchEffect 的回调函数,并打印不同的信息。
watchEffect 和 watch 的主要区别是,watch 只追踪显式指定的源数据,而 watchEffect 追踪所有在回调函数中访问的数据。另外,watch 的回调函数只在源数据真正改变时触发,而 watchEffect 的回调函数在每次渲染时都会触发