Vue render函数实战:3个真实项目场景教你动态生成UI(附代码)

对抗样本生成算法-FGSM、I-FGSM、ILCM、PGD 对抗样本生成算法FGSM、I-FGSM、ILCM、PGD 阅读详情

Vue Render函数实战:从模板到JavaScript的进阶之路

如果你已经用Vue开发过几个项目,大概会熟悉那种在模板里写v-if、v-for的日子。模板确实方便,声明式的语法让很多UI逻辑变得直观。但不知道你有没有遇到过这样的情况:需要根据后端返回的复杂数据结构动态生成表单字段,每个字段类型、验证规则、布局方式都不一样;或者要根据用户权限动态显示不同的操作按钮组,权限组合复杂到用模板写出来像一团乱麻;又或者在服务端渲染时,模板的某些特性导致性能瓶颈。这时候,你可能会觉得模板的灵活性有点不够用。

这就是render函数登场的时候了。它不是要取代模板,而是给你一把更锋利的工具,让你在需要完全编程能力时,能够更直接地控制渲染过程。今天我不打算重复那些基础概念,而是直接带你进入三个真实的项目场景,看看render函数如何解决实际问题。我们会从动态表单生成开始,接着处理复杂的权限按钮组,最后探讨服务端渲染中的优化技巧。每个场景我都会提供可以直接运行的代码示例,你可以跟着动手试试。

1. 动态表单生成器:当模板遇到动态数据结构

我在去年接手一个后台管理系统重构时,遇到了一个典型的需求:表单配置完全由后端驱动。不同的业务模块需要不同的表单字段,字段类型(输入框、下拉框、日期选择器、上传组件等)、验证规则、联动关系都是动态的。如果用传统的模板方式,我们可能会写一堆v-if和v-for,代码很快就会变得难以维护。

1.1 为什么模板在这里会吃力?

假设我们有这样一个数据结构来自后端:

const formConfig = [
  {
    type: 'input',
    label: '用户名',
    field: 'username',
    rules: [{ required: true, message: '请输入用户名' }],
    props: { placeholder: '请输入用户名', maxlength: 20 }
  },
  {
    type: 'select',
    label: '用户角色',
    field: 'role',
    options: [
      { label: '管理员', value: 'admin' },
      { label: '编辑', value: 'editor' },
      { label: '查看者', value: 'viewer' }
    ],
    rules: [{ required: true }]
  },
  {
    type: 'date-picker',
    label: '注册时间',
    field: 'registerDate',
    props: { type: 'daterange' }
  }
  // ... 更多字段
]

如果用模板来写,你可能会这样:

<template>
  <el-form :model="formData" :rules="formRules">
    <el-form-item v-if="field.type === 'input'" :label="field.label" :prop="field.field">
      <el-input v-model="formData[field.field]" v-bind="field.props" />
    </el-form-item>
    <el-form-item v-if="field.type === 'select'" :label="field.label" :prop="field.field">
      <el-select v-model="formData[field.field]">
        <el-option v-for="opt in field.options" :key="opt.value" :label="opt.label" :value="opt.value" />
      </el-select>
    </el-form-item>
    <!-- 更多v-if分支 -->
  </el-form>
</template>

注意:当字段类型增多时,这种模板会变得非常冗长,而且每次新增类型都需要修改模板。如果字段之间有复杂的联动关系(比如选择A后显示B,B的值又影响C),模板逻辑会更复杂。

1.2 用Render函数构建表单组件

让我们用render函数来创建一个更灵活的解决方案。首先,我们创建一个DynamicForm组件:

// DynamicForm.vue
export default {
  name: 'DynamicForm',
  props: {
    config: {  // 表单配置数组
      type: Array,
      required: true
    },
    value: {   // 表单数据对象
      type: Object,
      required: true
    }
  },
  
  render(h) {
    // 创建表单项的通用方法
    const createFormItem = (field) => {
      const formItemProps = {
        props: {
          label: field.label,
          prop: field.field,
          rules: field.rules
        }
      }
      
      // 根据字段类型创建对应的表单控件
      const createFormControl = () => {
        switch (field.type) {
          case 'input':
            return h('el-input', {
              props: {
                value: this.value[field.field],
                ...field.props
              },
              on: {
                input: (val) => {
                  this.$emit('input', {
                    ...this.value,
                    [field.field]: val
                  })
                }
              }
            })
            
          case 'select':
            return h('el-select', {
              props: {
                value: this.value[field.field],
                ...field.props
              },
              on: {
                change: (val) => {
                  this.$emit('input', {
                    ...this.value,
                    [field.field]: val
                  })
                }
              }
            }, field.options.map(opt => 
              h('el-option', {
                props: {
                  label: opt.label,
                  value: opt.value
                }
              })
            ))
            
          case 'date-picker':
            return h('el-date-picker', {
              props: {
                value: this.value[field.field],
                ...field.props
              },
              on: {
                change: (val) => {
                  this.$emit('input', {
                    ...this.value,
                    [field.field]: val
                  })
                }
              }
            })
            
          // 可以继续添加更多类型
          default:
            return h('div', `未知字段类型: ${field.type}`)
        }
      }
      
      return h('el-form-item', formItemProps, [createFormControl()])
    }
    
    // 创建整个表单
    return h('el-form', {
      props: {
        model: this.value
      }
    }, this.config.map(field => createFormItem(field)))
  }
}

