vue3自定义渲染内容如何当参数传递

前端

在实现一些高阶组件的时候我们往往需要传入自定义的渲染内容。一般实现使用插槽实现即可,但是在这些场景下:

  • 组件是通过函数调用
  • 子组件是通过参数动态创建

这里列出常用的4种解决方案:

  • slot
  • vhtml
  • h函数
  • jsx

1.slot

一般要实现自定义渲染内容分发,用官方的slot插槽实现即可。

缺点:对于动态渲染的组件需要加很多判断,对于通过函数创建的组件使用不友好。

Parent.vue

html 复制代码
<template>
    <h1>slot:</h1>
   <div class="box">
       <h2>parent</h2>
       <Child #default="scoped">
           <h2>child</h2>
           <span> 【这是Parent传入的内容】 </span>
           <span>  【{{scoped.subMsg}}】</span>
       </Child>
   </div>
</template>
<script setup lang="ts">
import Child from './Child.vue'
</script>

Child.vue

html 复制代码
<template>
 <div class="box"> 
   <slot subMsg='【这是child的内容】'></slot>  <!-- 这里渲染父组件传入的内容 -->
 </div>
</template> 

2.vhtml

用vhtml,其实也是可以实现,但是有被注入攻击风险,而且只支持纯html的标签。

Parent.vue

js 复制代码
<template>
 <h1>vhtml:</h1>
 <div class="box"> 
    <h2>parent</h2>
   <Child :htmlString="'<h2>child</h2><p style=color:red;>【这是Parent传入的内容】</p>'" />
 </div>
</template>

<script setup>
import Child from './Child.vue'
</script>
</script>

Child.vue

html 复制代码
<template> 
 <div class="box"> 
   <div v-html="htmlString"></div> 
 </div>
</template>

<script setup>
defineProps({
 htmlString: {
   type: String,
   required: true
 }
})
</script>

3.h函数

vue种的slot其实都是语法糖,最终还是得转化成vnode渲染。所以直接通过h函数(createVNode),也是能实现

这里我们也可以通过h函数的实现了解vue中插槽实现逻辑。

Parent.vue

html 复制代码
<template>
 <h1>h + < component is >: </h1> 
 <div class="box">
   <h2>parent</h2>
   <Child :renderContent="myRenderFunction" />
 </div>

</template>

<script setup>
import Child from './Child.vue'
import { h } from 'vue'
const myRenderFunction = (propsFromChild) => {
 return h(
   'div',
   { style: { color: 'green', border: '1px solid #ccc', padding: '10px' } },
   [
     h('h2', `child`),
      h('span', `【这是Parent传入的内容】`),
      h('span', `${propsFromChild.message}`)
   ]
 )
}
</script>

由于vue3支持多种定义组件的方式,可以参照这篇文章:juejin.cn/post/754594...

这里组件通过

  • template + <component is>
  • option API + render
  • composition API + return fn

3.1 template + <component is>

传统开发,我们使用template模板 + <component is> 动态加载组件

注意:由于vnode在有template的情况下不能直接渲染vnode,所以依赖 <component is>渲染vnode

ChildTemplateComponentIs.vue

html 复制代码
<template>
 <div class="box">
   <component :is="renderContent({ message: '【这是child的内容】' })" />
 </div>
</template>

<script setup>
const props = defineProps({
 renderContent: {
   type: Object,
   required: true
 }
})
</script>

3.2 option API + render

通过option API 和直接render方式导出,省去template定义

ChildOptionAPIRender.vue

html 复制代码
<script lang="ts">
import {defineComponent } from 'vue'

export default defineComponent({
  name: 'HelloRender',
  props: {
    renderContent: {
      type: Object,
      required: true
    }
  },
  render() {
    return this.renderContent({ message: '【这是child的内容】' })
  }
})
</script>

3.3 composition API + return fn

通过composition API 和 return fn,也等同于上面的option API+render的方式

ChildOptionAPIRender.vue

