Vue组件封装小技巧:教你自动传递属性、插槽和获取内部元素

504 阅读1分钟

一、如何让属性自动传递?

假设我们封装了一个带样式的输入框组件,但直接使用时发现输入没反应:

<!-- 父组件 -->
<Child></Child>

<!-- 子组件 -->
<el-input></el-input> <!-- 这里缺少v-model绑定 -->

解决方案:用$attrs自动收快递

就像快递员会把包裹自动送到你家,$attrs能自动接收父组件传来的所有属性:

<!-- 父组件 -->
<Child v-model="keyword"></Child>

<!-- 子组件 -->
<el-input v-bind="$attrs"></el-input> <!-- 自动接收所有属性 -->

小实验:看看$attrs怎么工作

<!-- 父组件传三个属性 -->
<Child a="苹果" b="香蕉" c="橙子"></Child>

<!-- 子组件只收a和b -->
<script>
props: ['a', 'b'], // 这里只收a和b
setup(props, { attrs }) {
  console.log(attrs) // 这里会显示{c: "橙子"}
}
</script>

二、如何让插槽也能穿透?

当需要在输入框里添加图标时,原组件的插槽要怎么穿透?

解决方法:用$slots转发插槽

就像转交快递包裹一样,把插槽原样转发:

<!-- 父组件 -->
<Child>
  <template #prepend>🔍 搜索</template>
</Child>

<!-- 子组件 -->
<el-input>
  <!-- 循环转发所有插槽 -->
  <template v-for="(_, name) in $slots" #[name]>
    <slot :name="name"></slot>
  </template>
</el-input>

需要传数据怎么办?(作用域插槽)

当插槽里需要显示子组件内部的数据时:

<!-- 父组件 -->
<Child>
  <template #prepend="{ message }">{{ message }}</template>
</Child>

<!-- 子组件 -->
<script>
setup() {
  const message = ref('请输入内容')
  return { message }
}
</script>

<el-input>
  <template v-for="(_, name) in $slots" #[name]="slotData">
    <slot :name="name" v-bind="slotData"></slot>
  </template>
</el-input>

三、如何直接操作内部元素?

想通过ref让输入框自动聚焦怎么办?

解决方法:搭桥接驳

<!-- 父组件 -->
<Child ref="myInput"></Child>

<script>
// 点击按钮时触发聚焦
myInput.value.inputRef.focus()
</script>

<!-- 子组件 -->
<el-input ref="inputRef"></el-input>

<script>
// 暴露内部输入框的引用
defineExpose({
  inputRef
})
</script>

demo

/** 父组件 */
<template>
  <Child ref="childRef" v-model="value">
    <template #prepend="{ msg }"> 测试插槽传递 {{ msg }}</template>
  </Child>
</template>

<script setup>
import Child from './components/child.vue'
import { ref, onMounted } from 'vue'

const value = ref()
const childRef = ref()
onMounted(() => {
  // 调用子组件中的el-input的focus方法
  childRef.value.inputRef.focus()
})
</script>

/** 子组件 */
<template>
  <div class="my-input">
    <el-input ref="inputRef" v-bind="$attrs">
      <!-- name就是插槽名称 -->
      <template v-for="(_, name) in $slots" #[name]>
        <slot :name="name" v-bind="message"></slot>
      </template>
    </el-input>
  </div>
</template>

<script setup>
import { ref } from 'vue'
defineProps(() => ['a', 'b'])
const message = ref({ msg: '作用域插槽传值' })
const inputRef = ref()

// 暴露出el-input实例
defineExpose({
  inputRef
})
</script>


常见问题解答

  1. 为什么用$attrs还要加v-bind?
    就像拆快递需要先签收,v-bind相当于把收到的属性拆包给内部组件
  2. 循环插槽会降低性能吗?
    就像转发快递不会改变包裹内容,Vue内部有优化机制,不会影响性能
  3. 直接暴露内部元素安全吗?
    建议用defineExpose只暴露需要的方法,就像只给快递员开小门取件

通过这三个技巧,你可以像搭积木一样轻松封装组件,还能保持原有组件的全部功能!