Реагировать.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 diffing 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 никогда не повторяет (по крайней мере, при нажатии клавиш)
2) Если компонент никогда не повторяется при нажатии клавиши, то React сохраняет устаревший виртуальный dom для этого contenteditable.
3) Если React сохраняет устаревшую версию contenteditable в своем виртуальном дереве dom, то если вы попытаетесь сбросить contenteditable до значения устаревшего в виртуальном dom, то во время виртуального DOM diff React вычислит, что нет никаких изменений для применения к виртуальному DOM Дом!
это происходит в основном, когда:
- у вас есть пустой contenteditable изначально (shouldComponentUpdate=true, prop="", предыдущий vdom=N / A),
- пользователь вводит некоторый текст, и вы предотвращаете визуализацию (shouldComponentUpdate=false, prop=text, предыдущий vdom="")
- после того, как пользователь нажимает кнопку проверки, вы хотите очистить это поле (shouldComponentUpdate=false,prop="",предыдущий vdom="")
- как недавно произведенных и старый вдом являются "", реагировать не трогать дом.
это, вероятно, не совсем тот ответ, который вы ищете, но, борясь с этим сам и имея проблемы с предлагаемыми ответами, я решил сделать его неконтролируемым вместо этого.
, когда
editable
опораfalse
, Я используюtext
проп как есть, но когда это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; } }