Vue2怎么用render渲染html?原理、方法和坑点全解析
在Vue2开发里,经常碰到要动态生成html结构的场景,比如富文本展示、根据数据渲染不同组件布局,但很多同学刚接触时,对render函数咋用、咋安全渲染html一头雾水,今天就从基础到实践,把Vue2 render渲染html的门道拆明白,不管是手动拼结构、渲染富文本,还是做性能优化,都能找到思路~
先搞懂render函数的“底层逻辑”——它和html渲染啥关系?
Vue2的页面渲染,本质是把「虚拟节点(VNode)」变成浏览器能识别的真实DOM,而render函数,Vue 提供的「手动创建VNode」的工具。
平时写模板,Vue会自动把它编译成render函数;但如果场景复杂(比如动态改标签、做高度自定义组件),就需要自己写render函数,简单说,render函数是“编程式”生成VNode的入口,最终这些VNode会渲染成页面上的html元素。
举个直观例子:你想渲染一个带类名的div,里面包个p标签,用render函数写的话,核心是调用h
(createElement的别名)来创建节点:
这里h的第一个参数是标签名(也能是组件),第二个是节点的属性/事件等配置,第三个是子节点,这段代码最终会生成对应VNode,渲染到页面就是:
```html这是内部文本
所以render函数的作用,就是用JS逻辑来描述“最终要渲染成啥html结构”,替代模板的声明式写法,更灵活地控制渲染过程。
想手动拼html结构?render函数基础用法走一波
如果只是简单拼接html标签,用h函数的“三要素”(标签名、属性配置、子节点)就能搞定,但实际开发中,往往要处理类名、事件、动态内容,得把这些细节理清楚。
给元素加属性和事件
比如给div加class、点击事件,配置对象里写对应的字段:
```javascript render(h) { return h('div', { class: { active: true }, // 动态类名 style: { color: 'red' }, // 内联样式 on: { click: this.handleClick } // 事件绑定 }, '点击我') } ```这里class支持对象、数组,style同理;on里面放事件名和回调,和模板里的@click
效果一样。
用JSX写更像“html”的render
纯h函数写复杂结构容易绕晕,这时候可以用JSX语法(需要装babel-plugin-transform-vue-jsx
插件),JSX让你像写html一样写render,比如上面的例子用JSX:
Hello JSX
是不是和模板写法几乎一样?但JSX是“在JS里写html结构”,能直接嵌JS逻辑,比如动态判断标签:
```javascript render() { const Tag = this.isH1 ? 'h1' : 'p'; // 动态选标签名 return这种动态性是template很难做到的,所以复杂场景下JSX+render的组合更顺手。
要渲染富文本类的html字符串?得注意安全和实现细节
开发中常遇到“后端返回带html标签的字符串,要原样渲染”的需求(比如文章内容、富文本编辑器输出),这时候不能直接把字符串当文本渲染,得让浏览器解析成html元素。
render里咋实现“v-html”效果?
v-html是指令,作用是给元素设置innerHTML
,在render函数里,可以通过domPropsInnerHTML
来实现:
这样渲染后,div里的内容会被当作html解析,和v-html效果一致。
必须重视的XSS安全风险
如果直接渲染用户输入的html(比如评论区、自定义内容),攻击者可能注入恶意脚本(比如),这就是XSS攻击,所以一定要对html字符串做过滤!
推荐用DOMPurify
这类库,它能过滤掉危险标签和属性,用法很简单:先装库,然后在渲染前处理字符串:
export default {
data() {
return {
rawHtml: ''
}
},
render(h) {
const cleanHtml = DOMPurify.sanitize(this.rawHtml)
return h('div', {
domProps: { innerHTML: cleanHtml }
})
}
}
<p>这样处理后,危险的script标签会被过滤掉,只保留安全的html结构。</p>
## render渲染html时,性能和复用咋做更高效?
<p>如果页面里有大量重复的html结构(比如长列表、表格),直接用render写可能会有性能问题,这时候得用些优化技巧,减少VNode创建的开销。</p>
### 1. 用“函数式组件”减少实例开销
<p>函数式组件没有自己的data和生命周期,渲染时开销更小,在render里可以这么写:</p>
```javascript
const MyFunctionalComponent = {
functional: true, // 标记为函数式
render(h, context) { // context包含props、children等
return h('div', context.props.text)
}
}
// 父组件里调用
render(h) {
return h('div', [
h(MyFunctionalComponent, { props: { text: '项1' } }),
h(MyFunctionalComponent, { props: { text: '项2' } })
])
}
这种方式适合纯展示、无状态的重复组件,性能比普通组件好很多。
缓存VNode避免重复创建
如果某个VNode结构不变(比如固定的头部导航),可以用computed或者手动缓存,避免每次render都重新创建:
```javascript export default { computed: { cachedHeader() { return h('header', '固定头部') } }, render(h) { return h('div', [ this.cachedHeader, // 直接复用缓存的VNode h('main', '主体内容') ]) } } ```这样头部的VNode只会创建一次,后续渲染时直接复用,减少性能消耗。
拆分组件,复用html结构
把重复的html片段抽成子组件,在render里调用,比如表格的每一行:
```javascript const TableRow = { props: ['data'], render(h) { return h('tr', [ h('td', this.data.name), h('td', this.data.age) ]) } }// 父组件渲染表格
render(h) {
return h('table', [
h('thead', ...),
h('tbody', this.tableData.map(item => h(TableRow, { props: { data: item } })))
])
}
<p>拆分后代码更整洁,而且子组件的逻辑和结构能复用,维护起来更方便。</p>
## render和template渲染html,啥场景选哪个?
<p>很多同学纠结该用template还是render,其实核心看<strong>“结构的动态性”和“逻辑复杂度”</strong>。</p>
### 选template的场景
<p>如果html结构相对固定,逻辑简单(比如几个v-if、v-for),用template更直观,比如普通的列表渲染:</p>
```html
<template>
<ul>
<li v-for="item in list" :key="item.id">{{ item.name }}</li>
</ul>
</template>
这种声明式写法,可读性高,新手也能快速理解。
选render的场景
当需要动态控制标签名、属性、子节点,或者逻辑特别复杂时,render更合适,举几个典型例子:
- 动态组件:根据后端返回的组件名,渲染不同组件(比如表单里的输入框、下拉框、单选框动态切换)。
- 复杂条件渲染:比如根据多个状态组合,决定渲染哪种布局,用render的JS逻辑判断比template的多层v-if更简洁。
- 高度自定义组件:比如写UI库的基础组件(按钮、弹窗),需要精细控制VNode的创建和属性传递。
举个动态组件的例子:后端返回表单字段配置,包含类型(input/select/radio),用render动态生成:
```javascript render(h) { return h('form', this.fields.map(field => { const Comp = field.type === 'input' ? MyInput : field.type === 'select' ? MySelect : MyRadio; return h(Comp, { props: field.props }) })) } ```这种场景下,用template的v-if逐个判断就很繁琐,而render的JS逻辑能高效处理动态性。
实战案例:用render做动态表单生成器
假设需求是:后端返回表单配置(包含字段类型、标签、默认值),前端用Vue2动态生成表单,还要支持双向绑定,这时候用render函数+JS逻辑,能高效实现。
定义表单配置数据
data() { return { formConfig: [ { type: 'input', label: '用户名', prop: 'username', placeholder: '请输入用户名' }, { type: 'select', label: '性别', prop: 'gender', options: ['男', '女'], default: '男' }, { type: 'radio', label: '爱好', prop: 'hobby', options: ['篮球', '足球', '游戏'] } ], formData: {} // 存储表单值 } }
写render函数生成表单
核心思路:循环formConfig,根据type渲染不同组件,同时处理双向绑定(模拟v-model,即value+input事件)。
```javascript render(h) { return h('form', this.formConfig.map(field => { // 选组件:这里假设已经有MyInput、MySelect、MyRadio组件 const Comp = { input: MyInput, select: MySelect, radio: MyRadio }[field.type];// 处理双向绑定:给组件传value,监听input事件更新formData
const props = {
...field,
value: this.formData[field.prop]
};
const on = {
input: (val) => {
this.formData[field.prop] = val;
}
};
// 渲染表单行:包含label和组件
return h('div', [
h('label', field.label),
h(Comp, { props, on })
])
}))
}
### 3. 效果和扩展性
<p>这样写后,不管后端加多少种字段类型,只要前端对应增加组件,render里补充type映射就行,而且双向绑定通过value和input事件实现,和v-model逻辑一致,表单数据也能实时同步到formData里。</p>
<p>如果后续要加“日期选择器”这类新组件,只需要在Comp映射里加一行,render函数不用大改,扩展性拉满~</p>
<p>Vue2里用render渲染html,核心是理解VNode的生成逻辑,灵活运用h函数或JSX来描述结构,同时注意安全和性能优化,不管是简单的标签拼接、富文本渲染,还是复杂的动态组件,掌握render的思路后,就能应对各种场景啦~要是你还有具体场景的疑问,评论区随时聊~</p>
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。