这个组件的使用方式很简单:

<template>
  <dynamic-form :config="formConfig" v-model="formData" />
</template>

<script>
import DynamicForm from './DynamicForm.vue'

export default {
  components: { DynamicForm },
  data() {
    return {
      formConfig: [...], // 动态配置
      formData: {}      // 表单数据
    }
  }
}
</script>

1.3 处理字段联动和动态验证

render函数的真正威力在于处理复杂逻辑。假设我们需要实现这样的需求:当用户选择"管理员"角色时,显示额外的权限配置字段;当选择其他角色时,隐藏这些字段。

我们可以在render函数中添加逻辑:

render(h) {
  const createFormItem = (field) => {
    // 检查字段是否应该显示(基于联动规则)
    if (field.showIf) {
      const shouldShow = field.showIf(this.value)
      if (!shouldShow) return null  // 返回null表示不渲染该字段
    }
    
    // ... 原有的创建逻辑
  }
  
  // 过滤掉null的字段
  const formItems = this.config
    .map(field => createFormItem(field))
    .filter(item => item !== null)
    
  return h('el-form', {
    props: { model: this.value }
  }, formItems)
}

然后在配置中定义showIf:

const formConfig = [
  {
    type: 'select',
    label: '用户角色',
    field: 'role',
    options: [...]
  },
  {
    type: 'checkbox-group',
    label: '系统权限',
    field: 'permissions',
    showIf: (formData) => formData.role === 'admin',  // 只有管理员才显示
    options: [
      { label: '用户管理', value: 'user_manage' },
      { label: '内容审核', value: 'content_review' },
      { label: '系统设置', value: 'system_config' }
    ]
  }
]

这种方式的优势很明显:

  • 配置驱动:表单结构完全由配置决定,新增字段类型只需扩展createFormControl方法
  • 逻辑集中:所有渲染逻辑都在一个地方,便于维护和调试
  • 动态性强:可以根据运行时数据动态决定渲染什么、怎么渲染

2. 权限控制按钮组:超越v-if/v-show的复杂条件渲染

权限控制是后台系统常见需求。简单的权限用v-if就能搞定,但实际项目中,权限往往很复杂:多个权限组合、权限继承、临时权限等。我见过一个项目,按钮的显示逻辑写了十几行v-if,阅读和维护都很困难。

2.1 复杂权限场景分析

考虑这样一个场景:一个内容管理页面,用户可以对文章进行不同操作,但操作权限由多个因素决定:

操作按钮需要的权限额外条件
编辑content:edit文章状态不是"已发布"
删除content:delete文章作者是自己或拥有content:delete_all权限
发布content:publish文章状态是"待发布"且拥有content:review权限
置顶content:top文章分类允许置顶且不在置顶上限内

如果用模板写,可能会变成这样:

<template>
  <div class="action-buttons">
    <el-button 
      v-if="hasPermission('content:edit') && article.status !== 'published'"
      @click="handleEdit">
      编辑
    </el-button>
    
    <el-button 
      v-if="hasPermission('content:delete') && 
           (article.author === currentUser.id || hasPermission('content:delete_all'))"
      @click="handleDelete">
      删除
    </el-button>
    
    <el-button 
      v-if="hasPermission('content:publish') && 
           article.status === 'pending' &&
           hasPermission('content:review')"
      @click="handlePublish">
      发布
    </el-button>
    
    <el-button 
      v-if="hasPermission('content:top') && 
           article.category.allowTop &&
           !isTopLimitReached"
      @click="handleTop">
      置顶
    </el-button>
  </div>
</template>

这种代码有几个问题:

  1. 可读性差:条件逻辑分散在各个按钮上
  2. 维护困难:权限逻辑变更时需要修改多个地方
  3. 复用性差:其他页面需要类似按钮组时得重写

2.2 用Render函数构建权限按钮组件

让我们用render函数创建一个更优雅的解决方案。首先定义按钮配置:

// button-configs.js
export const getActionButtons = (context) => {
  // context包含当前用户、文章信息、权限检查函数等
  const { user, article, hasPermission, isTopLimitReached } = context
  
  return [
    {
      name: 'edit',
      label: '编辑',
      type: 'primary',
      icon: 'el-icon-edit',
      visible: () => 
        hasPermission('content:edit') && article.status !== 'published',
      disabled: () => article.status === 'archived',
      onClick: () => context.handleEdit()
    },
    {
      name: 'delete',
      label: '删除',
      type: 'danger',
      icon: 'el-icon-delete',
      visible: () => 
        hasPermission('content:delete') && 
        (article.author === user.id || hasPermission('content:delete_all')),
      onClick: () => context.handleDelete()
    },
    {
      name: 'publish',
      label: '发布',
      type: 'success',
      icon: 'el-icon-check',
      visible: () => 
        hasPermission('content:publish') && 
        article.status === 'pending' &&
        hasPermission('content:review'),
      onClick: () => context.handlePublish()
    },
    {
      name: 'top',
      label: '置顶',
      type: 'warning',
      icon: 'el-icon-top',
      visible: () => 
        hasPermission('content:top') && 
        article.category.allowTop &&
        !isTopLimitReached,
      onClick: () => context.handleTop()
    }
  ]
}

