Я сейчас создаю компонент с contenteditable div
, используя React.Поскольку контент генерируется пользователем, я пытался синхронизировать мой компонент state
с его содержимым с помощью dangerouslySetInnerHTML
.Другой вариант - напрямую обновить свойство innerHTML
.
Я подумал, что будет важно использовать dangerouslySetInnerHTML
, основываясь на следующем ответе на этот другой вопрос SO:
React.js: установить innerHTML против опасноSetInnerHTML
Да, есть разница!
Непосредственный эффект от использования innerHTML по сравнению с опасно SetInnerHTML идентичен - узел DOM обновитсяс внедренным HTML.
Однако за кулисами, когда вы опасно используете SetInnerHTML, он дает React понять, что HTML внутри этого компонента не является чем-то, о чем он заботится.
Поскольку React использует виртуальный DOMкогда он сравнивает diff с фактическим DOM, он может обходить проверку дочерних узлов этого узла, поскольку он знает, что HTML-код поступает из другого источника.Таким образом, производительность повышается.
Что еще более важно, если вы просто используете innerHTML, React не сможет узнать, был ли изменен узел DOM. При следующем вызове функции рендеринга React будетперезапишите содержимое, которое было введено вручную, с тем, что, по его мнению, должно быть правильным состоянием этого узла DOM.
Но теперь у меня возникла следующая проблема:
Когда естьновый ввод, и вы устанавливаете состояние с новым Html, ваш компонент повторно визуализируется с новым dangerouslySetInnerHTML={{ __html: html }}
и ваша позиция каретки переходит (возвращается к началу строки во всех протестированных браузерах: Chrome, Firefox,Край).
Но здесь нет ничего нового.Это (переход каретки) также произошло бы, если бы я установил новый html, используя innerHTML
.Проблема с 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, например, добавлять текст в span и т. Д. Поэтому мне нужно обновить компонент без перехода по каретке.
Код Sandbox с примером
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>
);
}