Draft.js装饰器实现超1000字文本标背景时换行失效问题求助
问题根因
Draft.js 的 CompositeDecorator 会逐块处理内容(每次回车换行都会生成一个独立的 ContentBlock),你当前的策略逻辑仅判断单个块的字符长度,没有统计所有块的累计总长度,因此换行后功能异常。
解决方案
核心逻辑为每次编辑器内容变更时,先统计所有块的全局累计偏移量和总字符数,再基于全局值判断每个块内超出1000字的范围,生成对应装饰器。修改后的完整代码如下:
import React, { useState, useRef, useCallback } from "react"; import { Editor, EditorState, ContentState, CompositeDecorator, convertToRaw, } from "draft-js"; const styles = { editor: { border: "1px solid gray", minHeight: "6em", padding: "8px" }, }; // 统计所有块的全局起始偏移量和总字符数 const getBlockOffsets = (contentState) => { const blocks = contentState.getBlocksAsArray(); let currentOffset = 0; const blockOffsetMap = new Map(); blocks.forEach((block) => { blockOffsetMap.set(block.getKey(), currentOffset); // 每个块末尾加1个换行符长度,和提交时的拼接逻辑对齐 currentOffset += block.getText().length + 1; }); return { blockOffsetMap, // 减掉最后多算的1个换行符长度 totalLength: currentOffset > 0 ? currentOffset - 1 : 0 }; }; // 根据全局偏移和总长度生成装饰器 const createDecorator = (blockOffsetMap, totalLength) => { // 超出部分的高亮组件 const OverLengthComponent = ({ children }) => { return <span style={{ backgroundColor: "#fecdd3" }}>{children}</span>; }; // 块处理策略 const blockStrategy = (contentBlock, callback) => { // 总长度未超出直接返回 if (totalLength <= 1000) return; const blockKey = contentBlock.getKey(); const blockStartGlobalOffset = blockOffsetMap.get(blockKey); const blockText = contentBlock.getText(); const blockLength = blockText.length; // 计算当前块内超出1000字的区间 const overStartInBlock = Math.max(1000 - blockStartGlobalOffset, 0); const overEndInBlock = blockLength; if (overStartInBlock < overEndInBlock) { callback(overStartInBlock, overEndInBlock); } }; return new CompositeDecorator([ { strategy: blockStrategy, component: OverLengthComponent, }, ]); }; function TextArea(props) { const [editorState, setEditorState] = useState(() => { // 初始化时计算偏移生成装饰器 const initialContent = ContentState.createFromText(props.value || ""); const { blockOffsetMap, totalLength } = getBlockOffsets(initialContent); const initialDecorator = createDecorator(blockOffsetMap, totalLength); return EditorState.createWithContent(initialContent, initialDecorator); }); const editor = useRef(null); const focusEditor = useCallback(() => { editor.current?.focus(); }, []); const onChange = useCallback((newEditorState) => { const contentState = newEditorState.getCurrentContent(); // 计算最新的全局偏移和总长度 const { blockOffsetMap, totalLength } = getBlockOffsets(contentState); // 生成新装饰器 const newDecorator = createDecorator(blockOffsetMap, totalLength); // 更新editorState的装饰器 const finalEditorState = EditorState.set(newEditorState, { decorator: newDecorator }); // 原有提交逻辑 const rawBlocks = convertToRaw(contentState).blocks; const value = rawBlocks .map((block) => !block.text.trim() ? "\n" : block.text) .join("\n"); setEditorState(finalEditorState); props.formik?.setFieldValue(props.name, value === "\n" ? "" : value); }, [props]); React.useEffect(() => { focusEditor(); }, [focusEditor]); return ( <div style={styles.editor} onClick={focusEditor}> <Editor ref={editor} editorState={editorState} onChange={onChange} placeholder="请输入内容" /> </div> ); } export default TextArea;
内容的提问来源于stack exchange,提问作者stark_sanjeev
相关产品推荐
相关产品推荐