现在创建权限按钮组件:

// PermissionButtonGroup.vue
import { getActionButtons } from './button-configs'

export default {
  name: 'PermissionButtonGroup',
  props: {
    user: Object,
    article: Object,
    hasPermission: Function,
    isTopLimitReached: Boolean
  },
  
  methods: {
    handleEdit() {
      // 编辑逻辑
      this.$emit('edit', this.article)
    },
    
    handleDelete() {
      // 删除逻辑
      this.$emit('delete', this.article)
    },
    
    handlePublish() {
      // 发布逻辑
      this.$emit('publish', this.article)
    },
    
    handleTop() {
      // 置顶逻辑
      this.$emit('top', this.article)
    }
  },
  
  render(h) {
    // 获取按钮配置
    const buttonConfigs = getActionButtons({
      user: this.user,
      article: this.article,
      hasPermission: this.hasPermission,
      isTopLimitReached: this.isTopLimitReached,
      handleEdit: this.handleEdit,
      handleDelete: this.handleDelete,
      handlePublish: this.handlePublish,
      handleTop: this.handleTop
    })
    
    // 过滤出可见的按钮
    const visibleButtons = buttonConfigs.filter(config => 
      config.visible ? config.visible() : true
    )
    
    // 创建按钮元素
    const buttonElements = visibleButtons.map(config => {
      const buttonProps = {
        props: {
          type: config.type || 'default',
          icon: config.icon,
          disabled: config.disabled ? config.disabled() : false
        },
        on: {
          click: config.onClick
        },
        style: {
          marginRight: '8px',
          marginBottom: '8px'
        }
      }
      
      return h('el-button', buttonProps, config.label)
    })
    
    // 如果有按钮,渲染按钮组,否则渲染空内容
    if (buttonElements.length > 0) {
      return h('div', {
        class: 'permission-button-group',
        style: {
          display: 'flex',
          flexWrap: 'wrap',
          alignItems: 'center'
        }
      }, buttonElements)
    }
    
    return h('div', { class: 'no-actions' }, '无可用操作')
  }
}

2.3 高级功能:按钮组动态组合与状态管理

render函数让我们可以更灵活地处理按钮状态。比如,我们可以根据当前上下文动态改变按钮的显示方式:

render(h) {
  const buttonConfigs = getActionButtons(this.context)
  
  // 根据权限和状态对按钮进行分组
  const primaryActions = []
  const secondaryActions = []
  const dangerActions = []
  
  buttonConfigs.forEach(config => {
    if (!config.visible || config.visible()) {
      const button = this.createButtonElement(h, config)
      
      // 根据按钮类型分组
      if (config.type === 'danger') {
        dangerActions.push(button)
      } else if (config.type === 'primary') {
        primaryActions.push(button)
      } else {
        secondaryActions.push(button)
      }
    }
  })
  
  // 创建按钮组,可以添加分隔线或其他UI元素
  const actionGroups = []
  
  if (primaryActions.length > 0) {
    actionGroups.push(
      h('div', { class: 'action-group primary' }, primaryActions)
    )
  }
  
  if (secondaryActions.length > 0) {
    actionGroups.push(
      h('div', { class: 'action-group secondary' }, secondaryActions)
    )
  }
  
  if (dangerActions.length > 0) {
    actionGroups.push(
      h('div', { class: 'action-group danger' }, [
        h('el-divider', { props: { direction: 'vertical' } }),
        ...dangerActions
      ])
    )
  }
  
  return h('div', { class: 'permission-button-container' }, actionGroups)
}

这种设计的好处:

  • 关注点分离:按钮的显示逻辑集中在配置中,组件只负责渲染
  • 易于测试:可以单独测试每个按钮的visible逻辑
  • 高度可配置:新增按钮类型或修改权限逻辑只需修改配置
  • UI一致性:按钮的样式和行为保持一致

3. 服务端渲染优化:减少Hydration不匹配

服务端渲染(SSR)是Vue应用性能优化的重要手段,但在复杂场景下,模板的某些特性可能导致客户端激活(hydration)时出现不匹配。render函数在这方面有独特优势。

3.1 SSR中的常见问题

在SSR中,Vue会将组件渲染为静态HTML字符串发送到客户端,然后在客户端"激活"这些静态HTML,使其变成可交互的Vue组件。这个过程要求服务端和客户端渲染的结果完全一致,否则会出现hydration错误。

考虑这个模板:

