富文本编辑器中如何正确插入并编辑表格?

一云碧 阅读 4

我用 React + Draft.js 做富文本编辑器,现在想支持表格功能。但插入表格后,光标无法正常进入单元格,也不能用方向键在单元格间移动,感觉像是没被识别为可编辑区域。

我试过用原生 contentEditable 的 div 模拟,但和 Draft.js 的 EditorState 冲突了。下面是我目前插入表格的代码:

const insertTable = () => {
  const table = '<table><tr><td></td><td></td></tr></table>';
  const contentState = Modifier.replaceText(
    editorState.getCurrentContent(),
    editorState.getSelection(),
    table,
    null
  );
  setEditorState(EditorState.push(editorState, contentState, 'insert-table'));
};

这样插入的表格完全不能交互,有啥靠谱的方案吗?是不是得用自定义 block render?

我来解答 赞 1 收藏
二维码
手机扫码查看
1 条解答
诸葛荣荣
你这个问题很典型,Draft.js 本身是基于 block 的编辑器,你用 Modifier.replaceText 插入 HTML 字符串,它只会当成纯文本处理,不会解析成真正的 DOM 结构,更别谈交互了。

核心问题在于:Draft.js 的 block 是独立的编辑单元,你想在 block 内部再搞出可编辑的单元格,必须用自定义 block render。

标准做法是这样的:

1. 先定义一个自定义 block type,比如叫 table

2. 用 blockRendererFn 捕获这个 block,返回一个自定义组件

// 定义表格 block
const BLOCK_TYPE = 'table';

// 在 editorState 中插入表格 block
const insertTable = () => {
const contentState = editorState.getCurrentContent();
const selection = editorState.getSelection();

// 创建一个 atomic block
const newBlock = ContentState.createBlock({
type: BLOCK_TYPE,
text: ' ',
data: fromJS({
rows: [
[{ content: '' }, { content: '' }],
[{ content: '' }, { content: '' }]
]
})
});

const newContentState = Modifier.replaceWithFragment(
contentState,
selection,
List([newBlock])
);

setEditorState(EditorState.push(editorState, newContentState, 'insert-table'));
};

// blockRendererFn
const blockRenderer = (block) => {
if (block.getType() === BLOCK_TYPE) {
return {
component: TableBlock,
editable: false, // 表格整体不参与 Draft.js 的编辑
props: {
onChange: (newData) => {
// 将修改后的数据同步回 block
const newContentState = Modifier.mergeIn(
editorState.getCurrentContent(),
[block.getKey()],
{ data: newData }
);
setEditorState(EditorState.push(editorState, newContentState, 'update-table-data'));
}
}
};
}
return null;
};


3. 表格组件内部用原生 contentEditable 实现编辑

const TableBlock = (props) => {
const { data, onChange } = props.blockProps;
const [tableData, setTableData] = useState(data);

const handleCellChange = (rowIndex, colIndex, value) => {
const newData = {
...tableData,
rows: tableData.rows.map((row, ri) =>
row.map((cell, ci) =>
ri === rowIndex && ci === colIndex ? { ...cell, content: value } : cell
)
)
};
setTableData(newData);
onChange(newData);
};

return (
<table className="rich-table">
<tbody>
{tableData.rows.map((row, rowIndex) => (
<tr key={rowIndex}>
{row.map((cell, colIndex) => (
<td key={colIndex}>
<div
contentEditable
suppressContentEditableWarning
onBlur={(e) => handleCellChange(rowIndex, colIndex, e.target.innerText)}
>
{cell.content}
</div>
</td>
))}
</tr>
))}
</tbody>
</table>
);
};


几个关键点:

- 表格 block 的 editable 要设为 false,否则 Draft.js 会尝试把它当成普通文本编辑,导致行为混乱
- 表格内部用独立的 contentEditable div,每个单元格独立处理
- 数据通过 onChange 回写到 Draft.js 的 block data 中,保持状态同步
- 方向键问题需要在表格组件的 keyDown 处理里自己实现,或者用键盘事件冒泡

如果你还需要在表格里支持更复杂的操作(比如合并单元格),原理是一样的,数据结构设计好就行。
点赞
2026-03-13 21:07