React:输入范围滑块不会将最后一个值传播到Chrome中的所有者

时间:2015-05-30 19:14:39

标签: html5 google-chrome reactjs

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

它可以像我在Firefox中所期望的那样工作,但不能在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'));

1 个答案:

答案 0 :(得分:1)

handleOnChange ColorSlider中存在竞争条件。一旦在调用this.props.changeHandler()后没有直接更新状态,您应该调用setState。这是因为setState不是同步的。然而,它可能正确地在Firefox中运行的原因是因为它可能是一个非常快速的竞争,并且firefox恰好出现在没有显示错误的一面。

要强制它始终有效并消除竞争,请在handleOnChange中对ColorSlider使用类似的内容:

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]);
  })
}

允许您通过将App功能更改为以下内容来设置父update组件中的颜色更改:

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