ref和reactive都是响应式编程API
ref和reactive都是Vue3中用于响应式编程的API。
ref是用来为一个基本类型的值或对象创建一个响应式引用(Reactive Reference),它将值包装成一个响应式对象,并提供了一些便捷的方法来访问和修改这个响应式对象的值。例如,可以使用ref来创建一个响应式的数字或字符串:
import { ref } from 'vue'
const count = ref(0)
const message = ref('Hello, world!')
上面的代码中,ref分别将数字0和字符串'Hello, world!'包装成了响应式对象,并分别赋值给了count和message变量。可以使用.value属性来访问和修改这些响应式对象的值:
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
console.log(message.value) // 'Hello, world!'
message.value = 'Hello, Vue3!'
console.log(message.value) // 'Hello, Vue3!'
可以看到,通过.value属性,可以访问和修改count和message变量所包装的响应式对象的值。
reactive则是用来创建一个响应式的普通对象(Reactive Object),它会将对象中的所有属性都转化为响应式属性,并且在属性值发生变化时会自动更新视图。例如:
import { reactive } from 'vue'
const user = reactive({
name: 'Alice',
age: 20
})
上面的代码中,reactive将一个普通的JavaScript对象转化为了响应式对象,并将这个响应式对象赋值给了user变量。可以像普通对象一样访问和修改user变量中的属性:
console.log(user.name) // 'Alice'
user.age++
console.log(user.age) // 21
但是,不同于普通对象,当user变量中的属性值发生变化时,相关的视图会自动更新。例如,在Vue3的模板中可以这样使用user变量:
<template>
<div>
<p>Name: {{ user.name }}</p>
<p>Age: {{ user.age }}</p>
</div>
</template>
<script>
import { reactive } from 'vue'
export default {
setup() {
const user = reactive({
name: 'Alice',
age: 20
})
return { user }
}
}
</script>
这样,当user变量中的属性值发生变化时,模板中相应的数据会自动更新,从而实现了响应式更新的效果。
我在写案例的时候发现好像如果使用ref在父子组件中传递属性的时候,如果在页面执行修改操作数据校验会正常执行,但是如果执行新增操作的时候,数据校验就失效了;但是使用reative在父子组件中传递属性却没有这个问题
ref 和 reactive 在父子组件中传递属性时的差异,是因为它们的响应式系统的实现方式不同。
ref 创建的响应式数据是一个包装对象,包含一个值属性和多个方法,当我们访问 ref 创建的响应式数据时,实际上是访问这个包装对象的值属性,而这个值属性是一个普通的数据类型,例如字符串、数字、对象等,而不是一个真正的对象或数组。因此,当我们通过 ref 创建一个响应式对象或数组时,在子组件中访问时需要通过 $refs 属性来访问这个对象或数组,而不是直接在子组件中访问。
当我们在父组件中使用 ref 创建一个响应式对象或数组时,需要注意以下几点:
ref 函数创建响应式对象或数组,并且需要添加 ref 修饰符。props 接收父组件传递的 ref 属性时,需要添加 ref 修饰符,并且可以通过 $refs 属性来访问这个响应式对象或数组。在执行新增操作时,可能会修改响应式对象或数组的长度或者增加新的属性,而这些操作对于 ref 创建的响应式对象或数组来说是不能被监听到的,因此数据校验可能会失效。
相比之下,reactive 创建的响应式对象或数组是一个真正的对象或数组,它的每一个属性或元素都可以被监听到。在父组件中使用 reactive 创建一个响应式对象或数组时,不需要添加任何修饰符,直接通过 props 将它传递给子组件即可,而在子组件中访问时也可以直接使用。
因此,在父子组件中传递响应式属性时,如果需要对响应式对象或数组进行修改操作,建议使用 reactive 来创建响应式对象或数组,并传递给子组件,这样可以保证数据的正确性。
Vue 的响应式系统是基于 Object.defineProperty 这个 API 来实现的。这个 API 只能拦截到对象的 get 和 set 操作,而不能拦截对象属性的直接赋值操作。所以如果直接给 reactive 对象的属性赋值,Vue 是无法感知这个属性的变化的,也就无法更新视图。
而使用 Object.assign() 这个方法,则相当于通过逐个赋值的方式,将一个普通对象的属性复制到 reactive 对象中,这样 Vue 就可以监听到属性的变化,从而能够正常地更新视图。
在普通的对象中,属性的赋值确实是通过 set 操作完成的。但是在 Vue 的响应式系统中,它使用了 ES6 的 Proxy 对象来实现双向绑定,所以如果直接给响应式对象的属性赋值,Vue 无法检测到属性的变化,因为在赋值的时候,并没有触发 Proxy 的 set 操作。而通过 Object.assign() 等方法,Vue 可以监测到对象的变化并进行更新。
<script setup> 中使用的变量都是通过 ref 或 reactive 创建的响应式对象,因此在修改这些响应式对象的属性时,需要使用 Vue 提供的 API 来确保属性修改的响应性。
比如Vue3还提供了一个
reset函数会重置一个响应式对象,恢复其初始值。这个过程不会破坏响应式绑定关系,如果只是恢复初始值可以直接使用它,但注意reset函数不会直接清空对象,而是将对象重置为初始化时的状态。因此,如果您的对象没有提供初始化值,reset函数将不会生效。此外reset位于Vue的其他模块中而不能像ref等方法直接从Vue中导入;
当然如果是具体的整个对象修改还是要用到Object.assign()。
更新响应式数据时需要注意以下几点:
Object.assign() 或者 Vue.set(),否则 Vue 无法检测到属性的变化;Vue.set() 进行修改。例如:
import { ref, reactive, watch } from 'vue'
const count = ref(0)
// 在 setup 函数外部修改 count 不会触发组件的重新渲染
count.value++
// 使用 watch 监听 count 的变化
watch(count, (newCount, oldCount) => {
console.log(`count 从 ${oldCount} 变成了 ${newCount}`)
})
// 使用 Vue 提供的 API 修改 count,会触发组件的重新渲染
count.value = 1
const user = reactive({
name: 'Tom',
age: 18
})
// 对象属性的赋值需要使用 Object.assign 或者 Vue.set
Object.assign(user, { age: 20 })
// 数组元素的修改需要使用数组的内置方法或者 Vue.set
const list = reactive([1, 2, 3])
list[1] = 4 // 不会触发重新渲染
list.splice(1, 1, 4) // 会触发重新渲染
在其他地方,如果需要使用双向绑定,可以使用 v-model,否则可以使用 v-bind 直接赋值。在非 <script setup> 的 <script> 中,也可以通过 Vue 提供的 API,例如 this.$set 和 this.$watch等来实现响应式的属性修改。
其实不是完全没有机会使用直接赋值,只是需要注意在使用响应式对象时,直接赋值可能导致数据变化时视图无法更新的情况。而使用双向绑定或者通过Vue提供的修改方式(如Vue.set()或this.$set())可以保证数据变化时视图的更新。
在一些简单的场景中,直接赋值可能会更加方便,但需要注意数据变化时是否需要更新视图。而在复杂的场景中,建议使用Vue提供的响应式对象,以确保数据和视图的同步更新。
<template>
<div ref="myDiv">Hello World!</div>
</template>
<script>
import { ref, onMounted } from 'vue'
export default {
setup() {
const myDiv = ref(null)
onMounted(() => {
console.log(myDiv.value) // 输出DOM元素
})
return {
myDiv
}
}
}
</script>
在上面的例子中,我们使用ref创建了一个引用myDiv,并将其绑定到模板中的一个<div>;元素上。在onMounted生命周期钩子函数中,我们可以通过访问myDiv.value属性获取到该DOM元素。
Tip: 我们可以通过指定泛型的方式来指定类型推断的结果避免得到一个“any”而没有代码提示的情况,如下:

