React.js:contentEditable的onChange事件

如何聆听基于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); 

http://jsfiddle.net/NV/kb3gN/1621/

编辑:请参阅Sebastien Lorber的解决我的实现中的错误的答案 。


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

我个人有这个作为我的渲染function。

 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; } }); 

编辑2015年

有人用我的解决scheme在NPM上做了一个项目: https : //github.com/lovasoa/react-contented

编辑06/2016:我刚刚遇到了一个新的问题,当浏览器试图“重新格式化”刚刚给他的HTML,导致组件总是重新渲染。 看到

编辑07/2016:这是我的制作内容可编辑的实施。 它有一些额外的select,你可能想要react-contenteditable ,包括:

  • locking
  • 命令式API允许embeddedHTML片段
  • 重新格式化内容的能力

概要:

一段时间以来,FakeRainBrigand的解决scheme对我来说工作得相当好,直到我遇到新的问题。 ContentEditables是一个痛苦,并不是很容易处理React …

这个JSFiddle演示了这个问题。

正如你所看到的,当你键入一些字符并点击Clear ,内容不会被清除。 这是因为我们尝试将contentedable重置为最后一个已知的虚拟dom值。

所以看来:

  • 您需要shouldComponentUpdate来防止脱字符位置跳转
  • 如果shouldComponentUpdate这种方式使用shouldComponentUpdate则不能依赖React的VDOM shouldComponentUpdatealgorithm。

所以你需要一个额外的行,所以每当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保持过时,它可能不是最有效的代码,但至less它工作:) 我的错误已解决


细节:

1)如果你把shouldComponentUpdate设置为避免脱字符跳转,那么contenteditable就不会重写(至less在键盘上)

2)如果组件在关键笔划上永不放弃,那么React会为这个可满足的内容保留一个过时的虚拟世界。

3)如果React在其虚拟dom树中保留了contenteditable的过期版本,那么如果你尝试将contenteditable重置为虚拟dom中过期的值,那么在虚拟dom差异期间,React会计算出没有变化适用于DOM!

这主要发生在:

  • 你最初有一个空的contenteditable(shouldComponentUpdate = true,prop =“”,以前的vdom = N / A),
  • 用户键入一些文本,并防止渲染(shouldComponentUpdate = false,prop = text,previous vdom =“”)
  • 在用户点击一个validationbutton后,你需要清空该字段(shouldComponentUpdate = false,prop =“”,previous vdom =“”)
  • 因为新产生的旧vdom都是“”,React并没有碰到这个dom。

这可能不是你正在寻找的答案,但是自己一直在努力解决这个问题,并且在提出答案的时候遇到了问题,所以我决定把它变成不受控制的。

editable道具是false ,我使用text道具,但是当它是true ,我切换到编辑模式,其中text没有效果(但至less浏览器不会吓倒)。 在这段时间内,控制器会触发onChange 。 最后,当我将editable改回为false ,它将使用text中传递的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; 

下面是一个由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; } 

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

我build议使用一个mutationObserver来做到这一点。 它给你更多的控制权。 它也给你更多的细节,如何浏览解释所有的击键

在TypeScript中

 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; } }