Реагировать.JS: событие onChange для 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);
6 ответов
Edit: посмотреть ответ Себастьяна Лорбера, который исправляет ошибку в моей реализации.
используйте событие 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;
}
});
редактировать 2015
кто-то сделал проект на NPM с моим решением:https://github.com/lovasoa/react-contenteditable
изменить 06/2016: Я только что окружил новую проблему, которая возникает, когда браузер пытается "переформатировать" html, который вы только что дали ему, что приводит к компоненту всегда рендеринга. посмотреть
изменить 07/2016: вот моя производственная реализация contentEditable. Он имеет несколько дополнительных опций над react-contenteditable
что вы можете захотеть, в том числе:
- замок
- императивный API, позволяющий вставлять HTML-фрагменты
- возможность форматировать контент
резюме:
решение FakeRainBrigand работало довольно хорошо для меня в течение некоторого времени, пока у меня не появились новые проблемы. ContentEditables-это боль,и с ней нелегко справиться...
этот JSFiddle демонстрирует проблему.
как вы можете видеть, при вводе некоторых символов и нажмите на Clear
содержимое не очищается. Это связано с тем, что мы пытаемся сбросить contenteditable до последнего известного значения виртуального dom.
так кажется, что:
- вам нужно
shouldComponentUpdate
для предотвращения прыжков позиции каретки - вы не можете полагаться на алгоритм VDOM React, если вы используете
shouldComponentUpdate
этот путь.
так вам нужна дополнительная строка, чтобы всякий раз, когда shouldComponentUpdate
возвращает да, вы уверены, что содержимое 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 never rerenders (по крайней мере, при нажатии клавиш)
2) Если компонент никогда не перезапускает ключ, то React сохраняет устаревший виртуальный dom для этого contenteditable.
3) Если React сохраняет устаревшую версию contenteditable в своем виртуальном дереве dom, то если вы попытаетесь сбросить contenteditable до значения, устаревшего в виртуальном dom, то во время виртуального DOM diff React вычислит, что нет никаких изменений для применения к Дом!
это происходит в основном, когда:
- у вас есть пустой contenteditable изначально (shouldComponentUpdate=true, prop="", предыдущий vdom=N / A),
- пользователь вводит некоторый текст, и вы предотвращаете визуализации (shouldComponentUpdate=false,prop=text,предыдущий vdom="")
- после того, как пользователь нажимает кнопку проверки, вы хотите очистить это поле (shouldComponentUpdate=false,prop="",предыдущий vdom="")
- как и заново произведенный и старые vdom являются "", реагировать не трогает dom.
это, вероятно, не совсем тот ответ, который вы ищете, но, борясь с этим сам и имея проблемы с предлагаемыми ответами, я решил сделать его неконтролируемым.
, когда editable
опора 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;
вот компонент, который включает в себя большую часть этого 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;
}
Я использую аналогичный подход успешно
Я предлагаю использовать 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;
}
}
Это самое простое решение, которое сработало для меня.
<div
contentEditable='true'
onInput = {e => {console.log('text of div', e.currentTarget.textContent)}}
>
Text in div
</div>