<template>
  <div>
    <span v-if="showContent">{{ dynamicContent }}</span>
    <button @click="toggleContent">切换</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      showContent: false,
      dynamicContent: ''
    }
  },
  async mounted() {
    // 异步获取内容
    const response = await fetch('/api/content')
    this.dynamicContent = await response.text()
    this.showContent = true
  },
  methods: {
    toggleContent() {
      this.showContent = !this.showContent
    }
  }
}
</script>

这里有个问题:服务端渲染时,mounted钩子不会执行,所以showContent始终是false,dynamicContent是空字符串。但客户端渲染时,mounted会执行,可能改变状态,导致服务端和客户端渲染结果不一致。

3.2 用Render函数优化SSR兼容性

使用render函数,我们可以更好地控制渲染逻辑,确保服务端和客户端的一致性:

// SSROptimizedComponent.vue
export default {
  name: 'SSROptimizedComponent',
  
  // 使用asyncData进行服务端数据预取
  asyncData({ store, route }) {
    // 这个函数只会在服务端和客户端初始化时调用
    return fetch('/api/content')
      .then(res => res.text())
      .then(content => ({ dynamicContent: content }))
  },
  
  data() {
    return {
      // 初始化数据,会在服务端被asyncData的结果替换
      dynamicContent: '',
      showContent: false
    }
  },
  
  // 服务端渲染和客户端激活都会调用
  beforeMount() {
    // 如果有asyncData获取的数据,使用它
    if (this.$ssrContext && this.$ssrContext.asyncData) {
      Object.assign(this.$data, this.$ssrContext.asyncData)
      this.showContent = true
    }
  },
  
  // 只在客户端调用
  mounted() {
    // 如果没有服务端数据,则客户端获取
    if (!this.$ssrContext) {
      this.fetchContent()
    }
  },
  
  methods: {
    async fetchContent() {
      const response = await fetch('/api/content')
      this.dynamicContent = await response.text()
      this.showContent = true
    },
    
    toggleContent() {
      this.showContent = !this.showContent
    }
  },
  
  render(h) {
    // 使用render函数可以更精确地控制渲染逻辑
    const children = []
    
    // 添加按钮
    children.push(
      h('button', {
        on: {
          click: this.toggleContent
        },
        style: {
          marginBottom: '10px'
        }
      }, '切换内容显示')
    )
    
    // 根据showContent决定是否渲染内容
    if (this.showContent && this.dynamicContent) {
      children.push(
        h('div', {
          class: 'content-container',
          style: {
            padding: '20px',
            border: '1px solid #e4e7ed',
            borderRadius: '4px'
          }
        }, [
          h('h3', '动态内容'),
          h('p', this.dynamicContent)
        ])
      )
    } else if (this.showContent && !this.dynamicContent) {
      // 正在加载的状态
      children.push(
        h('div', { class: 'loading' }, '加载中...')
      )
    }
    
    return h('div', { class: 'ssr-optimized' }, children)
  }
}

3.3 高级SSR优化技巧

在复杂的SSR场景中,render函数可以帮助我们实现一些模板难以做到的优化:

条件性SSR渲染

有些组件可能只在客户端需要,比如依赖window或document的组件。我们可以这样处理:

export default {
  name: 'ClientOnlyComponent',
  
  render(h) {
    // 如果是服务端渲染,返回一个占位符
    if (this.$isServer) {
      return h('div', {
        class: 'client-only-placeholder',
        style: {
          width: '100%',
          height: '200px',
          backgroundColor: '#f5f7fa',
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'center'
        }
      }, '此内容仅在客户端显示')
    }
    
    // 客户端渲染真实内容
    return h('div', { class: 'client-only-content' }, [
      h('h3', '客户端专属内容'),
      h('p', '这部分内容依赖浏览器API,只在客户端渲染'),
      // 可以安全地使用window、document等
      h('button', {
        on: {
          click: () => {
            if (window.confirm('确定要执行此操作吗?')) {
              // 客户端逻辑
            }
          }
        }
      }, '客户端操作')
    ])
  },
  
  mounted() {
    // 只在客户端执行的初始化
    if (!this.$isServer) {
      this.initClientOnlyFeature()
    }
  },
  
  methods: {
    initClientOnlyFeature() {
      // 使用浏览器专属API
      window.addEventListener('resize', this.handleResize)
    }
  }
}

优化静态内容

对于完全静态的内容,我们可以使用render函数创建静态VNode,避免不必要的响应式开销:

export default {
  name: 'StaticContentWithDynamicParts',
  
  data() {
    return {
      dynamicValue: ''
    }
  },
  
  created() {
    // 获取动态数据
    this.fetchData()
  },
  
  methods: {
    async fetchData() {
      const res = await fetch('/api/data')
      this.dynamicValue = await res.text()
    }
  },
  
  render(h) {
    // 静态部分 - 不会重新渲染
    const staticHeader = h('header', {
      staticClass: 'page-header'
    }, [
      h('h1', { staticClass: 'title' }, '页面标题'),
      h('p', { staticClass: 'description' }, '这是一个描述文本,内容不会改变')
    ])
    
    // 静态侧边栏
    const staticSidebar = h('aside', {
      staticClass: 'sidebar'
    }, [
      h('nav', [
        h('ul', [
          h('li', [h('a', { attrs: { href: '#' } }, '菜单项1')]),
          h('li', [h('a', { attrs: { href: '#' } }, '菜单项2')]),
          h('li', [h('a', { attrs: { href: '#' } }, '菜单项3')])
        ])
      ])
    ])
    
    // 动态内容部分
    const dynamicContent = h('main', { class: 'content' }, [
      h('h2', '动态内容区域'),
      h('p', this.dynamicValue || '加载中...')
    ])
    
    // 组合所有部分
    return h('div', { class: 'page-container' }, [
      staticHeader,
      h('div', { class: 'page-body' }, [
        staticSidebar,
        dynamicContent
      ])
    ])
  }
}

