校招生问我在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 函数的应用场景和使用方法。

—转载自:熊的猫

#牛客在线求职答疑中心#
全部评论
学弟学妹,我们这边考虑不?base南京有大量的OD机会,可以聊聊~
点赞 回复 分享
发布于 08-26 21:48 贵州

相关推荐

社畜职场交流圈
点赞 评论 收藏
分享
评论
1
1
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务