vueuse的createReusableTemplate函数实现原理

使用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]
}
相关推荐
liangshanbo1215几秒前
什么是CSS原子化?它的优劣势是什么?
前端·css·tensorflow·css原子化
.道阻且长.2 分钟前
C++ 11:可变参数模板
前端·c++·算法
IMPYLH6 分钟前
HTML 的 <th> 元素
前端·html
风骏时光牛马7 分钟前
AI浪潮下程序员的职场破局与能力成长思考
前端
ljt27249606619 分钟前
Vue笔记(九)--defineEmits
javascript·vue.js·笔记
IMPYLH13 分钟前
HTML 的 <thead> 元素
前端·html
FungLeo9 小时前
成为全栈·React 管理后台篇·总复盘:从“接口能调通”到“后台值得使用”
前端·react.js·前端框架·项目复盘·成为全栈
Csvn10 小时前
并发模式:让渲染学会排队、插队和让路
前端
可乐鸡翅yeah_10 小时前
新手梳理:M3U8 线上问题,哪些是前端锅,哪些是后端锅
前端·ios·音视频·实时音视频·m3u8·音视频在线播放
Flynt10 小时前
Linear 用 1000 个 PR 换掉 styled-components,我写了 200 个按钮,把这笔账复现了一遍
前端·css·preact