React.js:contentEditable的onChange事件

时间:2014-03-27 03:52:31

标签: javascript dom contenteditable reactjs

如何监听基于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/

8 个答案:

答案 0 :(得分:65)

修改:请参阅Sebastien Lorber's answer修复我的实施中的错误。


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

答案 1 :(得分:60)

编辑2015

有人用我的解决方案在NPM上制作了一个项目:https://github.com/lovasoa/react-contenteditable

编辑06/2016:我刚刚遇到了一个新问题,当浏览器尝试“重新格式化”您刚给他的html时,会出现组件始终重新渲染的问题。 See

编辑07/2016:这是我的生产内容可编辑的实现。除react-contenteditable之外,它还有一些您可能需要的其他选项,包括:

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

要点:

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

Virtual dom保持过时,它可能不是最有效的代码,但至少它确实有效:) My bug is resolved


<强>详细信息:

1)如果你把shouldComponentUpdate放在以避免插入符号跳转,那么contenteditable永远不会重新渲染(至少在击键时)

2)如果组件永远不会在击键时重新渲染,那么React会保留一个过时的虚拟dom。

3)如果React在其虚拟dom树中保留了一个过时的contenteditable版本,那么如果你试图将contenteditable重置为虚拟dom中过时的值,那么在虚拟dom diff期间,React会计算出没有更改适用于DOM!

这主要发生在:

  • 你最初有空的contenteditable(shouldComponentUpdate = true,prop =“”,之前的vdom = N / A),
  • 用户键入一些文字并阻止渲染(shouldComponentUpdate = false,prop = text,previous vdom =“”)
  • 用户点击验证按钮后,您想要清空该字段(shouldComponentUpdate = false,prop =“”,之前的vdom =“”)
  • 因为新生产的和旧的vdom都是“”,React不接触dom。

答案 2 :(得分:14)

这可能并不是你正在寻找的答案,但是我自己也在努力解决这个问题并且遇到建议的答案,我决定让它不受控制。

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;

答案 3 :(得分:4)

我建议使用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;
    }
}

答案 4 :(得分:2)

以下是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;
}

我成功使用了类似的方法

答案 5 :(得分:1)

这是最适合我的解决方案。

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

答案 6 :(得分:1)

由于编辑完成后,总是会失去元素的焦点,因此您只需使用onBlur钩子即可。

<input type="text" pattern="\d{2}-\d{7}" />

答案 7 :(得分:-1)

如果您正在寻找一种简单易行的方法,那就是这里。

<div 
    contentEditable={true}
    onSelect={() => callMeOnSelect()}
    onChange={() => callMeOnChange()}
    onMouseUp={() => callMeOnMouseUp()}
    className="myClassNameHere"/>

如果您想在触发选择时获取所选文本,您可以这样做。

let selection = window.getSelection().toString();