React中使用contentEditable的坑--setState后光标位置移到开头

5,492 阅读2分钟
  • 使用contentEditable的坑

HTML元素中有个特殊的属性contentEditable,此属性可以将HTML节点变为可编辑的元素,以实现某些需要特殊输入需求(比如想要自己鲁一个富文本等)。但是,如果你是第一次在react中使用,那么多半会遇到我标题所述的坑。具体就是想把一个组件做成可输入的(别问为啥不用input之类的,问就是特殊需求,不想解释)的控件,输入的值会更新到组件上,此时你要搞定两个问题,第一,类似input输入的框的监听输入的onChange事件,第二,从onChange中获取值之后调用setState()更新。第一个问题很好解决,具体看以下代码,然而第二个问题,更新的时候,就让我掉坑了。多说无用,上代码:

//将一个span标签变成可输入的标签
import React, { Component } from 'react';

class EditNode extends Component {
  constructor(props) {
    super(props);
    this.ref = React.createRef();
    this.onChange = this.onChange.bind(this);
  }

  // 解决输入监听的问题
  onChange() {
    const html = this.ref.current.innerHTML;
    this.props.onChange(html);
  }

  render() {
    const {value} = this.props;
    return (
      <span contentEditable="true"
            ref={this.ref}
            dangerouslySetInnerHTML={{ __html: value }}
            onInput={this.onChange}
            onBlur={this.onChange}
      />
    );
  }
}

export default EditNode;

//使用以上组件

import React, { Component } from 'react';
import EditNode from './EditNode';

class SomeCompoent extends Component {
  constructor(props) {
    super(props);
    this.state = {
        html:''
    }
  }

  render() {
    return (
      <EditNode value={this.state.html}
                onChange={(value) => {
                  //当输入值改变,调用setState()更新的时候,不管你在哪个位置输入,光标妥妥的会跳到第一个位置
                  this.setState({html:value});
                }}
      />
    );
  }
}

export default SomeCompoent;
  • 解决坑

先说说解决历程,尝试了各种检查确认代码没有问题,做防抖更新,保存光标位置等,然并卵。直到启用了面向Google编程高级技能:

第一个结果,点击去,顺利解决问题。根据指示,最终代码修改如下:

import React, { Component } from 'react';

class EditNode extends Component {
  constructor(props) {
    super(props);
    this.ref = React.createRef();
    this.onChange = this.onChange.bind(this);
  }

  onChange() {
    const html = this.ref.current.innerHTML;
    if (this.props.onChange && html !== this.lastHtml) {
      this.props.onChange(html);
    }
    this.lastHtml = html;
  }

  shouldComponentUpdate(nextProps) {
    return nextProps.value !== this.ref.current.innerHTML;
  }

  componentDidUpdate() {
    if (this.props.value !== this.ref.current.innerHTML) {
      this.ref.current.innerHTML = this.props.value;
    }
  }

  render() {
    const {value } = this.props;
    return (
      <span contentEditable="true"
            ref={this.ref}
            dangerouslySetInnerHTML={{ __html: value }}
            onInput={this.onChange}
            onBlur={this.onChange}
      />
    );
  }
}

export default EditNode;

具体原理今天不想写了,被这个问题摩擦了一阵,没心情了。 链接:stackoverflow.com/questions/5…

很多时候,当你信心满满的以为发现了某种黑技术,准备起飞的时候,往往稍不注意就会被按在地上摩擦摩擦的,感觉contentEditable属性就是这么个东西。