提示:使用staticClass和staticStyle可以告诉Vue这些属性是静态的,不会改变,从而在更新时跳过比较,提升性能。

4. 性能优化与最佳实践

虽然render函数很强大,但不当使用也可能导致性能问题。这里分享一些我在实际项目中总结的经验。

4.1 避免不必要的重新渲染

render函数每次都会执行,如果里面有复杂的计算或数据转换,可能会影响性能。我们可以使用计算属性或方法来优化:

export default {
  name: 'OptimizedRenderComponent',
  
  props: {
    items: Array,
    filterText: String
  },
  
  computed: {
    // 在计算属性中处理过滤逻辑,避免在render中重复计算
    filteredItems() {
      if (!this.filterText) return this.items
      
      return this.items.filter(item => 
        item.name.toLowerCase().includes(this.filterText.toLowerCase()) ||
        item.description.toLowerCase().includes(this.filterText.toLowerCase())
      )
    },
    
    // 计算属性缓存渲染结果
    itemNodes() {
      return this.filteredItems.map(item => ({
        id: item.id,
        node: this.createItemNode(item)
      }))
    }
  },
  
  methods: {
    createItemNode(item) {
      // 这个方法只会在item变化时调用
      return this.$createElement('div', {
        key: item.id,
        class: 'item-card',
        style: {
          padding: '10px',
          margin: '5px 0',
          border: '1px solid #ddd',
          borderRadius: '4px'
        }
      }, [
        this.$createElement('h4', item.name),
        this.$createElement('p', item.description),
        this.$createElement('span', {
          class: 'item-meta',
          style: {
            fontSize: '12px',
            color: '#666'
          }
        }, `创建于: ${item.createdAt}`)
      ])
    }
  },
  
  render(h) {
    // 使用缓存的节点,避免重复创建
    return h('div', { class: 'item-list' }, 
      this.itemNodes.map(({ node }) => node)
    )
  }
}

4.2 合理使用函数式组件

对于纯展示型组件,可以使用函数式组件获得更好的性能:

// 函数式组件 - 无状态,无实例,性能更好
const FunctionalItem = {
  functional: true,
  props: {
    item: Object,
    onClick: Function
  },
  render(h, context) {
    const { item, onClick } = context.props
    
    return h('div', {
      class: 'functional-item',
      on: {
        click: () => onClick(item)
      },
      style: {
        cursor: 'pointer',
        padding: '10px',
        border: '1px solid #e4e7ed',
        marginBottom: '8px',
        borderRadius: '4px',
        transition: 'all 0.3s',
        '&:hover': {
          borderColor: '#409eff',
          backgroundColor: '#f5f7fa'
        }
      }
    }, [
      h('h4', { style: { margin: '0 0 8px 0' } }, item.name),
      h('p', { 
        style: { 
          margin: 0,
          color: '#606266',
          fontSize: '14px'
        }
      }, item.description)
    ])
  }
}

// 在父组件中使用
export default {
  name: 'ItemList',
  components: {
    FunctionalItem
  },
  props: {
    items: Array
  },
  methods: {
    handleItemClick(item) {
      console.log('点击了:', item)
      this.$emit('item-click', item)
    }
  },
  render(h) {
    return h('div', { class: 'item-list-container' },
      this.items.map(item => 
        h(FunctionalItem, {
          key: item.id,
          props: {
            item,
            onClick: this.handleItemClick
          }
        })
      )
    )
  }
}

4.3 组件拆分与组合

即使使用render函数,也应该遵循单一职责原则。不要在一个render函数中做太多事情:

// 不好的做法:一个巨大的render函数
export default {
  name: 'MonolithicComponent',
  render(h) {
    // 这里可能有几百行代码,处理各种不同的UI部分
    // ...
  }
}

// 好的做法:拆分成多个小组件
const Header = {
  functional: true,
  render(h, { props }) {
    return h('header', { class: 'app-header' }, [
      h('h1', props.title),
      h('p', props.subtitle)
    ])
  }
}

const Sidebar = {
  props: ['menuItems'],
  render(h) {
    return h('aside', { class: 'sidebar' },
      this.menuItems.map(item => 
        h('nav-item', { props: { item } })
      )
    )
  }
}

const Content = {
  props: ['sections'],
  render(h) {
    return h('main', { class: 'content' },
      this.sections.map(section => 
        h('content-section', { props: { section } })
      )
    )
  }
}

