ref主要是引用 DOM 节点或者引用组件实例,说白了就是 Vue 本身不需要你操作 DOM,Vue 底层已经帮你做好了数据的绑定,所有视图的更新都来源于 viewModel 去做双向数据绑定。但是,某些时候你又不得不去获取 DOM 节点(或者 DOM 的某些信息),或者你要操作组件的实例,这个时候你就可以使用ref去引用!

ref 引用 DOM 元素

当我们给 DOM 元素设置ref属性的时候,我们就可以在this.$refs中拿到 DOM 节点的引用。

  1. <template>
  2. <p>
  3. <label for="">Username:</label>
  4. <input type="text" ref="myRef" name="" id="" />
  5. </p>
  6. </template>
  7. <script>
  8. export default{
  9. mounted(){
  10. console.log(this.$refs);
  11. }
  12. }
  13. </script>

image.png

但其实,组件实例在beforeCreate阶段就已经能拿到$refs对象,只不过是个空对象,直到mounted组件挂载后,$refs对象才能拿到 DOM 的引用!

  1. <template>
  2. <p>
  3. <label for="">Username:</label>
  4. <input type="text" ref="myRef" name="" id="" />
  5. </p>
  6. </template>
  7. <script>
  8. export default {
  9. beforeCreate() {
  10. console.log(this.$refs);
  11. },
  12. created() {
  13. console.log(this.$refs);
  14. },
  15. beforeMount() {
  16. console.log(this.$refs);
  17. },
  18. mounted() {
  19. console.log(this.$refs);
  20. }
  21. };
  22. </script>

image.png
这主要是因为 DOM 节点还没有挂载,所以你并不能拿到引用。

如果你尝试修改$refs对象的属性,你讲看到警告信息,因为$refs对象属性是只读的。

  1. export default {
  2. mounted() {
  3. const oLink = document.createElement("a");
  4. oLink.innerText = "Google";
  5. oLink.href = "https://www.google.com";
  6. // 不可进行修改
  7. this.$refs.myRef = oLink;
  8. }
  9. };

image.png

因为ref是在 DOM 渲染后才进行挂载,所以当你通过v-if快速切换ref的时候,你将看到非理想的的结果:

  1. <template>
  2. <p v-if="showWhat === 'input'">
  3. <label for="">Username:</label>
  4. <input type="text" ref="myRef" name="" id="" />
  5. </p>
  6. <p v-else-if="showWhat === 'link'">
  7. <a href="https://www.google.com" ref="myRef">Google</a>
  8. </p>
  9. </template>
  10. <script>
  11. export default {
  12. data() {
  13. return {
  14. showWhat: "link"
  15. };
  16. },
  17. mounted() {
  18. console.log(this.$refs);
  19. this.showWhat = "input";
  20. console.log(this.$refs);
  21. }
  22. };
  23. </script>

image.png
你将看到两个$refs.myRef都为a

如果想要避免这样的情况发生,你应该使用setTimeout进行延迟获取,或者使用 Vue 的nextTick来回调获取:

  1. export default {
  2. data() {
  3. return {
  4. showWhat: "link"
  5. };
  6. },
  7. mounted() {
  8. console.log(this.$refs);
  9. this.showWhat = "input";
  10. setTimeout(() => {
  11. console.log(this.$refs);
  12. });
  13. // 或者
  14. console.log(this.$refs);
  15. this.showWhat = "input";
  16. this.$nextTick(() => {
  17. console.log(this.$refs);
  18. });
  19. }
  20. };

当我们获取到 DOM 节点的时候,我们就可以对 DOM 进行操作或获取 DOM 的信息:

  1. export default {
  2. mounted() {
  3. this.$refs.myRef.focus();
  4. this.$refs.myRef.offsetHeight;
  5. }
  6. };

ref 引用组件实例

ref还可以用来引用组件实例:

  1. <my-test ref="myTestRef"></my-test>

ref作用在组件上的时候,是可以拿到组件的实例,而 DOM 则是渲染后的 DOM 节点。

和 DOM 一样,ref引用组件也是在渲染完成后才能获取到。

  1. export default {
  2. beforeCreate() {
  3. console.log(this.$refs);
  4. },
  5. created() {
  6. console.log(this.$refs);
  7. },
  8. mounted() {
  9. console.log(this.$refs);
  10. }
  11. };

image.png

另外,ref本身并不是响应式的,所以不要在模版、计算属性(因为非响应式不会触发computed重新计算)中进行使用,也不要尝试去修改ref的值(ref只提供你获取 DOM 或组件,并不是让你去操作的!)

  1. export default{
  2. mounted(){
  3. // 不要这么做
  4. this.$refs.myTestRef.count++;
  5. }
  6. }

通过ref可以获取到组件内部的属性或者方法,但大多数情况下,你应该首先使用标准的propsemit接口来实现父子组件交互!

  1. <template>
  2. <my-test ref="myTestRef"></my-test>
  3. </template>
  4. <script>
  5. import MyTest from "./components/MyTest/index.vue";
  6. export default {
  7. components: {
  8. MyTest
  9. },
  10. methods: {
  11. handleClick() {
  12. // 可以获取到 myTestRef.count 属性
  13. console.log(this.$refs.myTestRef.count);
  14. // 可以调用 myTestRef.addCount 方法
  15. this.$refs.myTestRef.addCount();
  16. }
  17. }
  18. };
  19. </script>
  1. <template>
  2. <button @click="handleLog">点击</button>
  3. </template>
  4. <script>
  5. export default {
  6. name: "Test",
  7. data() {
  8. return {
  9. count: 0
  10. };
  11. },
  12. methods: {
  13. handleLog() {
  14. this.addCount();
  15. },
  16. addCount() {
  17. this.count++;
  18. console.log(this.count)
  19. }
  20. }
  21. };
  22. </script>

v-for 中的模板引用

当在 v-for 中使用模板引用时,相应的引用中包含的值是一个数组:

  1. <template>
  2. <ul>
  3. <li v-for="item in 10" ref="items">
  4. {{ item }}
  5. </li>
  6. </ul>
  7. </template>
  8. <script>
  9. export default {
  10. mounted() {
  11. console.log(this.$refs.items)
  12. }
  13. }
  14. </script>

image.png :::warning ⚠️ 注意
应该注意的是,ref 数组并不保证与源数组相同的顺序。 :::