使用vueuse的时候发现里面有一个createReusableTemplate
的工具函数很有意思
根据文档的介绍,它的使用方式如下:
开发中也许会经常遇到这种情况
html
<template>
<dialog v-if="showInDialog">
<!-- something complex -->
</dialog>
<div v-else>
<!-- something complex -->
</div>
</template>
我们希望代码尽可能的重用,通常情况下我们会将<!-- something complex -->
抽取成组件,但是那样做,在抽取出去的组件中,你就没法访问到当前SFC中的各种变量,而只能通过定义props
、emits
来实现数据传递,这样会有些繁琐
createReuseableTemplate
能够在当前组件的template中直接定义一个可重用的片段,就像这样:
html
<script setup>
import { createReusableTemplate } from '@vueuse/core'
const [DefineTemplate, ReuseTemplate] = createReusableTemplate()
</script>
<template>
<DefineTemplate>
<!-- something complex -->
</DefineTemplate>
<dialog v-if="showInDialog">
<ReuseTemplate />
</dialog>
<div v-else>
<ReuseTemplate />
</div>
</template>
在这个示例中,<DefineTemplate>
中写的代码将不会渲染任何东西,而会渲染在<ReuseTemplate />
处
不禁惊叹这是什么黑科技,迫切想知道是怎么实现的,于是研究了一下源码
其实实现原理很简单,稍微拆分一下它的代码并手动实现如下:
html
<script setup>
import { defineComponent } from 'vue';
import { ref } from 'vue'
const msg = ref('Hello World!')
// 首先是可重用的template的定义
// 其实它就是一个Component
const DefineTmpl = defineComponent({
setup(_, { slots }) {
// 拿到默认的插槽,console.log打印一下就能看出来,这玩意实际上就是一个渲染函数
// 而setup方法,是可以返回一个渲染函数的(也就是一个能返回vnode的函数),具体可看vue官方文档:
// https://cn.vuejs.org/guide/extras/render-function.html#declaring-render-function
return () => slots.default()
// 这里甚至可以直接return默认插槽,因为它本身就是一个函数
// return slots.default
}
})
</script>
<template>
<DefineTmpl>
<h1>{{ msg }}</h1>
<input v-model="msg" />
</DefineTmpl>
</template>
以上代码的结果就是:你在DefineTmpl
写的任何东西(其实就是默认插槽的内容)都会被原样渲染出来

其实也就是跟不存在DefineTmpl
几乎没有区别:
html
<script setup>
import { defineComponent } from 'vue';
import { ref } from 'vue'
const msg = ref('Hello World!')
</script>
<template>
<h1>{{ msg }}</h1>
<input v-model="msg" />
</template>
那么DefineTmpl
存在的意义其实就是拿到默认插槽的内容(也就是你在DefineTmpl
里写的任何html),它是一个渲染函数,那就用一个变量来接收它
然后该怎么重用它呢??也就是再定义一个Component,返回DefineTmpl
里获取到的默认插槽内容就行了:
html
<script setup>
import { defineComponent } from 'vue';
import { ref } from 'vue'
const msg = ref('Hello World!')
let renderFn;
const DefineTmpl = defineComponent({
setup(_, { slots }) {
renderFn = slots.default
}
})
const Reuse = defineComponent({
setup() {
if (!renderFn) return console.warn('未定义模板!')
return renderFn
// 或者 return () => renderFn()
}
})
</script>
<template>
<p>DefineTmpl部分(不会渲染任何东西)</p>
<DefineTmpl>
<h1>{{ msg }}</h1>
<input v-model="msg" />
</DefineTmpl>
<h2>================分割线================</h2>
<Reuse />
</template>
然后在页面任意地方写上就可以了,渲染结果:

如果想传入一些自定义的东西怎么办呢,稍微改造一下Reuse:
javascript
const Reuse = defineComponent({
setup(_, { attrs }) {
if (!renderFn) {
console.warn('未定义模板')
}
return () => renderFn({ ...attrs }) // 将attrs作为渲染函数的props传入
}
})
然后在DefineTmpl
部分可以这样写:
html
<template>
<p>DefineTmpl部分(不会渲染任何东西)</p>
<DefineTmpl #default="{ data }">
<h1>{{ msg }}</h1>
<input v-model="msg" />
<p>自定义内容:{{ data }}</p>
</DefineTmpl>
<h2>================分割线================</h2>
<Reuse :data="'我是自定义内容'" />
</template>
渲染结果:

整个封装一下,就是一个简陋版本的createReusableTemplate
了(当然比vueuse中简陋很多,不过差不多核心实现原理也就是这样了)
javascript
function createMyReusableTemplate() {
let renderFn;
const DefineTmpl = defineComponent({
setup(_, { slots }) {
renderFn = slots.default
}
})
const Reuse = defineComponent({
setup(_, { attrs }) {
if (!renderFn) {
console.warn('未定义模板')
}
return () => renderFn({ ...attrs })
}
})
return [DefineTmpl, Reuse]
}