html 复制代码
<script>
import { defineComponent} from 'vue'

export default defineComponent({ 
   props: {
    renderContent: {
      type: Object,
      required: true
    }
  },
  setup(props) { 
    return () =>
     props.renderContent({ message: '【这是child的内容】' })
  }
})
</script>

4.jsx

当然上面的h函数使用起来还是不方便,我们可以利用jsx让代码变得更加直观,jsx的参考

Parent.vue

html 复制代码
<template>
 <h1>jsx </h1>
 <div class="box">
   <h2>parent</h2>
   <ChildTemplateComponentIs :renderContent="myJsxFn" />
   <ChildOptionAPIRender :renderContent="myJsxFn" />
   <ChildCompositionAPIReturnFn :renderContent="myJsxFn" />
 </div>

</template>

<script setup lang="tsx">
import ChildTemplateComponentIs from './ChildTemplateComponentIs.vue'
import ChildOptionAPIRender from './ChildOptionAPIRender.vue'
import ChildCompositionAPIReturnFn from './ChildCompositionAPIReturnFn.vue'

// 定义一个 JSX 元素作为内容
const myJsxFn = (scope:Object) =>  (
 <div style={{ color: 'blue', border: '1px dashed', padding: '10px' }}>
   <span>【这是Parent传入的内容】</span>
   <span>{scope.message}</span>
 </div>
)
</script>

同样这里也列出三种实现的方式

  • template + <component is>
  • option API + render
  • composition API + return fn

4.1 template + <component is>

传统的template模板 + <component is> 动态加载组件

ChildTemplateComponentIs.vue

html 复制代码
<template>
 <div class="box">
   <component :is="renderContent({ message: '【这是child的内容】(template + <component is> 方式) ' })" />
 </div>
</template>

<script setup>
const props = defineProps({
 renderContent: {
   type: Object,
   required: true
 }
})
</script>

4.2 option API + render

通过option API 和直接render方式导出,省去template定义

ChildOptionAPIRender.vue

html 复制代码
<script lang="ts">
import {defineComponent } from 'vue'

export default defineComponent({
  name: 'HelloRender',
  props: {
    renderContent: {
      type: Object,
      required: true
    }
  },
  render() {
    return this.renderContent({ message: '【这是child的内容】(option API + render 方式)' })
  }
})
</script>

4.3 composition API + return fn

通过composition API 和 return fn,也等同于上面的option API+render的方式

ChildOptionAPIRender.vue

html 复制代码
<script>
import { defineComponent} from 'vue'

export default defineComponent({ 
   props: {
    renderContent: {
      type: Object,
      required: true
    }
  },
  setup(props) { 
    return () =>
     props.renderContent({ message: '【这是child的内容】(composition API + return fn 方式)' })
  }
})
</script>

效果图

5.实战

5.1 权限的动态组件

可以通过h函数先判断权限,然后再确定是否渲染,没有则直接返回null不渲染。

auth.tsx

js 复制代码
import { defineComponent, Fragment } from 'vue'
import { hasAuth } from '@/xxx/utils'

export default defineComponent({
  name: 'Auth',
  props: {
    value: {
      type: undefined,
      default: []
    }
  },
  setup(props, { slots }) {
    return () => {
      if (!slots) return null
      return hasAuth(props.value) ? <Fragment>{slots.default?.()}</Fragment> : null
    }
  }
})

5.2 动态组件创建

有时候我们需要根据字符串动态创建组件

ComponentA.vue

js 复制代码
<template>
  <div style="color: red;">
    <h3>我是 ComponentA</h3>
  </div>
</template>

ComponentB.vue

js 复制代码
<template>
  <div style="color: red;">
    <h3>我是 ComponentB</h3>
  </div>
</template>

test.vue

