在React JS中创建<select>元素

13

我正在使用 react.js 为我的 Web 应用程序重写 UI,并且以下问题让我有些困惑。

我有一个页面通过 AJAX 请求获取数据,并在下方显示一个提交新数据的表单。一切正常。

现在,我想在表单中添加一个 <select> 元素,并从不同的位置(URL)获取值。

当前的代码(不包括 <select>)看起来像这样(简化了一些内容,但所有的工作细节都相同;它主要遵循 react.js 网站上的教程):

var tasks_link = $('#tasks_link');

var getDataMixin = {
        loadDataFromServer: function() {
            $.ajax({
                url: this.props.url,
                dataType: 'json',
                success: function(data) {
                    this.setState({data: data});
                }.bind(this),
                error: function(xhr, status, err) {
                    console.error(this.props.url, status, err.toString());
                }.bind(this)
            });
        },
        getInitialState: function() {
            return {data: []};
        },
        componentDidMount: function() {
            this.loadDataFromServer();
        }
    };

var sendDataMixin = {
        handleDataSubmit: function(senddata) {
            $.ajax({
                url: this.props.url,
                dataType: 'json',
                contentType: 'application/json',
                type: 'POST',
                data: senddata,
                success: function(data) {
                    var curr_d = this.state.data;
                    var curr_d_new = curr_d.concat([data]);
                    this.setState({data: curr_d_new});
                }.bind(this),
                error: function(xhr, status, err) {
                    console.error(this.props.url, status, err.toString());
                }.bind(this)
            });
        }
    };

var taskForm = React.createClass({
        handleSubmit: function() {
            var name = this.refs.task_name.getDOMNode().value.trim();
            if (!name) {
              return false;
            }
            this.props.onTaskSubmit(JSON.stringify({name: name}));
            this.refs.task_name.getDOMNode().value = '';
            return false;
        },
        render: function () {
            return (
                <form className="well base_well new_task_well" onSubmit={this.handleSubmit}>
                    <div className="form-group">
                        <div className="input-group">
                            <span className="input-group-addon no_radius">Task name</span>
                            <input type="text" className="form-control no_radius" id="add_new_project_input" ref="task_name"/>
                        </div>
                    </div>
                    <button type="button" className="btn btn-default no_radius add_button" id="add_new_task_btn" type="submit">Add task</button>
                </form>
            );
        }
    });

var taskBox = React.createClass({
        mixins: [getDataMixin, sendDataMixin],
        render: function () {
            return (
                <div id="project_box" className="taskBox"> <taskList data={this.state.data} />
                    <taskForm onTaskSubmit={this.handleDataSubmit}/> </div>
            );
        }
    });

tasks_link.click(function() {
        React.renderComponent(
            <taskBox url="/api/tasks/" />,
            document.getElementById('content_container')
        );
    });

现在,我可以通过为TaskForm添加一个getDataMixin来添加一个select元素,获取数据并构建可能选项的列表,但是我需要有包含多个列表的表单,并且这种方法似乎不具备扩展性(由于命名冲突;或者我需要使用其他东西而不是mixins)。

所以我想创建一个单独的React类,它只包含getDataMixin,通过父级设置其props来接收API URL,并渲染<select>元素;然后在表单中使用这个类。 但是我不知道如何访问所选值(因为父级无法访问它的子级的refs)。 因此,我需要另一种将所选值“传递”上去的方法。

或者,如果这不可能,那就给我个正确方向的提示——我不想最终得到大量不能重复使用的代码(我切换到React的一部分原因是使用mixins并将代码保持在合理和可读的最低限度)。

1个回答

17
只要在父组件中将子组件设置为引用,父组件就可以访问其子组件的引用,从而访问子组件内的任何引用。例如:this.refs.childRef.refs.nestedChildRef。但是这样做是非常不好的,除非你只是读取信息(并处理错误,例如引用不存在的情况,因为父组件不知道子组件的引用是否已正确设置)。
但是幸运的是有一个非常简单的解决方案。你需要创建一个子组件,该子组件获取自己的数据,而不是使用mixin。如同在React内置的<select>一样,你可以通过从父组件传递onChange函数来处理将选择的项目返回给父组件。
以下是一个父子关系的示例:
var MyParent = React.createClass({
    getInitialState: function() {
        return {
            childSelectValue: undefined
        }
    },
    changeHandler: function(e) {
        this.setState({
            childSelectValue: e.target.value
        })
    },
    render: function() {
        return (
            <div>
                <MySelect 
                    url="http://foo.bar"
                    value={this.state.childSelectValue}
                    onChange={this.changeHandler} 
                />
            </div>
        )
    }
});

var MySelect = React.createClass({
    propTypes: {
        url: React.PropTypes.string.isRequired
    },
    getInitialState: function() {
        return {
            options: []
        }
    },
    componentDidMount: function() {
        // get your data
        $.ajax({
            url: this.props.url,
            success: this.successHandler
        })
    },
    successHandler: function(data) {
        // assuming data is an array of {name: "foo", value: "bar"}
        for (var i = 0; i < data.length; i++) {
            var option = data[i];
            this.state.options.push(
                <option key={i} value={option.value}>{option.name}</option>
            );
        }
        this.forceUpdate();
    },
    render: function() {
        return this.transferPropsTo(
            <select>{this.state.options}</select>
        )
    }
});

以上示例展示了一个父组件 MyParent 包含一个子组件 MySelect,它传递了一个url以及任何其他对于标准react <select>元素有效的props。在渲染函数中,使用react内置的 this.transferPropsTo() 函数将所有props转移到子组件的 <select> 元素中。

这意味着定义 changeHandler 作为 MySelectonChange 处理程序的父级直接将此函数传递给子级的 <select> 元素。因此,当选择更改时,事件由父级处理而不是子级。顺便说一下,这是React中进行事情的一种完全合法和合规的方法 - 因为它仍然遵循自上而下的哲学。如果您考虑一下,当您使用普通内置的来自react的 <select> 时,正是这种情况。 onChange 只是 <select> 的属性。

值得注意的是,如果您愿意,您可以“劫持”更改处理程序以添加自己的自定义逻辑。例如,我倾向于使用自定义输入组件将我的<input type="text" />字段包装起来,该组件接受任何和所有有效的<input>属性,但也以属性的形式接受一个 validator 函数,它允许我定义一个根据输入到<input>字段中的值返回true/false的函数。我可以通过在父级中定义onChange来实现这一点,对于包装子元素,但然后在子元素中内部定义自己的onChange,该元素检查是否定义了this.props.onChange 和函数,然后运行我的验证器并通过调用this.props.onChange(e, valid)将事件链传递回父级。向父级提供完全相同的事件对象,在其onChange处理程序中,但还带有一个添加的布尔值valid参数。无论如何,我离题了....

希望这可以帮助您 :)


1
Mike,按照你的解决方案,由于你没有使用setState(x)函数,所以不会调用选择对象上的渲染。 我修改了代码,如下所示创建一个位置数组var optionsArray = [];,然后更新该数组,在循环后最终调用:this.setState({options:optionsArray}); - Josef.B
好的发现 - 这就是我没有在答案中测试代码的结果,虽然个人而言,我更喜欢在直接改变状态对象时使用this.forceUpdate()。虽然大多数情况下只是个人偏好。 我已编辑我的答案进行更新。 - Mike Driver

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接