Js: onChange 事件 for contentEditable

如何侦听基于 contentEditable的控件的更改事件?

var Number = React.createClass({
render: function() {
return <div>
<span contentEditable={true} onChange={this.onChange}>
{this.state.value}
</span>
=
{this.state.value}
</div>;
},
onChange: function(v) {
// Doesn't fire :(
console.log('changed', v);
},
getInitialState: function() {
return {value: '123'}
}
});


React.renderComponent(<Number />, document.body);

JSFiddle 代码。

175075 次浏览

请参阅 塞巴斯蒂安 · 洛伯的回答,它修复了我的实现中的一个 bug。


使用 onInput 事件,可选地使用 onBlur 作为后备。您可能需要保存以前的内容,以防止发送额外的事件。

我个人将这个作为我的渲染函数。

var handleChange = function(event){
this.setState({html: event.target.value});
}.bind(this);


return (<ContentEditable html={this.state.html} onChange={handleChange} />);

Jsbin

它在 contentEditable 周围使用这个简单的包装器。

var ContentEditable = React.createClass({
render: function(){
return <div
onInput={this.emitChange}
onBlur={this.emitChange}
contentEditable
dangerouslySetInnerHTML=\{\{__html: this.props.html}}></div>;
},
shouldComponentUpdate: function(nextProps){
return nextProps.html !== this.getDOMNode().innerHTML;
},
emitChange: function(){
var html = this.getDOMNode().innerHTML;
if (this.props.onChange && html !== this.lastHtml) {


this.props.onChange({
target: {
value: html
}
});
}
this.lastHtml = html;
}
});

这可能并不是你想要的答案,但是我自己也在为这个问题苦苦挣扎,并且对建议的答案也有意见,所以我决定把它变得不受控制。

editable道具是 false,我使用 text道具原样,但当它是 true,我切换到编辑模式,在 text没有效果(但至少浏览器不会吓坏)。在此期间,控件触发 onChange。最后,当我将 editable改回 false时,它会用在 text中传递的内容填充 HTML:

/** @jsx React.DOM */
'use strict';


var React = require('react'),
escapeTextForBrowser = require('react/lib/escapeTextForBrowser'),
{ PropTypes } = React;


var UncontrolledContentEditable = React.createClass({
propTypes: {
component: PropTypes.func,
onChange: PropTypes.func.isRequired,
text: PropTypes.string,
placeholder: PropTypes.string,
editable: PropTypes.bool
},


getDefaultProps() {
return {
component: React.DOM.div,
editable: false
};
},


getInitialState() {
return {
initialText: this.props.text
};
},


componentWillReceiveProps(nextProps) {
if (nextProps.editable && !this.props.editable) {
this.setState({
initialText: nextProps.text
});
}
},


componentWillUpdate(nextProps) {
if (!nextProps.editable && this.props.editable) {
this.getDOMNode().innerHTML = escapeTextForBrowser(this.state.initialText);
}
},


render() {
var html = escapeTextForBrowser(this.props.editable ?
this.state.initialText :
this.props.text
);


return (
<this.props.component onInput={this.handleChange}
onBlur={this.handleChange}
contentEditable={this.props.editable}
dangerouslySetInnerHTML=\{\{__html: html}} />
);
},


handleChange(e) {
if (!e.target.textContent.trim().length) {
e.target.innerHTML = '';
}


this.props.onChange(e);
}
});


module.exports = UncontrolledContentEditable;

有人用我的解决方案 一个 href = “ https://github.com/lovasoa/response-contenteditable”rel = “ nofollow noReferrer”> response-contenteditable 做了一个关于 NPM 的项目

我遇到了另一个问题,当浏览器试图“重新格式化”您刚刚给它的 HTML 时,导致组件总是重新呈现。看这个.

这是我的产品内容 Editable 实现,它在 react-contenteditable上有一些你可能想要的附加选项,包括:

  • 锁定
  • 允许嵌入 HTML 片段的命令式 API
  • 重新格式化内容的能力

摘要:

在我遇到新问题之前,FakeRainBrigand 的解决方案在一段时间内对我非常有效。ContentEditables 是一个痛苦,并不是真正容易处理的反应..。

这个 JSFiddle 演示了这个问题。

如您所见,当您键入一些字符并单击 Clear时,内容不会被清除。这是因为我们试图将 contenteditable 重置为最后一个已知的虚拟 DOM值。

因此,似乎:

  • 您需要 shouldComponentUpdate以防止插入符号位置跳转
  • 如果这样使用 shouldComponentUpdate,就不能依赖 React 的 VDOM 差分算法。

所以您需要一个额外的行,这样当 shouldComponentUpdate返回‘ yes’时,您就可以确定 DOM 内容实际上已经更新。

因此,这里的版本增加了一个 componentDidUpdate,并成为:

var ContentEditable = React.createClass({
render: function(){
return <div id="contenteditable"
onInput={this.emitChange}
onBlur={this.emitChange}
contentEditable
dangerouslySetInnerHTML=\{\{__html: this.props.html}}></div>;
},


shouldComponentUpdate: function(nextProps){
return nextProps.html !== this.getDOMNode().innerHTML;
},


componentDidUpdate: function() {
if (this.props.html !== this.getDOMNode().innerHTML) {
this.getDOMNode().innerHTML = this.props.html;
}
},


emitChange: function() {
var html = this.getDOMNode().innerHTML;
if (this.props.onChange && html !== this.lastHtml) {
this.props.onChange({
target: {
value: html
}
});
}
this.lastHtml = html;
}
});

