vueuse的createReusableTemplate函数实现原理

使用vueuse的时候发现里面有一个createReusableTemplate的工具函数很有意思

根据文档的介绍,它的使用方式如下:

开发中也许会经常遇到这种情况

html 复制代码
<template>
  <dialog v-if="showInDialog">
    <!-- something complex -->
  </dialog>
  <div v-else>
    <!-- something complex -->
  </div>
</template>

我们希望代码尽可能的重用,通常情况下我们会将<!-- something complex -->抽取成组件,但是那样做,在抽取出去的组件中,你就没法访问到当前SFC中的各种变量,而只能通过定义propsemits来实现数据传递,这样会有些繁琐

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]
}
相关推荐
quan26316 分钟前
Vue实践篇-02,AI生成代码
前端·javascript·vue.js
GIS之路7 分钟前
GDAL 读取影像元数据
前端
qb1 小时前
vue3.5.18源码-编译-入口
前端·vue.js·架构
小桥风满袖1 小时前
极简三分钟ES6 - 类与继承
前端·javascript
虫无涯1 小时前
【分享】基于百度脑图,并使用Vue二次开发的用例脑图编辑器组件
前端·vue.js·编辑器
子兮曰1 小时前
🚀99% 的前端把 reduce 用成了「高级 for 循环」—— 这 20 个骚操作让你一次看懂真正的「函数式折叠」
前端·javascript·typescript
wifi歪f1 小时前
📦 qiankun微前端接入实战
前端·javascript·面试
小桥风满袖1 小时前
极简三分钟ES6 - Symbol
前端·javascript
子兮曰1 小时前
🚀Map的20个神操作,90%的开发者浪费了它的潜力!最后的致命缺陷让你少熬3天夜!
前端·javascript·ecmascript 6
NewChapter °1 小时前
如何通过 Gitee API 上传文件到指定仓库
前端·vue.js·gitee·uni-app