反应:输入范围滑块未将最后一个值传播给 Chrome 中的所有者

React: Input range slider not propagating last value to its owner in Chrome

以下fiddle使用React 0.13将3个输入范围显示为受控组件。状态更改后,彩色方块会更新以显示新的 rgb 颜色。 http://jsfiddle.net/faria/yw6q3s9L/

它在 Firefox 中的工作方式与我 expect/desire 相同,但在 Chrome 中则不然。具体来说,彩色方形组件显示倒数第二个值,而滑块显示最近的值。

这是 React/Chrome 错误吗?或者我可以修复我的代码中的某些内容吗?

代码:

var ColorDisplay = React.createClass({
    propTypes: {
        red: React.PropTypes.number.isRequired,
        green: React.PropTypes.number.isRequired,
        blue: React.PropTypes.number.isRequired
    },
    _toHex: function(s) {
        var hex = parseInt(s,10).toString(16);
        return hex.length == 1 ? "0" + hex : hex;
    },
    render: function() {
        var rr = this._toHex(this.props.red),
            gg = this._toHex(this.props.green),
            bb = this._toHex(this.props.blue);
        var color = "#" + rr + gg + bb;
        var divStyle = {backgroundColor: color};
        return (
            <div id="display">
                <h3>{color}</h3>
                <div id="color-swatch" style={divStyle}></div>
            </div>
        );
    }
});

var ColorSlider = React.createClass({
    propTypes: {
        red: React.PropTypes.number.isRequired,
        green: React.PropTypes.number.isRequired,
        blue: React.PropTypes.number.isRequired,
        changeHandler: React.PropTypes.func.isRequired
    },
    getInitialState: function() {
        return {
            red: this.props.red,
            green: this.props.green,
            blue: this.props.blue
            };
    },
    handleOnChange: function(name, event) {
        var new_state = {};
        new_state[name] = event.target.value;
        this.setState(new_state);
        this.props.changeHandler();
    },
    handleClick: function(name, event) {
        var new_state = {};
        var self = this;
        ['red','green','blue'].forEach(function(name) {
            new_state[name] = React.findDOMNode(self.refs[name]).value;
        });
        this.setState(new_state);
        this.props.changeHandler();
    },
    _getRGB: function() {
        return this.state;
    },
    _toHex: function(s) {
        var hex = parseInt(s,10).toString(16);
        return hex.length == 1 ? "0" + hex : hex;
    },
    render: function() {
        var red_style = {color: 'red'};
        var grn_style = {color: 'green'};
        var blue_style = {color: 'blue'};
        var min=0;
        var max=255;
        var step=1;
        var rr = this._toHex(this.state.red),
            gg = this._toHex(this.state.green),
            bb = this._toHex(this.state.blue);
        return (
            <div id="slider">
                <div>
                    <label htmlFor="id_red">Red</label>
                    <input
                        name="red"
                        type="range"
                        min={min}
                        max={max}
                        step={step}
                        value={this.state.red}
                        onInput={this.handleOnChange.bind(this, "red")}
                        onChange={this.handleOnChange.bind(this, "red")}
                        ref="red"
                        id="id_red"
                        />
                    <span style={red_style}>{this.state.red} / {rr}</span>
                </div>
                <div>
                    <label htmlFor="id_green">Green</label>
                    <input
                        name="green"
                        type="range"
                        min={min}
                        max={max}
                        step={step}
                        value={this.state.green}
                        onInput={this.handleOnChange.bind(this, "green")}
                        onChange={this.handleOnChange.bind(this, "green")}
                        ref="green"
                        id="id_green"
                        />
                    <span style={grn_style}>{this.state.green} / {gg}</span>
                </div>
                <div>
                    <label htmlFor="id_blue">Blue</label>
                    <input
                        name="blue"
                        type="range"
                        min={min}
                        max={max}
                        step={step}
                        value={this.state.blue}
                        onInput={this.handleOnChange.bind(this, "blue")}
                        onChange={this.handleOnChange.bind(this, "blue")}
                        ref="blue"
                        id="id_blue"
                        />
                    <span style={blue_style}>{this.state.blue} / {bb}</span>
                </div>
                <button onClick={this.handleClick}>Set Color (hack to sync slider with square on Chrome)</button>
            </div>
        );
    }
});

var App = React.createClass({
    getInitialState: function() {
        return { red: 0, green: 0, blue: 0 };
    },
    update: function() {
        var rgb = this.refs.slider._getRGB();
        // the values are now strings!
        this.setState({
            red: rgb.red,
            green: rgb.green,
            blue: rgb.blue
        });
    },
    render: function() {
        return(
            <div id="color-app">
                <ColorDisplay ref="color"
                    red={this.state.red}
                    green={this.state.green}
                    blue={this.state.blue}
                />
                <ColorSlider ref="slider"
                    red={this.state.red}
                    green={this.state.green}
                    blue={this.state.blue}
                    changeHandler={this.update}
                />
            </div>
        );
    }
});
React.render(<App />, document.getElementById('app'));

您在 ColorSlider 中的 handleOnChange 中存在竞争条件。您应该在状态更新后调用 this.props.changeHandler(),而不是在调用 setState 之后直接调用。这是因为 setState 不是同步的。然而,它可能在 Firefox 中正常工作的原因是因为它可能是一场非常快的比赛,而 Firefox 恰好出现在没有显示错误的一侧。

要强制它始终工作并消除竞争,请在 ColorSlider 中对 handleOnChange 使用类似的东西:

handleOnChange: function(name, event) {
    var new_state = {};
    new_state[name] = event.target.value;
    this.setState(new_state, function() {
        this.props.changeHandler();
    });
}

它会工作得很好。这样做是为了确保 this.props.changeHandler 仅在状态实际更新并呈现后才被调用。出于这个特定原因,setState 有一个可选的回调。

请注意,您不必将 setState 中的回调绑定到 this,因为 React 会自动为您绑定它。

进一步编辑:

你也只会出现这个问题,因为你直接从父组件调用子组件函数(不好)并且你在不需要时直接从 DOM 读取数据.从外部调用组件的方法在 React 中是一个巨大的禁忌。相反,您应该做的是通过 onChange 回调将新的颜色值向上传递到链中,并在父级中以这种方式对其做出反应。

EG:

handleOnChange: function(name, event) {
  var new_state = {};
  new_state[name] = event.target.value;
  this.setState(new_state, function() {
    this.props.changeHandler(name, this.state[name]);
  })
}

允许您通过将 update 函数更改为类似以下内容来设置父 App 组件中的颜色变化:

update: function(name, value) {
  var stateChange = {};
  stateChange[name] = value;
  this.setState(stateChange);
}