[英]React.js: onChange event 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);
编辑:请参阅Sebastien Lorber 的回答,它修复了我的实现中的错误。
使用 onInput 事件和可选的 onBlur 作为后备。 您可能希望保存以前的内容以防止发送额外的事件。
我个人会将此作为我的渲染功能。
var handleChange = function(event){
this.setState({html: event.target.value});
}.bind(this);
return (<ContentEditable html={this.state.html} onChange={handleChange} />);
它使用这个简单的 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;
}
});
这是对我有用的最简单的解决方案。
<div
contentEditable='true'
onInput={e => console.log('Text inside div', e.currentTarget.textContent)}
>
Text inside div
</div>
编辑 2015
有人用我的解决方案在 NPM 上做了一个项目: https ://github.com/lovasoa/react-contenteditable
编辑 06/2016:我刚刚遇到了一个新问题,当浏览器尝试“重新格式化”你刚刚给他的 html 时会出现这个问题,导致组件总是重新渲染。 看
编辑 07/2016:这是我的生产内容可编辑实现。 它有一些您可能想要的超过react-contenteditable
的附加选项,包括:
FakeRainBrigand 的解决方案在我遇到新问题之前对我来说效果很好。 ContentEditables 很痛苦,而且处理 React 并不容易......
这个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)如果您放置 shouldComponentUpdate 以避免插入符号跳转,则 contenteditable 永远不会重新呈现(至少在击键时)
2) 如果组件在击键时从不重新渲染,那么 React 会为这个 contenteditable 保留一个过时的虚拟 dom。
3) 如果 React 在其虚拟 dom 树中保留了一个过期版本的 contenteditable,那么如果你尝试将 contenteditable 重置为虚拟 dom 中过期的值,那么在虚拟 dom diff 期间,React 将计算出没有对适用于 DOM!
这主要发生在以下情况:
因为当编辑完成时,元素的焦点总是丢失,您可以简单地使用onBlur
事件处理程序。
<div
onBlur={e => {
console.log(e.currentTarget.textContent);
}}
contentEditable
suppressContentEditableWarning={true}
>
<p>Lorem ipsum dolor.</p>
</div>
这可能不完全是您正在寻找的答案,但是我自己也在为此苦苦挣扎并且对建议的答案有疑问,所以我决定让它不受控制。
当editable
prop 为false
时,我按原样使用text
prop,但当它为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;
我建议使用 mutationObserver 来做到这一点。 它使您可以更好地控制正在发生的事情。 它还为您提供有关浏览器如何解释所有击键的更多详细信息
在打字稿中
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;
}
}
这是一个包含 lovasoa 的大部分内容的组件: https ://github.com/lovasoa/react-contenteditable/blob/master/index.js
他在 emitChange 中填充事件
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;
}
我成功地使用了类似的方法
<div
spellCheck="false"
onInput={e => console.log("e: ", e.currentTarget.textContent}
contentEditable="true"
suppressContentEditableWarning={true}
placeholder="Title"
className="new-post-title"
/>
我尝试使用上面来自 Saint Laurent 的示例:
<div
onBlur={e => {
console.log(e.currentTarget.textContent);
}}
contentEditable
suppressContentEditableWarning={true}
>
<p>Lorem ipsum dolor.</p>
</div>
在我尝试将此值设置为 state 之前,它运行良好。 当我使用调用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>
这是我基于https://stackoverflow.com/a/27255103的基于钩子的版本
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
contentEditable={true}
dangerouslySetInnerHTML={{ __html: str }}
/>
如果您正在寻找一种简单而简单的方法,那就是。
<div
contentEditable={true}
onSelect={() => callMeOnSelect()}
onChange={() => callMeOnChange()}
onMouseUp={() => callMeOnMouseUp()}
className="myClassNameHere"/>
如果您想在触发选择时获取所选文本,您可以执行此操作。
let selection = window.getSelection().toString();
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.