一个访问组件实例的例子:
<template>
<div>
<button @click="increment">Increment</button>
<p>Count: {{ count }}</p>
</div>
</template>
<script>
import { defineComponent, ref } from 'vue'
export default defineComponent({
setup() {
const count = ref(0)
const increment = () => {
count.value++
}
// 将组件实例保存到 ref 中
const componentInstance = ref(null)
const saveComponentInstance = (instance) => {
componentInstance.value = instance
}
// 在 mounted 生命周期钩子中访问组件实例
onMounted(() => {
console.log(componentInstance.value) // 输出组件实例
})
return {
count,
increment,
saveComponentInstance
}
}
})
</script>
在上面的例子中,我们创建了一个保存组件实例的 ref componentInstance,并在组件的 setup() 函数中定义了一个函数 saveComponentInstance,用来将组件实例保存到 componentInstance 中。在模板中,我们使用 v-bind 指令将 saveComponentInstance 函数传递给子组件,并将子组件的实例作为参数传递给该函数。这样,我们就能够在 mounted 生命周期钩子中访问组件实例了。
Tip: 在Vue中,每个组件实例都是一个对象,其中包含了该组件的状态、属性、生命周期方法等信息。在这个例子中,我们将子组件的实例作为参数传递给
saveComponentInstance函数,实际上就是将这个组件实例对象保存到componentInstanceref 中。因此,instance参数就是子组件的实例对象。
我们都知道Vue3强调的理念就是组件化开发而且也提供了很多内置的组件但我们往往需要使用自定义的组件来满足我们的需求,而ref和reative就能够用来自定义响应式组件,其实前面在组件定义结构一文中的代码案例就已经开始涉及了该部分内容
在Vue 3中,我们可以使用 ref 和 reactive 来定义自定义响应式组件。
下面是一个使用 ref 和 reactive 定义自定义响应式组件的例子:
<template>
<div>
<p>Count: {{ count }}</p>
<button @click="increment">Increment</button>
</div>
</template>
<script>
import { defineComponent, ref, reactive } from 'vue'
export default defineComponent({
setup() {
// 使用 ref 定义 count 变量
const count = ref(0)
// 使用 reactive 定义状态对象
const state = reactive({
message: 'Hello Vue 3!'
})
// 定义 increment 函数来增加 count 变量的值
const increment = () => {
count.value++
}
return {
count,
state,
increment
}
}
})
</script>
在上面的例子中,我们使用 ref 来定义 count 变量,并使用 reactive 来定义一个状态对象 state,该对象包含了一个 message 属性。在组件的 setup() 函数中,我们还定义了一个 increment 函数,该函数用来增加 count 变量的值。
在模板中,我们可以直接使用 count 变量和 state.message 属性,而无需使用 value 属性来访问它们。这是因为在模板中,Vue 3会自动地将 ref 和 reactive 的值解包并响应式地渲染它们的值。
总的来说,使用 ref 和 reactive 可以让我们更方便地定义自定义响应式组件,并可以避免在模板中使用 value 属性的繁琐操作。
Human:
???你说的模板是什么?还有这上面怎么就是自定义组件了呢?啥是自定义组件
Assistant:
这里的模板指的是Vue组件中的模板部分,用来渲染组件的内容。
至于自定义组件,指的是开发者自己编写的组件,通常用来封装一些特定的功能或界面元素。在Vue中,自定义组件可以使用 defineComponent 方法来进行定义。
至于自定义组件,指的是开发者自己编写的组件,通常用来封装一些特定的功能或界面元素。在Vue中,自定义组件可以使用 defineComponent 方法来进行定义。
以下是一个使用 defineComponent 定义的自定义组件示例:
<template>
<div>
<p>Count: {{ count }}</p>
<button @click="increment">Increment</button>
</div>
</template>
<script>
import { defineComponent, ref } from 'vue'
export default defineComponent({
name: 'MyComponent', // 组件名
props: {
msg: String // 组件属性
},
setup(props) {
// 使用 ref 定义 count 变量
const count = ref(0)
// 定义 increment 函数来增加 count 变量的值
const increment = () => {
count.value++
}
return {
count,
increment
}
}
})
</script>
在上面的例子中,我们使用 defineComponent 方法来定义一个名为 MyComponent 的组件,并通过 props 属性定义了一个名为 msg 的组件属性。在组件的 setup() 函数中,我们使用 ref 来定义 count 变量,并定义了一个 increment 函数用来增加 count 变量的值。
最后,我们将组件的模板部分放在 <template> 标签中,用来渲染组件的内容。我们也可以在其他组件中使用该自定义组件,例如:
<template>
<div>
<my-component msg="Hello"></my-component>
</div>
</template>
<script>
import MyComponent from './MyComponent.vue'
export default {
components: {
MyComponent
}
}
</script>
在上面的例子中,我们将 MyComponent 自定义组件引入到了当前组件中,并通过 msg 属性向 MyComponent 传递了一个字符串。