如何在Draft.js中添加React组件并展示?求无dangerouslySetInnerHTML方案
解决方案:Draft.js动态输入富文本并安全展示+合规数据存储
核心需求
- 向Draft.js编辑器动态传入段落、标题、有序列表等富文本内容
- 安全展示样式化结果(优先避免使用
dangerouslySetInnerHTML) - 获取可存入数据库、便于后续复用的标准数据格式
方案一:基于Draft.js原生ContentState构建内容(无dangerouslySetInnerHTML)
直接通过ContentState和ContentBlock构建符合Draft.js规范的富文本结构,编辑器和展示都用Draft.js原生组件,完全避免危险HTML注入。
实现步骤
- 构建包含不同块类型的初始
ContentState(比如标题、段落、有序列表) - 用
react-draft-wysiwyg的Editor组件编辑内容 - 展示时使用只读模式的
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对象**,原因:
- 完全保留富文本的块结构、样式、实体信息
- 可通过
convertFromRaw快速还原为ContentState,支持后续编辑 - 是标准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
相关产品推荐
相关产品推荐