// 主组件组合其他组件
export default {
  name: 'WellStructuredComponent',
  components: {
    Header,
    Sidebar,
    Content
  },
  render(h) {
    return h('div', { class: 'app-container' }, [
      h(Header, {
        props: {
          title: '应用标题',
          subtitle: '应用描述'
        }
      }),
      h('div', { class: 'app-body' }, [
        h(Sidebar, {
          props: {
            menuItems: this.menuItems
          }
        }),
        h(Content, {
          props: {
            sections: this.contentSections
          }
        })
      ])
    ])
  }
}

4.4 错误边界与降级处理

在render函数中,我们可以更好地控制错误处理:

export default {
  name: 'ErrorBoundaryComponent',
  
  data() {
    return {
      hasError: false,
      error: null
    }
  },
  
  errorCaptured(err, vm, info) {
    // 捕获子组件的错误
    this.hasError = true
    this.error = { err, info }
    console.error('组件渲染错误:', err, info)
    return false // 阻止错误继续向上传播
  },
  
  methods: {
    resetError() {
      this.hasError = false
      this.error = null
    }
  },
  
  render(h) {
    // 如果有错误,显示错误界面
    if (this.hasError) {
      return h('div', { class: 'error-boundary' }, [
        h('h3', { style: { color: '#f56c6c' } }, '组件渲染出错'),
        h('p', this.error?.info || '未知错误'),
        h('button', {
          on: {
            click: this.resetError
          },
          style: {
            marginTop: '10px',
            padding: '5px 10px'
          }
        }, '重试')
      ])
    }
    
    // 正常渲染子内容
    try {
      return this.$slots.default || h('div', '默认内容')
    } catch (err) {
      // 捕获当前组件的渲染错误
      this.hasError = true
      this.error = { err, info: '渲染过程中发生错误' }
      return h('div', { class: 'render-error' }, '渲染失败')
    }
  }
}

在实际项目中,我发现合理使用render函数确实能解决很多模板难以处理的问题,但也要注意不要过度使用。对于大多数常规UI,模板仍然是更简洁、更易读的选择。render函数更像是工具箱里的专业工具,在需要完全编程能力时才会拿出来使用。

如果你刚开始接触render函数,建议先从简单的组件开始尝试,比如动态图标组件、条件复杂的列表项等。随着经验的积累,你会逐渐找到模板和render函数之间的平衡点,写出既灵活又易维护的Vue组件。

【前端基础知识】最基础的render渲染函数知识,一看就会 【前端基础知识】最基础的render渲染函数知识,一看就会!!! 阅读详情

相关推荐

Vue.js 渲染函数(Render Functions)与 JSX 深度研究

Vue.js 提供了两种主要的方式来声明组件的渲染输出:基于 HTML 的模板语法和基于 JavaScript 的渲染函数(Render Functions)。模板语法是 Vue 最核心和最广为人知的功能,它允许开发者使用类似 HTML 的声明式语法来描述 UI 结构,这种方式直观、易读,并且对于大多数应用场景来说已经足够强大。模板在编译阶段会被 Vue 的编译器转换成渲染函数,这意味着渲染函数是更接近 Vue 底层实现的、更原始和灵活的方式。

步子哥的博客 1284

生成对抗样本的方法|攻击方法

对抗样本 1.Biggio′s attack Biggio[22]等人首先针对传统机器学习分类器(如SVM和三层全连接神经网络)的MNIST手写数字识别数据集生成对抗样本。 它通过优化判别函数来误导分类器。 2. Szegedy′s limited-memory BFGS (L-BFGS) attack Szegedy[8]等人首次证明了可以通过对图像添加小量的人类察觉不到的扰动误导深度神经网络图像分类器做出错误的分类。他们首先尝试求解让神经网络做出误分类的最小扰动的方程。作者认为,深度神经网络所具有的强大

honor 9744

对抗样本生成技术综述

如今,深度学习已被广泛应用于图像分类和图像识别的问题中,取得了令人满意的实际效果,成为许多人工智能应用的关键所在.在对于模型准确率的不断探究中,研究人员在近期提出了“对抗样本”这一概念。

对抗样本生成方法综述(FGSM、BIM\I-FGSM、PGD、JSMA、C&W、DeepFool)

随着深度学习的快速发展,其脆弱性也越来越被关注。本篇文章将介绍一些经典的对抗样本生成方法,并自己编写了相应代码实现。

qq_51171586的博客 1万+

计量经济学tempdisagg的python库:年度时序变月度

计量经济学tempdisagg的python库:年度时序变月度

素质云笔记 1533

企业微信群机器人Webhook配置全攻略:从创建到发送消息的完整流程

本文详细介绍了企业微信群机器人Webhook的配置流程,从创建机器人到获取Webhook地址,再到发送各类消息的完整步骤。通过Python代码示例展示了文本、Markdown等消息类型的发送方法,并提供了性能优化建议和常见问题排查技巧,帮助企业高效实现自动化消息推送。

weixin_29208327的博客 6856

对抗样本生成方法学习总结

对抗样本生成方法学习