虚拟的 DOM 仍然过时,它可能不是最有效的代码,但至少它可以工作:) 我的问题解决了


详情:

  1. 如果使用 should dComponent Update 来避免插入符号跳转,那么 contenteditable 永远不会重新呈现(至少在击键时)

  2. 如果组件在击键时从未重新呈现,那么 React 会为这个 contenteditable 保留一个过时的虚拟 DOM。

  3. 如果 React 在其虚拟 DOM 树中保留了一个过时版本的 contenteditable,那么如果您尝试将 contenteditable 重置为虚拟 DOM 中过时的值,那么在虚拟 DOM diff 期间,React 将计算出没有对 DOM 应用的更改!

这种情况通常发生在:

  • 您最初有一个空的 contenteditable (should dComponent Update = true,prop = “”,前面的 vdom = N/A) ,
  • 用户输入一些文本,您就可以防止呈现(should dComponent entUpdate = false,prop = text,before vdom = “”)
  • 在用户单击验证按钮之后,需要清空该字段(should dComponent entUpdate = false,prop = “”,before vdom = “”)
  • 由于新生成的和旧的虚拟 DOM 都是“”,React 不接触 DOM。

下面是一个由 lovasoa 组成的组件: https://github.com/lovasoa/react-contenteditable/blob/master/index.js

他在发射变化中闪烁事件

emitChange: function(evt){
var html = this.getDOMNode().innerHTML;
if (this.props.onChange && html !== this.lastHtml) {
evt.target = { value: html };
this.props.onChange(evt);
}
this.lastHtml = html;
}

我正在成功地使用类似的方法

我建议使用 变异观察者来做这件事。它能让你更好地控制事态的发展。它还提供了有关浏览如何解释所有击键的更多细节。

返回文章页面打字脚本:

import * as React from 'react';


export default class Editor extends React.Component {
private _root: HTMLDivElement; // Ref to the editable div
private _mutationObserver: MutationObserver; // Modifications observer
private _innerTextBuffer: string; // Stores the last printed value


public componentDidMount() {
this._root.contentEditable = "true";
this._mutationObserver = new MutationObserver(this.onContentChange);
this._mutationObserver.observe(this._root, {
childList: true, // To check for new lines
subtree: true, // To check for nested elements
characterData: true // To check for text modifications
});
}


public render() {
return (
<div ref={this.onRootRef}>
Modify the text here ...
</div>
);
}


private onContentChange: MutationCallback = (mutations: MutationRecord[]) => {
mutations.forEach(() => {
// Get the text from the editable div
// (Use innerHTML to get the HTML)
const {innerText} = this._root;


// Content changed will be triggered several times for one key stroke
if (!this._innerTextBuffer || this._innerTextBuffer !== innerText) {
console.log(innerText); // Call this.setState or this.props.onChange here
this._innerTextBuffer = innerText;
}
});
}


private onRootRef = (elt: HTMLDivElement) => {
this._root = elt;
}
}

这对我来说是最简单的解决方案。

<div
contentEditable='true'
onInput={e => console.log('Text inside div', e.currentTarget.textContent)}
>
Text inside div
</div>

因为当编辑完成时,来自元素的焦点总是丢失,所以您可以简单地使用 onBlur事件处理程序。

<div
onBlur={e => {
console.log(e.currentTarget.textContent);
}}
contentEditable
suppressContentEditableWarning={true}
>
<p>Lorem ipsum dolor.</p>
</div>

我试过使用 来自圣罗兰的例子:

<div
onBlur={e => {
console.log(e.currentTarget.textContent);
}}
contentEditable
suppressContentEditableWarning={true}
>
<p>Lorem ipsum dolor.</p>
</div>

在我尝试将这个值设置为状态之前,它一直工作得很好。当我使用调用 setState(e.currentTarget.textContent)的函数性组件时,我得到的 currentTarget为 null。setState异步工作,currentTarget不可用。

在 React 17.0.2中,我的解决方案是使用 e.target.innerText:

<div
onBlur={e => setState(e.target.innerText)}
contentEditable
suppressContentEditableWarning={true}
>
<p>Lorem ipsum dolor.</p>
</div>

以下是基于 塞巴斯蒂安 · 洛伯的回答的基于 钩子的版本:

const noop = () => {};
const ContentEditable = ({
html,
onChange = noop,
}: {
html: string;
onChange?: (s: string) => any;
}) => {
const ref = useRef<HTMLDivElement>(null);
const lastHtml = useRef<string>('');


const emitChange = () => {
const curHtml = ref.current?.innerHTML || '';
if (curHtml !== lastHtml.current) {
onChange(curHtml);
}
lastHtml.current = html;
};


useEffect(() => {
if (!ref.current) return;
if (ref.current.innerHTML === html) return;
ref.current.innerHTML = html;
}, [html]);


return (
<div
onInput={emitChange}
contentEditable
dangerouslySetInnerHTML=\{\{ __html: html }}
ref={ref}
></div>
);
};
<div
spellCheck="false"
onInput={e => console.log("e: ", e.currentTarget.textContent}
contentEditable="true"
suppressContentEditableWarning={true}
placeholder="Title"
className="new-post-title"
/>