我使用 React JS 创建了一个带有行号的简单文本编辑器。它应该只编辑纯文本(即不需要富文本编辑)。文本输入字段用
contentEditable
div 表示。打字时,我面临三个挑战:
map
为每一行创建一个。 ✅React.useRef
来确保我的文本输入不会重新渲染(并且光标位置不会丢失)。 ✅不幸的是,目前这并没有达到我想要的效果。我似乎不知道如何获取渲染的 div 的高度,然后将它们设置为行号。我尝试使用
useEffect
来运行 after DOM 渲染,但我无法找到如何“挂钩”到 div 数组并计算它们的高度。
请参阅这个工作 JSFiddle 示例。不幸的是我无法让它在 StackOverflow 代码工具中工作。为了方便,我还是把相关代码贴在下面:
const initialText=`Nature's first green is gold,
Her hardest hue to hold.
Her early leaf's a flower;
But only so an hour.
Then leaf subsides to leaf.
So Eden sank to grief,
So dawn does down to day.
Nothing gold can stay.`
function App () {
const [lines, setLines] = React.useState(initialText.split("\n"));
let text = React.useRef(initialText)
React.useEffect(() => {
console.log('Rendered!')
// TODO: Update the height of each line-number to match the real-lines
});
function handleInput(e) {
console.log('Edited!')
setLines(e.target.textContent.split("\n"));
}
return (
<div className='container'>
<div className='margin'>
{lines.map((line,i)=>(<div>{(1+i).toString()}</div>))}
</div>
<div className='editor-container'>
<div onInput={handleInput} contentEditable='plaintext-only' className='editor' suppressContentEditableWarning={true}>
{text.current}
</div>
<div className='editor' style={{visibility:'hidden'}}>
{lines.map(line=>(<div>{line}</div>))}
</div>
</div>
</div>
)
}
const root = ReactDOM.createRoot(document.getElementById("app"))
root.render(<App/>)
.container {
display: flex;
justify-content: space-between;
}
.margin {
text-align: right;
flex-shrink: 0;
max-width: 1em;
}
.margin > div {
width: 100%;
}
.editor-container {
margin-left: 0.5em;
flex-grow: 1;
position: relative;
}
.editor {
flex-grow: 1;
position: absolute;
white-space: pre-wrap;
width: 100%;
height: 100%;
}
<div id="app"></div>
要获取渲染的 div 的高度,您需要:
ref
获取隐藏div的内容:<div ref={hiddenContentRef} className='editor' style={{visibility:'hidden'}}>
useLayoutEffect
更改,使用 lines
迭代每个内部 div,获取每个 div 的高度(item.getBoundingClientRect().height
)并将其存储到状态中: const [linesHeight, setLinesHeight] = React.useState(new Array(lines.length).fill(0));
React.useLayoutEffect(() => {
// Update the height of each line number to match the real-lines
hiddenContentRef.current.childNodes.forEach(function(item, index){
setLinesHeight(prevLines => ([...prevLines.slice(0, index), item.getBoundingClientRect().height, ...prevLines.slice(index + 1)]))
});
}, [lines]);
然后您将能够为每个行号设置适当的高度:
<div className='margin'>
{lines.map((line,i)=>(<div style={{height: `${linesHeight[i]}px`}}>{(1+i).toString()}</div>))}
</div>