前端面试必考:ref 4 种经典考法与解法

320 阅读2分钟

在前端开发的过程中,我们经常会遇到需要直接操作 DOM 元素的情况。比如,在一个表单中,我们可能希望在页面加载完成后,自动将焦点聚焦到某个输入框上;又或者在一个动画效果里,需要获取某个元素的尺寸来进行一些计算。这时候,ref就派上用场啦,它可以帮助我们轻松拿到 DOM 元素的引用,从而对其进行各种操作。下面,咱们就来详细看看ref的几种常见使用方式。

一、直接使用 ref 获取 DOM 引用

使用场景

假设我们有一个简单的登录表单,当页面加载完成后,我们希望自动将焦点放在用户名输入框上,让用户可以直接输入用户名,提升用户体验。这时候,就可以通过ref直接获取到用户名输入框的 DOM 引用,然后调用其focus()方法来实现自动聚焦。

代码实例

在 Vue 中,我们可以这样写代码:

<template>
  <div>
    <input type="text" ref="usernameInput" placeholder="请输入用户名">
    <input type="password" placeholder="请输入密码">
    <button @click="login">登录</button>
  </div>
</template>
<script>
export default {
  mounted() {
    this.$refs.usernameInput.focus();
  },
  methods: {
    login() {
      // 登录逻辑
    }
  }
}
</script>

在这段代码中,我们在<input>标签上添加了ref="usernameInput",这样在 Vue 实例中,就可以通过this.$refs.usernameInput来获取到这个输入框的 DOM 元素。在mounted钩子函数中,当组件挂载完成后,调用this.$refs.usernameInput.focus(),就实现了自动聚焦的效果。

二、通过父容器的 ref 遍历获取子元素的 DOM 引用

使用场景

想象一下,我们有一个包含多个列表项的无序列表,每个列表项都是一个<li>元素。现在我们需要获取所有<li>元素的高度,并计算它们的总高度。这时候,如果一个个给每个<li>添加ref就会比较麻烦,我们可以通过给父容器<ul>添加ref,然后遍历父容器的子元素来获取所有<li>的 DOM 引用。

代码实例

<template>
  <div>
    <ul ref="list">
      <li>列表项1</li>
      <li>列表项2</li>
      <li>列表项3</li>
    </ul>
    <button @click="calculateTotalHeight">计算总高度</button>
  </div>
</template>
<script>
export default {
  methods: {
    calculateTotalHeight() {
      const list = this.$refs.list;
      let totalHeight = 0;
      for (let i = 0; i < list.children.length; i++) {
        totalHeight += list.children[i].offsetHeight;
      }
      console.log('所有列表项的总高度为:', totalHeight);
    }
  }
}
</script>

在这个例子中,我们给<ul>标签添加了ref="list"。在calculateTotalHeight方法中,首先通过this.$refs.list获取到父容器<ul>的 DOM 引用。然后,利用list.children来遍历所有子元素(也就是<li>元素),通过offsetHeight属性获取每个<li>的高度,并累加到totalHeight变量中,最后输出总高度。

三、使用:ref 将 DOM 引用存储到数组中

使用场景

比如我们正在开发一个图片轮播组件,轮播图中有多个图片,我们希望能够对每个图片进行单独的操作,比如淡入淡出效果。这时候,我们可以使用:ref将每个图片的 DOM 引用存储到一个数组中,方便后续对每个图片进行个性化操作。

代码实例

<template>
  <div>
    <img v-for="(image, index) in images" :key="index" :src="image.src" :ref="setImageRef">
    <button @click="fadeInImages">淡入所有图片</button>
  </div>
</template>
<script>
export default {
  data() {
    return {
      images: [
        { src: 'image1.jpg' },
        { src: 'image2.jpg' },
        { src: 'image3.jpg' }
      ],
      imageRefs: []
    };
  },
  methods: {
    setImageRef(el, index) {
      this.imageRefs[index] = el;
    },
    fadeInImages() {
      this.imageRefs.forEach((image, index) => {
        image.style.opacity = '1';
        // 这里可以添加更多淡入效果的动画代码,比如过渡时间等
      });
    }
  }
}
</script>

在这段代码中,我们使用v-for循环渲染图片。通过:ref="setImageRef",当每个图片渲染完成后,会调用setImageRef方法,将图片的 DOM 元素el存储到imageRefs数组对应的位置。在fadeInImages方法中,遍历imageRefs数组,通过修改style.opacity属性来实现图片的淡入效果。

四、通过子组件 emit 传递 ref

使用场景

假设有一个父组件和一个子组件,子组件中有一个按钮,当点击子组件的按钮时,父组件需要获取子组件中某个 DOM 元素的引用并进行操作。这时候,就可以通过子组件emit事件的方式,将子组件中的ref传递给父组件。

代码实例

子组件(ChildComponent.vue)

<template>
  <div>
    <input type="text" ref="childInput" placeholder="子组件输入框">
    <button @click="sendRefToParent">传递ref给父组件</button>
  </div>
</template>
<script>
export default {
  methods: {
    sendRefToParent() {
      this.$emit('passRef', this.$refs.childInput);
    }
  }
}
</script>

父组件(ParentComponent.vue)

<template>
  <div>
    <ChildComponent @passRef="receiveRef"></ChildComponent>
    <button @click="focusChildInput">聚焦子组件输入框</button>
  </div>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
  components: {
    ChildComponent
  },
  data() {
    return {
      childInputRef: null
    };
  },
  methods: {
    receiveRef(ref) {
      this.childInputRef = ref;
    },
    focusChildInput() {
      if (this.childInputRef) {
        this.childInputRef.focus();
      }
    }
  }
}
</script>

在子组件中,当点击按钮时,调用sendRefToParent方法,通过this.$emit('passRef', this.$refs.childInput)将子组件中输入框的ref发送出去。

在父组件中,通过@passRef="receiveRef"监听这个事件,在receiveRef方法中接收子组件传递过来的ref并存储到childInputRef变量中。当点击父组件的按钮时,在focusChildInput方法中,通过this.childInputRef.focus()实现聚焦子组件输入框的操作。

通过以上几种方式,我们可以灵活运用ref来获取 DOM 引用,满足各种前端开发场景的需求。无论是简单的表单操作,还是复杂的组件交互,ref都能成为我们开发过程中的得力助手。希望这篇文章能帮助你更好地理解和使用ref。