You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将Token列表转换为React元素?附Markdown渲染场景示例

基于Token列表生成React元素的简易Markdown渲染实现

需求背景

需要实现一个仅支持行内格式的简易Markdown渲染器(无块级元素、代码块),将解析后的Token列表转换为React.createElement调用的嵌套元素结构,而非HTML字符串。

示例场景

复杂嵌套示例

Markdown原文:

Foo *italic **bold-italic** italic* **bold** Blah

解析出的Token序列:

{ type: text, content: "Foo " }
{ type: em_open }
{ type: text, content: "italic " }
{ type: strong_open}
{ type: text, content: "bold-italic" }
{ type: strong_close}
{ type: text, content: " italic" }
{ type: em_close }
{ type: text, content: " " }
{ type: strong_open}
{ type: text, content: "bold" }
{ type: strong_close}
{ type: text, content: " Blah" }

需生成嵌套的React元素,对应多层createElement调用。

基础加粗示例

Markdown原文:

**foo**

Token序列:

{ type: strong_open }
{ type: text, content: "foo" }
{ type: strong_close }

期望生成:

createElement(Text, { fontWeight: "bold" }, [ "foo" ]);

斜体+加粗嵌套示例

Markdown原文:

***foo***

Token序列:

{ type: em_open }
{ type: strong_open }
{ type: text, content: "foo" }
{ type: strong_close }
{ type: em_close }

期望生成:

createElement(Text, { fontStyle: "italic" }, [ 
  createElement(Text, { fontWeight: "bold" }, [ "foo" ])
]);

核心实现方案:栈(Stack)模式

处理嵌套格式最经典的方式就是用栈结构,因为Token的开/关顺序天然符合栈的“后进先出”特性。具体步骤如下:

1. 定义Token与React元素的映射

先把em_open、strong_open这类开标记,对应到React组件和样式props:

const openTokenMap = {
  em_open: { component: 'Text', props: { fontStyle: 'italic' } },
  strong_open: { component: 'Text', props: { fontWeight: 'bold' } }
};

2. 初始化栈结构

栈中每个元素保存当前层级的组件信息、props和子元素数组,初始时放入一个根容器(比如React Fragment):

const stack = [{ component: 'Fragment', props: {}, children: [] }];

3. 遍历Token处理逻辑

逐个遍历Token,根据类型执行不同操作:

  • 开标记(*_open):根据映射创建新的上下文对象,压入栈顶,作为当前活跃的元素容器。
  • 关标记(*_close):弹出栈顶的上下文,将其转换为createElement调用后,加入到新栈顶元素的子数组中。
  • 文本Token:若内容非空,直接将文本内容加入当前栈顶元素的子数组;空文本直接跳过。

完整代码实现

import { createElement, Fragment } from 'react';

const renderMarkdownTokens = (tokens) => {
  const openTokenMap = {
    em_open: { component: 'Text', props: { fontStyle: 'italic' } },
    strong_open: { component: 'Text', props: { fontWeight: 'bold' } }
  };

  const stack = [{ component: Fragment, props: {}, children: [] }];

  for (const token of tokens) {
    const currentCtx = stack[stack.length - 1];

    if (openTokenMap[token.type]) {
      // 处理开标记,压入新上下文
      const { component, props } = openTokenMap[token.type];
      stack.push({ component, props, children: [] });
    } else if (token.type.endsWith('_close')) {
      // 处理关标记,弹出并添加到父级子元素
      if (stack.length > 1) {
        const closedCtx = stack.pop();
        const parentCtx = stack[stack.length - 1];
        parentCtx.children.push(
          createElement(closedCtx.component, closedCtx.props, closedCtx.children)
        );
      }
    } else if (token.type === 'text') {
      // 处理文本,跳过空内容
      if (token.content !== '') {
        currentCtx.children.push(token.content);
      }
    }
  }

  // 根元素处理:如果只有一个子元素直接返回,否则用Fragment包裹
  const rootChildren = stack[0].children;
  return rootChildren.length === 1 ? rootChildren[0] : createElement(Fragment, {}, rootChildren);
};

空文本Token处理

在遍历文本Token时,直接判断token.content是否为空,为空则不加入子元素数组,避免生成无效的空文本节点。

方案优势

  • 天然适配嵌套格式的Token顺序,逻辑直观易懂;
  • 扩展性强:新增格式(如下划线、删除线)只需在openTokenMap中添加对应映射即可;
  • 自动处理多层嵌套,无需手动维护层级关系。

内容的提问来源于stack exchange,提问作者Archimedes Trajano

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 20:35:18