跳转到内容

Vue 组件内的模板复用

刀刀
0字
0分钟
2026/8/16

场景

模块多处复用,代码如下:

vue
<template>
  <div class="container">
    <div class="title">标题</div>
    <div class="content">内容</div>
  </div>
  <div>111</div>
  <div class="container">
    <div class="title">标题</div>
    <div class="content">内容</div>
  </div>
  <p>2222</p>
  <div>333</div>
  <div class="container">
    <div class="title">标题</div>
    <div class="content">内容</div>
  </div>
</template>

.container 内容重复出现,如何不独立封装为子组件,实现模块内复用?

解决方案

组件即函数

Vue中,组件即函数,<template></template> 后续会被转化为函数,可通过函数参数实现复用。

创建一个对象,包含 setup 函数,从第二个参数解构出 slots 插槽,通过 slots.default() 获取插槽内容:

js
let render
const DefineTemplate = {
  setup(_, { slots }) {
    return () => {
      render = slots.default
      return render()
    }
  },
}

const UseTemplate = () => render()
vue
<template>
  <DefineTemplate>
    <div class="container">
      <div class="title">标题</div>
      <div class="content">内容</div>
    </div>
  </DefineTemplate>
  <div>111</div>
  <UseTemplate></UseTemplate>
  <p>2222</p>
  <div>333</div>
  <UseTemplate></UseTemplate>
</template>

利用插槽特性,从 DefineTemplatesetup 中获取相同模块并保存到 render,调用 render() 即可复用。

打印 slot.default() 的内容:

打印slot.default()的内容

Vue中,虚拟组件是函数,存储类型 type、内容 children 等信息,通过虚拟组件的 render 函数渲染真实组件。

抽离封装复用

抽离为公共函数,每个需要的 .vue 文件都能使用:

js
export const createReusableTemplate() { 
  let render;
  const DefineTemplate = {
    setup(_, { slots }) {
      return () => {
        render = slots.default;
        return render(); 
      }
    }
  }

  const UseTemplate = () => render()
  return [DefineTemplate, UseTemplate] 
} 
vue
<script setup>
import { createReusableTemplate } from './utils.js'
const [DefineTemplate, UseTemplate] = createReusableTemplate() 
</script>

<template>
  <DefineTemplate>
    <div class="container">
      <div class="title">标题</div>
      <div class="content">内容</div>
    </div>
  </DefineTemplate>
  <UseTemplate></UseTemplate>
  <div>111</div>
  <UseTemplate></UseTemplate>
  <p>2222</p>
  <div>333</div>
  <UseTemplate></UseTemplate>
</template>

DefineTemplate 负责保存插槽内的组件,UseTemplate 负责渲染。返回数组,外部通过数组解构自定义命名。

注意

DefineTemplatesetup 去掉了副作用,不再 return 默认插槽内容,仅保存到 render 变量中。

支持传参数

使用时要传参数,如何解决?例如:

vue
<script setup>
import { createReusableTemplate } from './utils.js'
const [DefineTemplate, UseTemplate] = createReusableTemplate()
</script>

<template>
  <DefineTemplate>
    <div class="container">
      <div class="title">标题</div>
      <div class="content">内容</div>
    </div>
  </DefineTemplate>
  <UseTemplate title="header" @foo="console.log"></UseTemplate>
  <div>111</div>
  <UseTemplate title="main"></UseTemplate>
  <p>2222</p>
  <div>333</div>
  <UseTemplate title="footer"></UseTemplate>
</template>

组件 UseTemplateUseTemplate 函数,传的变量和事件可在形参接收:

js
export const createReusableTemplate() {
  let render;
  const DefineTemplate = {
    setup(_, { slots }) {
      return () => {
        render = slots.default();
      }
    }
  }

  const UseTemplate = (props) => { 
    console.log(props) // { title: 'header', foo: ƒ }
    return render() 
  } 
  return [DefineTemplate, UseTemplate]
}

实现传参的步骤:

  1. UseTemplate 接收到参数后,传给 render
  2. render 接收参数,相当于默认插槽 slots.default 接收参数
  3. DefineTemplate 通过 v-slot="data" 从默认插槽获取数据
  4. 使用

最终代码:

js
export const createReusableTemplate() {
  let render;
  const DefineTemplate = {
    setup(_, { slots }) {
      return () => {
        render = slots.default();
      }
    }
  }

  const UseTemplate = (props) => render(props) 
  return [DefineTemplate, UseTemplate]
}
vue
<script setup>
import { createReusableTemplate } from './utils.js'
const [DefineTemplate, UseTemplate] = createReusableTemplate()
</script>

<template>
  <DefineTemplate>
    <div class="container" v-slot="{ title, onFoo }">
      <div class="title">{{ title }}</div>
      <div class="content" @click="onFoo(111)">内容</div>
    </div>
  </DefineTemplate>
  <UseTemplate title="header" @foo="console.log"></UseTemplate>
  <div>111</div>
  <UseTemplate title="main"></UseTemplate>
  <p>2222</p>
  <div>333</div>
  <UseTemplate title="footer"></UseTemplate>
</template>

注意

方法内部会被转化为 on + 首字母大写的形式。

动手实操

跳转预览:点击跳转

贡献者

The avatar of contributor named as duyidao duyidao
The avatar of contributor named as 刀刀 刀刀
The avatar of contributor named as v_duyilin v_duyilin

页面历史

刀刀博客累计访客 人;文档累计访问量共