校招生问我在vue中,什么时候该用 render 函数?
前言
最近有一位校招生入职两个多月了,开始参与组件库封装工作,其中就包含了 <overflow-list>
和 <space>
组件,这两个组件本身并不难。
他在实现过程中遇到了一些困惑,特别是对于何时该使用 render 函数感到迷茫。
我建议他先参考其他成熟组件库的实现方式,并结合 render 函数来完成需求。
他又问:那什么时候该用 render 函数?
什么时候该用 render 函数?
在 Vue 中,render
函数是一种用 JavaScript 代码来描述组件渲染内容的方式,它是 Vue 模板编译后的底层实现形式。
简单来说,template
模板最终会被编译成 render
函数,而 render
函数的执行结果会生成虚拟 DOM(VNode),Vue 再根据虚拟 DOM 渲染出真实 DOM。
如果你有如下三种场景,那么你就可以考虑使用 render 函数来实现。
技术大厂跳板机会→前端/后端/测试,待遇还不错,感兴趣可以试试~
需要更高的灵活性和控制权时
模板语法是声明式的,它描述的是“在某种状态下,视图应该是什么样子”。
而 Render 函数是命令式的,它用 JavaScript 的完整能力来“命令”Vue 如何构建视图。
这种根本性的差异使得后者对 VNode 具有极致控制性和灵活性。
比如前面提到的 <overflow-list>
就符合这个场景,<space>
组件比较简单,不过多介绍。
<overflow-list>
折叠列表
- 作用:根据当前的宽度下动态判断能正常展示多少个子组件,无法展示的子组件个数通过一个 tag 来表示被收起
- 用法:
组件内部需要做什么?
从组件的用法上来看,在组件内部需要做下面几件事:
- 获取 默认插槽 中的所有子节点,为不同子节点间设置间隔 margin子节点间要实现两两间隔,可以使用 margin 和 padding 实现,但最好不要直接修改子节点上的样式,应该要为每个子节点包裹一层 <div class="overflow-list-item"> 容器,所有的样式变更都在它上面设置
- 当父容器宽度发生变化时,计算当前最大能够显示子节点的个数,其他的通过 tag 的方式,表示还有剩余未展示 可以通过 ResizeObserver API 实现对当前容器宽度的监听,然后进行计算
- 支持通过 from 设置折叠方向,意味着 自定义折叠元素 的位置会发生变化
- 通过 overflow 插槽 支持外部自定义折叠元素
基于以上要完成的内容,如果直接使用 template
模版是不好实现的,render
函数版本如下:
<script> import { h, onBeforeUnmount, onMounted, ref } from 'vue' export default { name: 'OverflowList', props: { from: { type: String, default: 'end', }, margin: { type: Number, default: 8, }, }, emits: ['change'], setup(props, { slots, emit }) { const showCount = ref(Infinity) let childrenWidths = [] const overflowListRef = ref(null) const initObserve = (el, callback) => { // 保存初始子节点宽度 childrenWidths = Array.from(el.children).map((child) => child.clientWidth) const resizeObserver = new ResizeObserver(callback) resizeObserver.observe(el) onBeforeUnmount(() => resizeObserver.unobserve(el)) } const resizeAction = debounce(() => { const parentWidth = overflowListRef.value.clientWidth let count = 0, currentWidth = 0 for (const childrenWidth of childrenWidths) { currentWidth += childrenWidth if (currentWidth < parentWidth) { count++ } else { break } } showCount.value = count emit('change', showCount.value) }) onMounted(() => { initObserve(overflowListRef.value, resizeAction) }) return () => { console.log('render') // 获取默认插槽子节点集合,作为初始值 let newChildren = slots.default()[0].children.slice() // 计算出折叠元素的个数 const overflowCount = newChildren.length - showCount.value let hasReverse = false // 有折叠元素,才需要 tag if (overflowCount > 0) { const overflowChild = slots.overflow ? slots.overflow()[0] : h('div', { class: 'overflow-tag' }, `+${overflowCount + 1}`) // 定义 overflow 元素位置 if (props.from === 'start') { newChildren.push(overflowChild) // 翻转是为了方便下面的判断 newChildren.reverse() hasReverse = true } else { newChildren.unshift(overflowChild) } } // 根据 showCount 值计算能够显示多少子元素,多余的就不需要渲染 newChildren = newChildren.filter((child, index) => { if (index + 1 > showCount.value) return null return child }) // 处理完成,恢复子节点顺序 if (hasReverse) { newChildren.reverse() } // 为每个子节点包裹一层容器,并设置间距 newChildren = newChildren.map((child, index) => { const margin = (index + 1 < newChildren.length ? props.margin : 0) + 'px' return h( 'div', { class: 'overflow-list-item', style: { marginRight: margin, }, }, child, ) }) // 最终渲染 return h('div', { class: 'overflow-list', ref: overflowListRef }, newChildren) } }, } </script>
效果如下:
在运行时,动态生成模板时
如果你在某个 js、ts 文件中,想要在运行某段逻辑之后要展示某些视图(如 Notification),并且想要自定义其对应的 title、content、footer 时,也可以使用 render 函数来实现。
如下:
import { h } from 'vue' import HandTitle from './HandTitle.vue' import HandContent from './HandContent.vue' function action(data) { Notification.info({ id, showIcon: false, position: 'bottomLeft', closable: false, duration: 0, style: { width: '260px' }, title: () => h(HandTitle, { getName: getShowName(), }), content: () => h(HandContent, { onOk() {}, onRefuse() {}, }) }); }
此处使用 render 函数的好处在于:
- 可以在 JavaScript 逻辑中直接创建组件实例
- 支持动态传递 props 和事件处理函数
- 避免在模板中编写复杂的条件渲染逻辑
需要复杂的条件渲染内容时
当条件渲染逻辑非常复杂,使用模板会导致代码难以维护时,适用场景有:
- 多重嵌套的条件渲染
- 基于复杂业务逻辑的视图渲染
- 需要编程式生成大量相似但略有不同的元素
比如实现最常见 <h1>、<h2>、<h3>、<h4>、<h5>、<h6>
的组件效果。
template 模版需要如下的实现:
<template> <h1 v-if="level === 1">{{ title }}</h1> <h2 v-else-if="level === 2">{{ title }}</h2> <h3 v-else-if="level === 3">{{ title }}</h3> <h4 v-else-if="level === 4">{{ title }}</h4> <h5 v-else-if="level === 5">{{ title }}</h5> <h6 v-else-if="level === 6">{{ title }}</h6> </template>
而 render 函数只需要如下的实现:
<script> import { h } from 'vue' export default { name: 'Title', props: { level: { type: Number, default: 1, }, title: { type: String, default: '', }, }, setup(props, context) { return () => { h(`h${props.level}`, {}, props.title) } }, } </script>
总结
render 函数是 Vue 提供的强大工具,它在以下场景中特别有用:
- 需要高度控制组件渲染逻辑时
- 需要根据运行时数据动态生成组件结构时
- 需要实现复杂的条件渲染时
- 开发可复用组件库时
对于刚接触 Vue 的开发者,建议先从模板语法开始,当遇到模板无法优雅解决的问题时,再考虑使用 render 函数。
随着经验的积累,你会逐渐体会到 render 函数在复杂场景下的价值。
希望这篇文章能帮助你更好地理解 Vue render 函数的应用场景和使用方法。
—转载自:熊的猫
#牛客在线求职答疑中心#