maxiaovivi的博客 2262

TensorFlow实战:5步搞定因果推断模型TARNet(附完整代码)

本文提供了一份基于TensorFlow的TARNet因果推断模型实战指南。通过五个清晰步骤,从数据预处理、模型构建到训练评估,详细讲解了如何利用深度模型解决营销评估、疗效分析等场景中的因果效应估计问题,并附有完整代码解决样本不平衡等工程挑战。

python9snake的博客 418

FGSM对抗样本生成算法实现(pytorch版)

FGSM(Fast Gradient Sign Method) 是一种经典的对抗样本生成方法,其核心思想是通过在输入数据的梯度方向上添加扰动,从而生成对抗样本。

入梦风行的博客 3011

Deeplabcut实战:从数据标注到行为分析的全流程解析

本文详细解析了Deeplabcut从数据标注到行为分析的全流程实战技巧,包括项目创建、配置文件定制、智能帧提取、高效标注方法、模型训练优化以及多视角融合等高级应用。通过具体案例和实用参数配置,帮助研究者快速掌握这一行为分析工具,提升实验效率和准确性。

weixin_29057163的博客 247

IEC103协议实战指南:报文解析与传输优化策略

本文深入解析IEC103协议在电力自动化系统中的核心应用,重点拆解其固定帧与可变帧两种报文格式,并详细阐述了主从问答与非平衡传输机制。文章结合实战经验,针对报文解析中的常见问题(如字节序、超时重发)提供了解决方案,并探讨了传输层优化策略,旨在帮助工程师实现更可靠、高效的通信链路。

r2s3t4的博客 542

详解vue渲染函数render的使用

Vue的渲染函数(render)提供了比模板更灵活的编程能力,允许直接生成虚拟DOM。文章通过标题组件对比了模板和render函数的写法差异,详细介绍了createElement函数的三个参数:标签名/组件、属性对象、子节点。重点解析了第二个参数(Option数据对象)的配置项,包括class/style绑定、事件监听、指令等。同时说明了常见指令在render函数中的变化:v-if转为条件判断、v-model需手动实现双向绑定、事件修饰符使用特定前缀处理。render函数更适合需要完全JavaScript编

鹏多多的博客 1909

微信小程序:使用mqtt手机屏幕锁屏后导致中断问题,让mqtt在手机黑屏后依然能保持连接及持续后台运行接收或处理任务,详解微信小程序MQTT在应用退到后台或手机熄屏的情况下自动检测并重连【强制保活】

微信小程序切后台后MQTT连接无法维持,微信小程序MQTT一旦用户锁屏、切换到后台或手机熄屏,连接就会断开,导致无法继续监听和处理业务消息,微信小程序mqtt保活方案,微信小程序屏幕锁屏或息屏导致mqtt连接中断怎么办,如何解决MQTT不能黑屏持续连接的问题,微信小程序应用退到后台 MQTT连接断开问题解决,苹果ios手机,安卓手机,uniapp微信小程序mqtt断开怎么解决,后台应用怎么保活,uni-app中MQTT连接频繁断开如何解决?应用退到后台后MQTT连接断开以及常连接断连的问题,微信小程序、MQ

✅ 精选推荐文章 3026

Vue | Vue深入浅出——Vue中的render函数详解

在编写vue单文件的大多数情况下,我们都是使用template模板来创建HTML。然而在一些条件判断比较复杂的场景下,使用JavaScript去描绘HTML的生成逻辑会显得更加的简洁直观。

青春木鱼的博客 3619

【原创】汇川H5U PLC控制伺服的方法

6、在线调试-进入伺服调试,可以在本软件上进行调试,可以实现原点回归、正向点动、反向电动、使能(这里使能打开时触摸屏上的使能不能打开,会冲突)、复位、停止(如下图所示)。5、在原点返回设置中一般情况下对原点信号、正限位、负限位、原点返回方向、原点返回方式进行设置,其他设置默认不改(如下图所示)。3、在单位换算设置上只需要修改脉冲数和移动量(根据伺服配套导轨上名牌进行设置),其他设置默认不改(如下图所示)。11、双击后右边出现编辑框 在上半部分定义变量(IN、INOUT、OUT、VAR)(如下图所示)。

Qinmingyue13的博客 6674

fasterlio原理及代码框架梳理

Faster-LIO 的优势是在保持同等高精度的前提下,显著提升计算效率,以支持更极端的应用场景(如高速无人机)和更廉价的硬件平台。可以把 Faster-LIO 理解为用一把更快的“空间哈希筛子”(iVox)替换了原来精细的“动态平衡树”(ikd-Tree),从而在精度损失极小的情况下,实现了效率的显著提升。:只在实际有地图点的空间区域动态创建和保留体素单元,形成一个稀疏的体素地图。是关键:将去畸变点云中的每个点,在 iVox 维护的局部地图中搜索近邻(如5个最近点),构建点到面的距离残差。

Studying…… 246

Vue中的render()函数

