Element UI $confirm 把 h 函数渲染成源代码字符串的真相
某次需要在后台管理系统的确认弹窗里展示一段多行文本——用户名 + 若干条记录拼在一起,中间要有换行。顺手在 options 里加了个 message: h => h('div', ...) 以为万事大吉。打开一看,弹窗里赫然印着:
function message(h) { return h('div', { style: 'white-space: pre-wrap' }, fullText); }
函数源代码原封不动被当成文本显示出来了。
现象
调用代码如下:
this.$confirm(fullText, '存在同名记录', {
confirmButtonText: '继续',
cancelButtonText: '取消',
type: 'warning',
message: h => h('div', { style: 'white-space: pre-wrap' }, fullText)
})预期:弹窗 body 渲染出带样式的多行内容。
实际:弹窗 body 显示 function message(h) { return h('div', ...) } 这一串字面量。
根因
Element UI 2.x 有两种调用 MessageBox 的方式,签名完全不同:
配置式($msgbox):接受一个 options 对象,message 字段可以是字符串、VNode 或 h => VNode 函数:
$msgbox(options: ElMessageBoxOptions)
// options.message: string | VNode | ((h: CreateElement) => VNode)快捷式($confirm / $alert / $prompt):前两个位置参数已经固定为 message 和 title:
$confirm(message: string | VNode | ((h) => VNode), title: string, options?: ElMessageBoxOptions)在快捷式里,第一个位置参数才是真正的 message。如果同时在 options.message 里再传一个 h 函数,Element UI 优先用第一参数(已经是字符串 fullText),而 options.message 里的函数对象在 options merge 阶段某个渲染路径上被 toString() 打印出来。结果就是把函数源码显示在弹窗里。
$confirm / $alert / $prompt 是 $msgbox 的快捷封装,签名第一位已经是 message。在 options 里再传 message: h => ... 是无效重复——行为不可靠,可能被 toString 打印为函数源代码字符串。
正解
方案 A(首选)— dangerouslyUseHTMLString + 手动 escape
简单可控,适合绝大多数多行/带格式场景:
const escapeHtml = (s) =>
String(s == null ? '' : s).replace(
/[&<>"']/g,
c => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c])
)
const html = `姓名「${escapeHtml(name)}」已有 <b>${count}</b> 条记录:<br/><br/>
${list.map(item => escapeHtml(item.phone)).join('<br/>')}<br/><br/>
是否继续?`
await this.$confirm(html, '存在同名记录', {
confirmButtonText: '继续',
cancelButtonText: '取消',
type: 'warning',
dangerouslyUseHTMLString: true // ← 关键
})escapeHtml 不可省略——用户输入字段如果含 <script> 或 <img onerror> 就是 XSS。
方案 B — $msgbox 配置式 + h 函数
如果确实需要 VNode 渲染,改用 $msgbox,让 message 字段在正确的位置发挥作用:
await this.$msgbox({
title: '存在同名记录',
message: h => h('div', { style: 'white-space: pre-wrap' }, fullText),
showCancelButton: true, // $msgbox 默认不显示取消,需手动开
confirmButtonText: '继续',
cancelButtonText: '取消',
type: 'warning'
})方案 C — $confirm 第一参数直接传 h 函数
await this.$confirm(
h => h('div', { style: 'white-space: pre-wrap' }, fullText),
'存在同名记录',
{ confirmButtonText: '继续', cancelButtonText: '取消', type: 'warning' }
)Element UI 会调用这个箭头函数并传入内部的 $createElement,正确渲染 VNode。
需要 VNode/HTML 内容时:优先用方案 A(dangerouslyUseHTMLString + escapeHtml),最稳;需要 h 函数则用 $msgbox 配置式或把 h 函数作为 $confirm 第一参数传入,不要放在 options.message 里。
一句话外卖
Element UI 快捷式
$confirm(message, title, options)的第一位参数就是 message,options.message是配置式专属字段——两者签名不同,混用会把函数对象 toString 打印为源码。