【问题标题】:Creating <select> elements in React JS在 React JS 中创建 <select> 元素
【发布时间】:2014-11-05 18:49:50
【问题描述】:

我正在用 react.js 为我的 web 应用程序重写 UI,但我有点被以下问题难住了。

我有一个显示通过 AJAX 请求获得的数据的页面,在该页面下方显示了一个提交新数据的表单。都很好。

现在,我想在表单中添加一个&lt;select&gt; 元素,并从不同的位置(url)获取值。

当前代码(没有&lt;select&gt;)看起来像这样(简化了一点,但所有工作细节都是一样的;它主要遵循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')
        );
    });

现在,我可以通过将getDataMixin 添加到TaskForm 来添加select 元素,获取数据并构建可能选项的列表,但我需要有包含许多列表的表单,并且该方法不' 似乎无法扩展(由于命名冲突;或者我需要使用 mixins 以外的东西)。

所以我想创建一个单独的React class,它只有getDataMixin,通过设置其props 的父级接收API url,并渲染&lt;select&gt; 元素;并在表单中使用此类。 但我不知道如何访问选定的值(因为父母无法访问它的孩子的refs)。 所以我需要另一种方法来“向上”传递所选值。

或者,如果这不可能,请朝正确的方向轻推——我不想最终得到大量不可重用的代码(我改用 react 的部分原因是使用 mixins并将代码保持在合理且可读的最低限度)。

【问题讨论】:

    标签: javascript jquery ajax forms reactjs


    【解决方案1】:

    只要您将子级设置为父级内部的引用,父级就可以访问其子级的引用,您就可以访问该子级内部的任何引用。例如,this.refs.childRef.refs.nestedChildRef 之类的东西。然而,这是一个非常糟糕的主意,除非您只是在阅读信息(并处理诸如 refs 不正确存在的错误,因为父母不知道孩子的 ref 是否设置正确)。

    但幸运的是,有一个非常简单的解决方案。你是对的,你会想要创建一个子组件来获取它自己的数据,而不是一个 mixin。但是,您如何处理将选定项目返回给父级的方式只是将父级的 onChange 函数传递给反应中的内置 &lt;select&gt;

    以下是这种父子关系的示例:

    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,以及任何其他对标准反应&lt;select&gt;元素有效的props。使用 react 内置的 this.transferPropsTo() 函数将所有道具转移到子渲染函数中的 &lt;select&gt; 元素。

    这意味着定义changeHandlerMySelectonChange 处理程序的父级将此函数直接传递给子级的&lt;select&gt; 元素。 So when the select changes, the event gets handled by the parent instead of the child.顺便说一句,这是在 React 中做事的一种完全合法且合法的方式——因为它仍然遵循自上而下的理念。如果您考虑一下,当您使用来自 react 的普通内置 &lt;select&gt; 时,这正是正在发生的事情。 onChange 只是 &lt;select&gt; 的属性。

    还值得注意的是,如果您愿意,您可以“劫持”更改处理程序以添加您自己的自定义逻辑。例如,我倾向于使用自定义输入组件包装我的 &lt;input type="text" /&gt; 字段,该组件接受任何和所有有效的 &lt;input&gt; 道具,但也将 validator 函数作为属性,它允许我定义一个返回 true 的函数/false 基于在&lt;input&gt; 字段中输入的值。我可以通过在父级中为包装子级定义 onChange 来做到这一点,但是在子级中我在内部定义自己的 onChange 来检查要定义的 this.props.onChange 和一个函数,然后运行我的验证器和通过调用this.props.onChange(e, valid) 将事件链传递回父级。在它的onChange 处理程序中为父级提供完全相同的事件对象,但还添加了一个布尔值valid 参数。反正我跑题了……

    希望对你有所帮助:)

    【讨论】:

    • Mike,正如您所写,您的解决方案不会在选择对象上调用渲染,因为您不使用 setState(x) 函数。我修改代码如下创建一个位置数组,var optionsArray = [];,然后更新该数组,最后在循环之后调用:this.setState({options:optionsArray});
    • 很好——这就是我没有在答案中测试代码所得到的结果,尽管我个人更喜欢在直接改变状态对象时使用this.forceUpdate()。不过大多只是个人喜好。我已经编辑了我的答案以更新它。
    猜你喜欢
    • 2021-08-06
    • 2016-10-25
    • 2022-11-16
    • 1970-01-01
    • 2013-08-01
    • 2022-01-02
    • 2021-01-23
    • 1970-01-01
    • 2016-02-06
    相关资源
    最近更新 更多