Vue的render()方法用于手动编写组件渲染逻辑,直接返回虚拟节点(VNode)替代模板语法。该方法通过h()函数创建VNode,适合处理复杂动态UI、组件库开发等场景。在render()中可通过this访问组件实例的数据和方法,实现灵活渲染。虽然功能强大,但简单UI仍推荐使用模板语法以提高可读性。render()是Vue渲染系统的核心能力之一,也是实现自定义渲染的基础。

dd的博客 1997

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

Vue的render函数

回忆Vue的一些基本概念 今天我们学习的目的是了解和学习Vue的render函数。如果想要更好的学习Vue的render函数相关的知识,我们有必要重温一下Vue中的一些基本概念。那么先上一张图,这张图从宏观上展现了Vue整体流程: 从上图中,不难发现一个Vue的应用程序是如何运行起来的,模板通过编译生成AST,再由AST生成Vue的render函数(渲染函数),渲染函数结合数据生成Virtual DOM树,Diff和Patch后生成新的UI。从这张图中,可以接触到Vue的一些主要概念: 模板.

qq_35430000的博客 847

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

Vue 中,使用模板(Template) 和 Render 函数编写组件的区别

当需要将组件渲染到非 DOM 环境(如 Canvas、Native 应用)时,Render 函数更灵活。适合熟悉 React 或偏好 JSX 的开发者,可以利用 JS 的全部能力。当组件的结构需要根据复杂逻辑动态生成时(如动态标签名、动态子组件)。适合大多数静态或简单动态的 UI 布局,如表单、列表、卡片等。需要更细粒度控制渲染逻辑时(如实现高阶组件或抽象功能)。需要手动优化渲染性能时(如避免不必要的子组件重渲染)。示例:根据权限动态渲染按钮,或根据配置生成表单字段。示例:固定结构的页面布局、表单控件。

DarinZanya的博客 1308

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

Vue 中模板语法与 Render 函数的详细对比说明

通过理解这些差异和适用场景,开发者可以更精准地选择适合项目需求的开发方式。模板语法提供简洁高效的标准开发流程,而 Render 函数则为复杂场景和性能优化提供终极解决方案。

把代码调成柠檬味 974

Vue.js实战—Render函数

1.1 什么是Virtual Dom Virtual Dom并不是真正意义上的DOM,而是一个轻量级的JavaScript对象,在状态发生变化时,Virtual Dom会进行Diff运算,来更新只需要被替换的DOM,而不是全部重绘。 与DOM操作相比,Virtual Dom是基于JavaScript计算的,所以开销会小很多。 在Vue.js 2中,Virtual Dom就是通过一种VNode...

u011229269的博客 924

【Vue】Vue render 函数

对Vue中的渲染函数render进行简要的介绍。

cannotbecounted的博客 421

深入解析:Vue 中的 Render 函数、JSX 与 @vitejs/plugin-vue-jsx 实践指南

Vue渲染机制对比:Render函数与JSX 摘要:Vue提供了两种渲染模式:底层render函数和JSX语法。render函数作为核心机制,通过h()创建VNode,具备极致灵活性但代码冗长。JSX作为语法糖,将类HTML结构编译为render函数,兼具开发效率和动态性。二者本质相同,JSX通过Babel编译最终生成render函数,但在复杂场景下JSX更易维护。JSX尤其适合逻辑复杂组件和类型化开发。

Yue 2484

vue render 函数详解

Vue中怎么编写可复用的组件,提到要对Vue的render函数有所了解。可仔细一想,对于Vue的render函数自己只是看了官方的一些介绍,并未深入一点去了解这方面的知识。为了更好的学习后续的知识,又折回来了解Vue中的render函数,这一切主要都是为了后续能更好的学习Vue的知识。 回忆Vue的一些基本概念 今天我们学习的目的是了解和学习Vue的render函数。如果想要更好的学习Vue的render函数相关的知识,我们有必要重温一下Vue中的一些基本概念。那么先上一张图,这张图从宏观上展现了Vue

winxin_58206976的博客 1294

render vue 添加类_Vue.js render函数那些事儿

大多时候,我会使用template, vue单文件去渲染组件。虽然知道Vue中有个render函数,但却很少在项目中去主动使用它。使用最多的地方是在使用一些UI框架的时候,比如iview table中的按钮操作,会使用到render函数。另外平时在阅读一些Vue UI框架源码的时候,也时常能遇到使用render函数的地方,这也激发了自己研究学习的欲望。如果你也感兴趣,那就继续阅读吧。在本文中,会有...

weixin_42512933的博客 1288

vue3中的return,h,render

函数是 Vue 的核心 API,用于定义组件的渲染逻辑。创建的 VNode,Vue 会将其转换为真实 DOM。是 "hyperscript" 的缩写),用于创建。只能返回对象或函数,不能混合返回。它是构建渲染函数的核心函数。

m0_51429350的博客 725
上一篇: R语言autoReg包实战:5分钟搞定医学研究三线表与森林图(附完整代码)
下一篇: 避坑指南:STM32F103C8T6低功耗调试时为什么总卡在10mA?5个硬件工程师容易忽略的细节
Python
博客等级 码龄26年 1973粉丝 1567原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值