html 复制代码
<script lang="ts">
import { defineComponent, h, resolveComponent } from 'vue'
export default defineComponent({
  name: 'DynamicRenderer',
  props: {
    name: {
      type: String,
      required: true, // 必须传入组件名,如 'ComponentA'
    },
  },
  setup(props) {
    // 根据 props.name 解析出组件对象
    const targetComponent = resolveComponent(props.name)
    return () => h(targetComponent)
  },
})
</script>

5.3 动态表格支持自定义渲染内容

一般动态表格我们都是配置化方式实现(参考juejin.cn/post/739805... ),这时候扩展就很适合使用jsx传入。

js 复制代码
    //定义列格式
let columns = 
[ 
  { label: '城市', property: 'city', render:'component',  type: 'select',  options: cityOptions , validate:true },  
  { label: '介绍', property: 'info', render:'component',width:120,  type: 'input'},   
]
//定义表格数据
let tableData = [
{"id":2,"city":1,"area":1,"username":3,"account":4,"age":5,"createtime":"2024-01-01",birthday:"202405",score:5,info:'人品好',readonlyKeyList:[]},
{"id":3,"city":2,"area":3,"username":3,"account":4,"age":5,"createtime":"2024-01-02",birthday:"202406",score:-2,info:'孤僻',ignoreList:['birthday']}, 
]

//组件使用
  <DTable ref="tableRef" rowKey="id"  :tableData="tableData" :columns="columns"></DTable>   
  

在上面代码如果我们希望 { label: '介绍', property: 'info', render:'component',width:120, type: 'input'}这里自带的input 改成自己想要定制化input,改成el-input-number,就可以利用 jsx,多传入一个参数实现 伪代码如下:

js 复制代码
  { label: '介绍', property: 'info', render:'component',width:120,
    type: 'input',render: (model) => (<el-input-number v-model="model" :min="1" :max="10" />)} 

6.扩展

render

render是 Vue 3 提供的一个 ​底层 API ,用于 ​手动渲染一个 Vue 应用实例(即 Vue 根组件)到指定的 DOM 容器中

js 复制代码
//弹出全屏遮罩的 点击验证码 页面
import { createVNode, render, VNode } from 'vue'
const clickCaptcha = () => {
  let vnode: VNode | null = createVNode(CaptchaComponent)
  render(vnode, document.body) // 直接渲染到 body上面
  vnode = null
}

resolveComponent

在 Vue 3 中,resolveComponent是一个 ​编译时辅助函数​ 通过组件的名称(字符串)解析出对应的组件定义(Component)对象。

由于h函数第一个参数 默认是不支持通过字符串直接匹配到具体的组件

js 复制代码
    h('el-date-picker') // 这里虽然已经全局引入'el-date-picker',但是这样也还是会报错。
    h(resolveComponent('el-date-picker')) // 通过resolveComponent方法就能动态去查询组件定义,并返回实际的组件对象。

7.总结

在封装高阶组件的时候,当slot无法实现或实现起来比较麻烦的时候,我们就可以借用h函数或jsx作为参数,灵活创建或定制自己的渲染内容。

8.源码地址

github.com/mjsong07/cu...

相关推荐
布兰妮甜10 小时前
封装Element UI中el-table表格为可配置列公用组件
vue.js·ui·el-table·前端开发·element-ui
年年测试10 小时前
Browser Use 浏览器自动化 Agent:让浏览器自动为你工作
前端·数据库·自动化
维维酱10 小时前
React Fiber 架构与渲染流程
前端·react.js
gitboyzcf10 小时前
基于Taro4最新版微信小程序、H5的多端开发简单模板
前端·vue.js·taro
姓王者11 小时前
解决Tauri2.x拖拽事件问题
前端
williamdsy11 小时前
实战复盘:pnpm Monorepo 中的 Nuxt 依赖地狱——Unhead 升级引发的连锁血案
vue.js·pnpm
冲!!11 小时前
vue3存储/获取本地或会话存储,封装存储工具,结合pina使用存储
前端·javascript·vue.js
BUG创建者11 小时前
uniapp vue页面传参到webview.nvue页面的html或者另一vue中
vue.js·uni-app·html