vue3 组合式api ref使用详解,以及使用ref绑定dom时,ref.value为undefind的注意事项

5 阅读1分钟

ref基本用法为使用ref()包裹数值,赋给变量,完成响应式


    Count: {{ count }}
    Increment

import { ref } from 'vue';

const count = ref(0);

const increment = () => {
  console.log(count.value)
  count.value++;
};

此变量变成ref对象,具有响应式,在template中可以直接使用,无需加.value,但是在setup中必须使用.value才能取到里面的值

ref也可以包裹对象,如下


    Name: {{ person.name }}
    Age: {{ person.age }}
    Update Person

import { ref, isReactive } from "vue";

// 使用 ref 包裹对象,使对象的属性变得响应式
const person = ref({
  name: "John Doe",
  age: 25,
});

// 定义一个函数来更新对象的属性
const updatePerson = () => {
  person.value.name = "Jane Smith";
  person.value.age = 30;
};

// 验证响应式对象本质是reactive对象,打印结果为true
console.log(isReactive(person.value));

包裹对象时,本质上是把普通对象,变成reactive对象放入ref对象上的value值中,可以通过isReactive函数验证ref的value值,结果为true

相较于reactive包裹对象完成响应式,ref更具优势,因为当改变reactive中保存的对象的内存地址时(也就是换一个全新的对象),由于vue3的响应式是通过创建代理对象,在代理对象的set方法中放入该实例的渲染函数的方法构建的,而替换整个对象,而不是修改对象内部的属性值,其并不会调用set方法,所以页面并不会响应式。

但是如果使用ref包裹对象,那么此对象仅仅是ref对象的一个value属性,所以更改内存地址时,也仅仅是修改ref对象的value属性,依然会调用代理对象的set方法,完成响应式dom更新

我们依旧使用ref函数定义一个变量,但其中并不包裹任何值,并且在我们想要获取的dom上,写上属性ref="我们定义的这个变量",那么这个dom就被保存进这个变量了,代码如下


    This is a paragraph.

import { ref, onMounted } from 'vue';

// 创建一个 ref 用于 DOM 元素
const paragraph = ref(null);

// 在组件挂载时可以访问 DOM 元素
onMounted(() => {
  console.log(paragraph.value); // 打印出 <p> 元素
});

我们也可以使用ref绑定子组件获取子组件实例 (注:语法糖形式下,组件实例内容默认私有,如果想被父组件获取,需要通过defineExpose方法将属性暴露出去),如下

//子组件

    Child component

import { ref } from 'vue';

const childMethod = () => {
  console.log('Child method called');
};

defineExpose({ childMethod });
//父组件

    Call Child Method

import { ref, onMounted } from 'vue';
import ChildComponent from './ChildComponent.vue';

// 创建一个 ref 用于子组件实例
const childRef = ref(null);

// 定义一个函数来调用子组件的方法
const callChildMethod = () => {
  if (childRef.value) {
    childRef.value.childMethod();
  }
};

// 在组件挂载时可以访问子组件实例
onMounted(() => {
  console.log(childRef.value); // 打印出子组件实例
});

但无论是绑定dom,还是绑定子组件实例我们一定要注意以下两点!

第一,我们在setup中定义ref,绑定的dom或者是子组件,仅仅是他们的代理对象,此时真实的dom和子组件并没有被渲染出来,所以无法在setup中对其进行操作,此时获取其value值是undefind。

我们通常不会在setup中执行dom或组件实例操作,但是我们经常会在setup中进行传递数据操作(如传递函数参数),此时我们一定要注意,不可以传递value值,应该传递ref对象本身,因为此时真实的dom和子组件还没有被渲染出来,value值为undefind

当需要定义一些不是因为某些事件执行的函数,而是直接执行的函数时,我们可以把这些dom和子组件实例的操作函数放入onMounted()生命周期中,当然还有一个比较巧妙的方法,是通过settimeout函数包裹,此函数被推入队列中,会在真实dom和子组件实例渲染后执行。也可以通过v-bind和ref结合,也就是:ref来实现,这个后面说。

第二,当我们不使用setup语法糖形式时(

通过这种方式绑定的每个dom或是子组件,都会将自己实例的代理对象作为函数参数传入其中,由于不是传入ref对象,而是代理对象本身,所以可以直接调用dom或子组件实例的方法,不需要.value。与此同时,这个函数也会在真实dom和子组件渲染完成后调用,无需考虑其是否为undefind。

在setup生命周期函数中直接操作dom目前有三种方法,分别是 :ref,onMounted和settimeout,其中他们三个的执行顺序是 :ref >> onMounted >> settimeout,所以想直接操作dom或子组件实例时,我们最好选用:ref,它不仅最快运行,而且更加符合逻辑