【问题标题】:Editing a rich data structure in React.js在 React.js 中编辑丰富的数据结构
【发布时间】:2013-12-27 05:30:46
【问题描述】:

我正在尝试为数据结构创建一个简单的基于网格的编辑器,但我在使用 React.js 时遇到了一些概念问题。他们的文档对此不是很有帮助,所以我希望这里有人可以提供帮助。

首先,将状态从外部组件转移到内部组件的正确方法是什么?内部组件的状态变化是否可以“冒泡”到外部组件?

其次,两个独立的组件是否可以共享数据,从而使一个中的突变在另一个中可见?

下面是我想做的事情的简化示例 (JSFiddle version):

我有一个 company 对象,其中包含一组 employee 对象。我想在可编辑的网格中布置employee 列表。当我单击按钮时,我想查看生成的 company 对象,以及任何突变(写入控制台)。

/** @jsx React.DOM */

var Cell = React.createClass({
    getInitialState: function () {
        return {data: ""};
    },

    componentWillMount: function () {
        this.setState({data: this.props.data});
    },
    onChange: function (evt) {
        console.log(this.state, evt.target.value);
        this.setState({data: evt.target.value});
    },
    render: function () {
        var data = this.props.data;
        return <input value={this.state.data} onChange={this.onChange} />
    }
});

var Row = React.createClass({
    render: function () {
        return (<div className="row">
            <Cell data={this.props.data.name} />
            <Cell data={this.props.data.location} />
            <Cell data={this.props.data.phone} />
        </div>);
    }
});

var Grid = React.createClass({
    render: function () {
        var rows = this.props.data.map(function (rowData, index) {
            return <Row key={index} data={rowData}>Row</Row>;
        });
        return <div className="table">{rows}</div>;
    }
});

var Button = React.createClass({
    getInitialState: function () {
        return {data: {}}
    },
    componentWillMount: function () {
        this.setState({data: this.props.data});
    },
    onClick: function () {
        console.log(this.state);
    },
    render: function () {
        return <button onClick={this.onClick}>Update</button>;
    }
});

var company = {
    name: "Innotech",
    employees: [
        {id: "1", name: "Peter", location: "IT", phone: "555-1212"},
        {id: "2", name: "Samir", location: "IT", phone: "555-1213"},
        {id: "3", name: "Milton", location: "loading dock", phone: "none"}
    ]
};


React.renderComponent(
    <div><Grid data={company.employees} /><Button data={company} /></div>,       
    document.getElementById('employees')
);

【问题讨论】:

    标签: javascript reactjs


    【解决方案1】:

    我认为这是目前 React 文档最不充分的部分。建议的组件间通信方式是在父子通信时简单地设置 props,在子父子通信时通过 props 传递回调。

    当你觉得你想在兄弟组件之间共享数据时,这意味着应该有一个父组件来管理状态并将其传递给两个组件。大多数情况下,您的状态应该位于组件层次结构的顶部附近,并且每条信息应该(最多)位于一个组件的状态中,而不是更多。

    有关这方面的更多信息,请参阅 Pete Hunt 的博文,Thinking in React


    考虑到这一点,我更新了您的fiddle

    我更改了Grid,使其不再保持自己的状态,而是始终显示通过其道具传递的数据,并在它想要请求时从其道具调用onCellChange > 从其父项更改数据。 (Grid 组件将期望其父级使用修改后的数据更新网格的 data 属性。如果父级拒绝(可能是因为数据验证失败或类似原因),您最终会得到一个只读网格。)

    您还会注意到我创建了一个新的Editor 组件来包装网格及其同级按钮。 Editor 组件现在基本上管理整个页面。在一个真实的应用程序中,网格的内容很可能在其他地方需要,因此状态会被移动到更高的位置。我删除了您的 Button 组件,因为它在本机 &lt;button&gt; 标记之外没有做太多事情;我离开了Cell,但它也可以被删除——Row 可以很容易地直接使用&lt;input&gt; 标签。

    希望这是有道理的。随意询问是否有任何不清楚的地方。如果您想更多地聊聊这些,#reactjs IRC room 周围通常也有人。

    【讨论】:

    • Pete Hunt 帖子的链接非常有用。我遇到的最有用的技术文章之一。谢谢!
    • Ben,在你的小提琴中并不清楚你为什么写这个: // 如果我们在这里很懒,我们会简单地写 // this.state.data[rowIdx][prop] = val; // this.forceUpdate(); // 但以这种方式进行变异可能会造成混淆并妨碍性能 // 稍后进行优化,因此我们改为处理
    • 为什么不 (jsfiddle.net/5vL63c62): row = this.state.data[rowIdx];行[prop] = val; this.replaceState(this.state);
    • 猜测 react 可能不仅可以检测到数据的键已更新,还可以检测到数据中的一行数组已更新。是吗?
    • 与 Pete Hunt 讨论此事:我相信这是更好的方法:jsfiddle.net/kp1zq2kp/1 使用不变性助手:facebook.github.io/react/docs/update.html
    【解决方案2】:

    过去一周左右我一直在探索 ReactJS。我对您的问题提出了一个新问题:为什么要将 Cell 组件与 Row 和 Grid 组件分开?

    来自 Backbone 背景,Cell & Row & Grid 是有意义的,可以对单个 Cell Backbone.Views 进行精细控制。然而,粒度控制和 DOM 更新似乎是 ReactJS 试图为您解决的问题,对我来说,这意味着有一个 Grid 组件在其内部实现了一个 Row/Cell:

    var Grid = React.createClass({
        onChange: function(evt, field) {
            this.props.data[field] = evt.target.value;
        },
    
        render: function () {
            var rows = this.state.data.map(function (rowData, index) {
                return (
                    <div className="row" key={index}>
                        <input value={rowData.name} onChange={this.onChange.bind(null, "name")} />
                        <input value={rowData.location} onChange={this.onChange.bind(null, "location")} />
                        <input value={rowData.phone} onChange={this.onChange.bind(null, "phone")} />
                    </div>
                );
            });
    
            return <div className="table">
                       {rows}
                   </div>;
        }
    });
    

    (忽略 on*Change 处理,那里有改进的余地。未经测试的代码)

    问题是,您会在其他地方重复使用 Cell 或 Row 作为单独的组件吗?对我来说,答案是“很可能不会”。在这种情况下,我上面的解决方案是有意义的,恕我直言,并且摆脱了传递数据和上下变化的问题。

    【讨论】:

    • 我一直在尝试寻找一个解决方案,让单个项目在自己的函数中处理回调,以便我可以将 uid 传递给根组件。例如页面导航。但有时会碰到这些长长的链条。传递...this.props 会有所帮助,但并非在所有情况下都可以,尽管您可以对其进行过滤。另一件事是 LinkedStateMixin,据我所知,它只适用于输入组件,在这种情况下它是完美的。我没有测试过这个,但我不确定你是否可以随意绑定自己的状态。但我认为这不是一个好的模式
    【解决方案3】:

    当父组件没有意义时,在兄弟组件之间共享数据的另一种方法是在组件之间使用事件。例如,您可以使用 Backbone.Events、Node.js Emitter 移植到浏览器或任何类似的库。如果您更喜欢反应式流,您甚至可以使用 Bacon.js。这里有一个结合 Bacon.js 和 React.js 的伟大而简单的例子:http://joshbassett.info/2014/reactive-uis-with-react-and-bacon/

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-08-29
      • 1970-01-01
      • 2010-12-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-03-19
      • 1970-01-01
      相关资源
      最近更新 更多