我目前正在使用React构建带有contenteditable div
的组件。由于内容是由用户生成的,因此我试图通过使用state
使组件dangerouslySetInnerHTML
与内容保持同步。我的另一个选择是直接更新innerHTML
属性。
基于这个其他SO问题的以下答案,我认为使用dangerouslySetInnerHTML
很重要:
React.js: Set innerHTML vs dangerouslySetInnerHTML
是的,有区别!
使用innerHTML和危险地设置InnerHTML的直接效果是相同的-DOM节点将使用注入的HTML进行更新。
但是,当您在危险地使用SetInnerHTML时,它会在幕后使React知道该组件内部的HTML无关紧要。
因为React使用了一个虚拟DOM,当它比较diff与实际DOM时,它可以直接绕过检查该节点的子节点,因为它知道HTML来自其他来源。这样就可以提高性能。
更重要的是,如果仅使用innerHTML,React无法知道已修改DOM节点。下一次调用render函数时,React将覆盖手动注入的内容它认为该DOM节点的正确状态应该是什么。
但是现在我想出了以下问题:
当有新的输入并且您使用新的HTML设置状态时,您的组件将使用新的dangerouslySetInnerHTML={{ __html: html }}
重新渲染,并且插入符号会跳转到位置(在所有经过测试的浏览器上返回到行首到目前为止:Chrome,Firefox,Edge)。
但是这里没有新内容。如果我使用innerHTML
设置新的html,则也会出现此插入符号。 dangerouslySetInnerHTML
用新的state
接收html
的问题是,当您设置新的state
时,它是对setState()
的异步调用。
因此,您无法立即致电placeCaret()
。仅当您使用setTimeout()
花费一些时间来使新的state
更新组件时,此方法才有效。
setHTML(root.innerHTML);
setTimeout(() => placeCaret(root.innerText.length), 100); // TIMEOUT NEEDED
//placeCaret(root.innerText.length);
无论如何,这是一种解决方法,但最终会导致糟糕的UX,因为用户可以看到插入符号在每次击键时都会跳动。
问题
是否有使用dangerouslySetInnerHTML
来解决这种插入符跳跃情况的解决方案?还是应该避免使用它,而直接使用innerHTML
以便可以立即更改元素?
注意:我认为使用shouldComponentUpdate
阻止组件更新不是可行的解决方案。因为有时我实际上必须修改html,例如,将一些文本添加到跨度中,等等。因此,我将需要在不插入符号的情况下更新组件。
import React, { useRef, useState } from "react";
import ReactDOM from "react-dom";
import styled from "styled-components";
const S = {};
S.div = styled.div`
border: 1px dotted blue;
`;
function App() {
const divRef = useRef(null);
const [html, setHTML] = useState("<div><br></div>");
function onInput() {
const root = divRef.current;
setHTML(root.innerHTML);
setTimeout(() => placeCaret(root.innerText.length), 100);
//placeCaret(root.innerText.length);
}
// PLACE CARET BACK IN POSITION
function placeCaret(position) {
const root = divRef.current;
const selection = window.getSelection();
const range = selection.getRangeAt(0);
range.setStart(root.firstChild.firstChild, position);
}
function onKeyDown(e) {
console.log("On keydown... " + e.key);
if (e.key === "Enter") {
e.preventDefault();
}
}
return (
<React.Fragment>
<S.div
contentEditable
ref={divRef}
onInput={onInput}
onKeyDown={onKeyDown}
dangerouslySetInnerHTML={{ __html: html }}
/>
</React.Fragment>
);
}