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

如何在Draft.js中添加React组件并展示?求无dangerouslySetInnerHTML方案

解决方案:Draft.js动态输入富文本并安全展示+合规数据存储

核心需求

  • 向Draft.js编辑器动态传入段落、标题、有序列表等富文本内容
  • 安全展示样式化结果(优先避免使用dangerouslySetInnerHTML)
  • 获取可存入数据库、便于后续复用的标准数据格式

方案一:基于Draft.js原生ContentState构建内容(无dangerouslySetInnerHTML)

直接通过ContentState和ContentBlock构建符合Draft.js规范的富文本结构,编辑器和展示都用Draft.js原生组件,完全避免危险HTML注入。

实现步骤

  1. 构建包含不同块类型的初始ContentState(比如标题、段落、有序列表)
  2. 用react-draft-wysiwyg的Editor组件编辑内容
  3. 展示时使用只读模式的Editor或Draft.js的StaticRenderer

代码示例

import { useState } from 'react';
import { EditorState, ContentState, ContentBlock, convertToRaw, convertFromRaw } from 'draft-js';
import { Editor } from 'react-draft-wysiwyg';
import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css';

// 构建初始富文本ContentState
const buildInitialContentState = () => {
  const blocks = [
    // 一级标题
    new ContentBlock({
      key: 'h1',
      type: 'header-one',
      text: '欢迎使用Draft.js',
      entityRanges: [],
    }),
    // 段落
    new ContentBlock({
      key: 'p1',
      type: 'unstyled',
      text: '这是一段普通文本段落,支持后续编辑样式。',
      entityRanges: [],
    }),
    // 有序列表项1
    new ContentBlock({
      key: 'ol1',
      type: 'ordered-list-item',
      text: '有序列表第一项',
      entityRanges: [],
    }),
    // 有序列表项2
    new ContentBlock({
      key: 'ol2',
      type: 'ordered-list-item',
      text: '有序列表第二项',
      entityRanges: [],
    }),
  ];

  return ContentState.createFromBlockArray(blocks);
};

export const Editors = () => {
  const initialContentState = buildInitialContentState();
  const [editorState, setEditorState] = useState(
    EditorState.createWithContent(initialContentState)
  );

  // 获取可存数据库的JSON格式数据
  const getStorableData = () => {
    const contentState = editorState.getCurrentContent();
    return JSON.stringify(convertToRaw(contentState));
  };

  return (
    <div style={{ width: 600, border: '1px solid #ccc', padding: '10px' }}>
      <h3>编辑器</h3>
      <div style={{ border: '1px solid #eee', marginBottom: '20px' }}>
        <Editor
          editorState={editorState}
          onEditorStateChange={setEditorState}
          toolbar={{
            options: ['inline', 'blockType', 'list', 'textAlign'],
            blockType: {
              options: ['unstyled', 'header-one', 'header-two', 'blockquote'],
            },
            list: {
              options: ['ordered', 'unordered'],
            },
          }}
        />
      </div>

      <h3>样式化展示(无dangerouslySetInnerHTML)</h3>
      <div style={{ border: '1px solid #eee', padding: '10px' }}>
        {/* 使用只读模式的Editor展示 */}
        <Editor
          editorState={editorState}
          readOnly={true}
          toolbarHidden={true}
        />
      </div>

      <button onClick={() => console.log('可存数据库的数据:', getStorableData())}>
        获取存储数据
      </button>
    </div>
  );
};

方案二:将React元素转换为标准ContentState(适配自定义React元素输入)

如果需要传入React元素作为初始内容,需解析元素类型并映射到Draft.js的块类型,避免只提取文本内容。

核心转换逻辑

import { ContentState, ContentBlock } from 'draft-js';

// 映射React元素类型到Draft.js块类型
const elementTypeMap = {
  h1: 'header-one',
  h2: 'header-two',
  p: 'unstyled',
  li: 'ordered-list-item', // 可根据父元素区分有序/无序列表
};

const reactElementToContentState = (elements) => {
  const blocks = [];
  let keyCounter = 0;

  // 递归解析React元素数组/单个元素
  const parseElement = (element) => {
    if (!element || typeof element !== 'object') return;
    
    // 处理文本节点
    if (typeof element === 'string') {
      blocks.push(new ContentBlock({
        key: `block-${keyCounter++}`,
        type: 'unstyled',
        text: element,
        entityRanges: [],
      }));
      return;
    }

    const blockType = elementTypeMap[element.type] || 'unstyled';
    blocks.push(new ContentBlock({
      key: `block-${keyCounter++}`,
      type: blockType,
      text: element.props.children || '',
      entityRanges: [],
    }));

    // 处理子元素(比如嵌套列表)
    if (Array.isArray(element.props.children)) {
      element.props.children.forEach(parseElement);
    } else {
      parseElement(element.props.children);
    }
  };

  parseElement(elements);
  return ContentState.createFromBlockArray(blocks);
};

使用方式

// 传入React元素作为初始内容
const initialReactContent = (
  <>
    <h1>React元素转Draft.js</h1>
    <p>这是通过React元素生成的内容</p>
    <ol>
      <li>列表项1</li>
      <li>列表项2</li>
    </ol>
  </>
);

const initialContentState = reactElementToContentState(initialReactContent);
// 后续编辑器和展示逻辑同方案一

合规数据存储方案

Draft.js官方推荐的存储格式是**convertToRaw生成的JSON对象**,原因:

  1. 完全保留富文本的块结构、样式、实体信息
  2. 可通过convertFromRaw快速还原为ContentState,支持后续编辑
  3. 是标准JSON格式,适配任何数据库存储

存储/读取流程

// 存储:将ContentState转为JSON字符串
const saveToDatabase = () => {
  const contentState = editorState.getCurrentContent();
  const rawContent = convertToRaw(contentState);
  const jsonStr = JSON.stringify(rawContent);
  // 调用API存入数据库
};

// 读取:从数据库获取JSON字符串转为ContentState
const loadFromDatabase = async () => {
  const jsonStr = await fetchContentFromDB(); // 从数据库获取
  const rawContent = JSON.parse(jsonStr);
  const contentState = convertFromRaw(rawContent);
  setEditorState(EditorState.createWithContent(contentState));
};

内容的提问来源于stack exchange,提问作者Maxim Gordiyenko

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 22:53:13