消息气泡自定义渲染器

IMKit 的 MessageBubble 内置渲染文本/图片/文件/语音/位置/流式/合并转发/系统消息;对 content.type==="custom" 的消息,允许宿主通过 renderers 提供自定义渲染器。渲染器返回 DOM 节点或字符串,并在气泡内安全展示业务卡片(订单、工单、位置共享等)。

概念​

content.type是否可自定义说明
text/image/file/voice/location/stream/combine/recall/system否由内置渲染器负责,行为与协议一致
custom是renderers.custom(message, helpers) 或 Map.get("custom")
未知类型否显示 [自定义消息] 占位
  • 渲染器签名:(message, helpers) => Node | string;抛异常时降级为 [自定义消息],不会破坏列表。
  • helpers 提供 messagePreview(message)、escapeHtml(text)、resolveMediaUrl(url)。
  • renderers 会从 IMKitFacade → ChatView → MessageBubble 透传,推荐在门面统一配置。
  • 自定义卡片仍受气泡布局约束;不要返回覆盖整页的绝对定位节点。
XSS 边界

不要直接拼接 message.content.data 到 innerHTML。使用 helpers.escapeHtml 或 textContent;图片 URL 用 resolveMediaUrl 解析,避免相对路径失效。

前置条件​

  • 已按 快速开始 完成 init 与 connect,页面能收发消息。
  • 使用 @ni-paas/im-web/imkit(浏览器原生 ESM,无第三方依赖);自定义代码只调用 IMKit/IMLib 公开入口。
  • 已引入默认样式:调用 ensureStyles(),或自行加载 IMKit 样式表。
  • 自托管版无“UI 主题商店/付费皮肤”;所有自定义均为本地代码(不适用项)。

步骤​

  1. 约定 custom 数据结构

    例如 {type:"custom",data:{kind:"order",orderId,title,status,url}};发送端与渲染端共用同一约定。

  2. 实现渲染器

    创建 DOM 节点,用 textContent 写入用户输入;链接只允许 http(s) 并加 rel="noopener"。

  3. 注册到门面/聊天视图
    IMKit.setup({renderers}) 或 new ChatView({renderers});Map 或普通对象都支持。
  4. 验证降级

    故意抛错或返回空值,确认气泡显示占位而不是白屏;再验证历史消息与实时消息都能渲染。

参数表​

参数 / 方法类型说明
renderersMap<string,Function> | Record<string,Function>键为 content.type;当前仅 custom 生效
renderer(message, helpers)Function返回 Node 或字符串
helpers.messagePreviewFunction会话列表预览文案
helpers.escapeHtmlFunctionHTML 转义
helpers.resolveMediaUrlFunction补全服务端媒体地址
MessageBubble.props.renderers同上单气泡覆盖(一般不用)

示例​

订单卡片渲染器(安全 DOM 构造)​

JavaScript
import { IMKit, ChatView } from '@ni-paas/im-web/imkit';

const renderers = new Map();

renderers.set('custom', (message, helpers) => {
  const data = (message.content && message.content.data) || {};

  // 只处理自己的业务类型,其余返回占位文本
  if (data.kind !== 'order') {
    return document.createTextNode(data.kind ? '[' + data.kind + ']' : '[自定义消息]');
  }

  const card = document.createElement('a');
  card.className = 'order-card';
  card.href = /^https?:\/\//.test(data.url || '') ? data.url : '#';
  card.target = '_blank';
  card.rel = 'noopener noreferrer';

  const title = document.createElement('div');
  title.className = 'order-card__title';
  // 关键:用户可控文本一律 textContent / escapeHtml
  title.textContent = data.title || '订单';

  const meta = document.createElement('div');
  meta.className = 'order-card__meta';
  meta.textContent = '订单号 ' + (data.orderId || '-') + ' · ' + (data.status || '待处理');

  card.appendChild(title);
  card.appendChild(meta);
  return card;
});

// 方式一:门面统一配置,所有 ChatView 生效
IMKit.setup({ renderers });

// 方式二:单个聊天视图
const chat = new ChatView({
  conversationType: 1,
  targetId: 'bob',
  currentUserId: 'alice',
  renderers,
});

发送端构造同构 custom 消息​

JavaScript
await client.sendMessage({
  conversationType: 1,
  targetId: 'bob',
  content: {
    type: 'custom',
    data: {
      kind: 'order',
      orderId: 'SO-20261027-001',
      title: '秋季外套 × 1',
      status: '已发货',
      url: 'https://shop.example.com/orders/SO-20261027-001',
    },
  },
});

常见问题​

能替换文本/图片气泡吗?​

不能。内置类型没有 renderer 钩子;需要完全自定义时请用 custom 承载,并自行做文本/媒体渲染。

渲染器返回字符串会怎样?​

字符串会作为文本节点插入气泡(不会当 HTML 解析),因此是安全的。

为什么实时消息能渲染,历史消息不行?​

两者都走同一 MessageBubble;检查历史消息的 content 是否缺少 data.kind 或被服务端改写过。

renderers 能异步返回吗?​

不能。渲染器必须同步返回节点/字符串;异步数据请先取回再更新消息内容或自行追加节点。

相关链接​