React.js:contentEditable 的 onChange 事件

IT技术 javascript dom contenteditable reactjs
2021-01-22 04:48:30

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

6个回答

编辑:请参阅Sebastien Lorber 的答案,该答案修复了我的实现中的错误。


使用 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;
    }
});
@NVI,它是 shouldComponentUpdate 方法。只有当 html prop 与元素中的实际 html 不同步时,它才会跳转。例如,如果你做了this.setState({html: "something not in the editable div"}})
2021-03-14 04:48:30
当您想设置state.html为最后一个“已知”值时,这实际上有点缺陷,React 不会更新 DOM,因为就 React 而言,新的 html 完全相同(即使实际 DOM 不同)。请参阅jsfiddle我还没有找到一个好的解决方案,所以欢迎提出任何想法。
2021-03-15 04:48:30
@SebastienLorber 不是优化,但我很确定阅读 html 比设置它更好。我能想到的唯一其他选择是监听所有可能改变 html 的事件,当这些事件发生时,你缓存 html。大多数情况下这可能会更快,但会增加很多复杂性。这是非常确定和简单的解决方案。
2021-03-20 04:48:30
@dchestshouldComponentUpdate应该是纯的(没有副作用)。
2021-03-28 04:48:30
不错,但我猜对this.getDOMNode().innerHTMLin的调用shouldComponentUpdate不是很优化
2021-04-02 04:48:30

编辑 2015

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

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

编辑 07/2016:这是我的生产 contentEditable 实现。它有一些react-contenteditable您可能想要的附加选项,包括:

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

概括:

FakeRainBrigand 的解决方案在一段时间内对我来说效果很好,直到我遇到新问题。ContentEditables 很痛苦,而且处理 React 并不容易......

这个JSFiddle演示了这个问题。

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

所以看起来:

  • 您需要shouldComponentUpdate防止插入符号位置跳转
  • 如果你使用shouldComponentUpdate这种方式,你就不能依赖 React 的 VDOM diffing 算法

所以你需要一个额外的行,这样每当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 仍然过时,它可能不是最有效的代码,但至少它确实有效 :)我的错误已解决


细节:

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

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

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

这主要发生在:

  • 您最初有一个空的 contenteditable (shouldComponentUpdate=true,prop="",previous vdom=N/A),
  • 用户输入一些文本,你阻止渲染(shouldComponentUpdate=false,prop=text,previous vdom="")
  • 用户单击验证按钮后,您想清空该字段(shouldComponentUpdate=false,prop="",previous vdom="")
  • 由于新生成的和旧的 vdom 都是 "",因此 React 不会触及 dom。
@LucaColonnello 您最好使用,{...this.props}以便客户端可以从外部自定义此行为
2021-03-15 04:48:30
你能解释一下shouldComponentUpdate代码是如何防止插入符号跳转的吗?
2021-03-21 04:48:30
我已经实现了 keyPress 版本,当按下 Enter 键时会提醒文本。jsfiddle.net/kb3gN/11378
2021-03-24 04:48:30
@kmoe 因为如果 contentEditable 已经具有适当的文本(即击键时),则组件永远不会更新。使用 React 更新 contentEditable 会使插入符号跳转。在没有 contentEditable 的情况下尝试,看看你自己 ;)
2021-03-29 04:48:30
很酷的实现。您的 JSFiddle 链接似乎有问题。
2021-04-12 04:48:30

这是对我有用的最简单的解决方案。

<div
  contentEditable='true'
  onInput={e => console.log('Text inside div', e.currentTarget.textContent)}
>
Text inside div
</div>
当我用 React 状态更新文本时,它会不断地将插入符号移到文本的开头。
2021-03-27 04:48:30
如果您需要不受控制的组件,这非常有用。正如其他人所提到的,它不适用于受控情况,但 React 也通过警告来阻止这种情况:A component is `contentEditable` and contains `children` managed by React. It is now your responsibility to guarantee that none of those nodes are unexpectedly modified or duplicated. This is probably not intentional.
2021-03-27 04:48:30
漂亮干净,我希望它适用于许多设备和浏览器!
2021-04-05 04:48:30
无需对此进行投票,它有效!请记住onInput按照示例中的说明使用
2021-04-11 04:48:30
此解决方案将去除所有标记,只为您提供文本内容,从而消除使用内容可编辑 div 的原因。而是使用innerHTML ie onInput={(e) =>console.log("Text inside div", e.currentTarget.innerHTML) }
2021-04-13 04:48:30

由于编辑完成后元素的焦点总是丢失,因此您可以简单地使用onBlur事件处理程序。

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

这可能不是您正在寻找的答案,但我自己也在为此苦苦挣扎并且对建议的答案有疑问,因此我决定让它不受控制。

editableprop is 时false,我text按原样使用prop,但是当它是时true,我切换到text没有效果的编辑模式(但至少浏览器不会吓坏了)。在此期间onChange由控件发射。最后,当我改editable回 时false,它会用传入的任何内容填充 HTML text

/** @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;
我不太明白它是如何工作的。你永远不会改变editableUncontrolledContentEditable你能提供一个可运行的例子吗?
2021-03-17 04:48:30
@NVI:这有点难,因为我在这里使用了 React 内部module。基本上我是editable从外部设置的想想当用户按下“编辑”时可以内联编辑的字段,当用户按下“保存”或“取消”时应该再次只读。所以当它是只读的时,我使用道具,但每当我进入“编辑模式”时我就不再看它们,只有当我退出时才再次看道具。
2021-03-18 04:48:30
您能否扩展您在其他答案中遇到的问题?
2021-03-20 04:48:30
@NVI:我需要安全注射,因此不能按原样放置 HTML。如果我不放置 HTML 并使用 textContent,我会遇到各种浏览器不一致的问题,并且无法shouldComponentUpdate如此轻松地实现,因此即使它不再使我免于插入符号跳转。最后,我有 CSS 伪元素:empty:before占位符,但此shouldComponentUpdate实现阻止了 FF 和 Safari 在用户清除该字段时对其进行清理。我花了 5 个小时才意识到我可以通过不受控制的 CE 来避免所有这些问题。
2021-03-30 04:48:30
您将要为谁使用此代码,React 已重命名escapeTextForBrowserescapeTextContentForBrowser.
2021-04-06 04:48:30