用适当的状态/道具反应自我修改组件

时间:2015-06-13 09:47:11

标签: javascript reactjs

我现在正在开展一个宠物项目,我决定使用React进行查看。我正在开发它,但我刚开始意识到我可能不会理解如何使用React非常好,因为看起来为了做一些简单的事情,你必须采取一些非常极端的措施!

所以,时间可以任意一个例子!以下是JsFiddle

的代码
var Name = React.createClass({
    render: function() {
        return <button onClick={this.props.onClick.bind(null, this.props.id, 'John')}>{this.props.value}</button>
    }
});

var NameContainer = React.createClass({
    getInitialState: function() {
        return {
            names: [
                { id: 1, name: 'Fred' },
                { id: 2, name: 'George' }
            ]
        };
    },
    handleClick: function(id, newName) {
        names = this.state.names;
        names.map(function(obj){
            if (obj.id === id) {
                obj.name = newName;
            }
            return obj;
        });
        this.setState({
            names: names
        });
    },
    render: function() {
        var handleClick = this.handleClick;

        var children = this.state.names.map(function(obj){
            return <Name key={obj.id} id={obj.id} value={obj.name} onClick={handleClick} />;
        });

        return (
            <div>
                {children}
            </div>
        );
    }
});

我的想法是,我正在尝试制作一个可以自我修改的组件。在此示例中,修改只是让组件能够将其名称从其原始值更改为“John”。

为什么这么复杂?

通过在Name组件中设置一些状态并让它们自行修改,似乎可以使更多更简单。不幸的是,据我所知,唯一的方法就是设置基于道具的初始状态,这违反了“getInitialState中的道具是反模式”的事情。

为什么不手动制作组件?

在我的实际应用程序中,数据来自服务器,因此必须立即获取所有数据。这就是我使用getInitialState预加载数据而不是手动创建多个Name组件的原因。此外,他们在React文档中说状态应该存储在一个公共父组件中,因此该代码遵循该模式。

事情开始变得复杂

当您需要修改该状态时,问题就开始了。由于它以对象数组的形式出现(非常典型地用于从服务器序列化数据),因此您不能立即获取与您正在处理的组件对应的特定对象。据我所知,唯一的方法是知道将ID传递给所有子元素,以便在触发事件处理程序以识别自身时可以将其传递回去。即使这样,你仍然需要遍历状态中的每个元素,以找到你实际想要修改的元素。

复杂性加剧......

更糟糕的是,您似乎无法从具有该属性的组件内部访问密钥属性,因此为了获得您需要的所有信息,您必须对该值进行双重设置(密钥和在我的例子中的id)。这是红旗真正为我提升的地方,因为它似乎是实现目标的唯一方法,而不是破坏模式是绕过他们系统的一部分..

我烦恼的另一件事是你必须从状态保持对象一直传递一个事件处理程序到子对象。在这个例子中,这很简单,但在我的实际应用中,关系是四层深。在我看来,所有这些中间步骤只是通过错过道具传递链中的一个步骤来增加引入错误的可能性。

那么答案是什么?

这就是React的样子吗?我完全无视一些巨大的捷径吗?如何在保持我指定的约束的同时更简单地做到这一点?

2 个答案:

答案 0 :(得分:5)

我认为,你的例子中存在一些问题,使得它比必要的更复杂:Name组件是一个漏洞抽象,Name组件知道太多。 (这些实际上有点交织在一起。)

在解决这些问题之前,我想绕道一点来讨论我设计组件的方法。我发现混合自上而下和自下而上的方法在构建React应用程序时非常有用。使用自上而下帮助您确定要构建的组件,从组件的概念开始并确定其子组件应该是什么,然后识别子组件的子组件 ad nauseam 。在确定组件之后,是时候切换到自下而上的方法,在这种方法中,您采用最基本的组件(不需要构建它所需的新子组件),并将其构建为尽可能独立且独立,然后选择下一个最基本的组件并实现它,并重复直到完成。

实现组件时,您可以将传入道具视为其他组件要使用的API,并且您希望尽可能专注于组件应提供的功能。

回到你的例子中的问题。

首先,你的Name组件有点漏洞。 (是的,我知道这只是一个随意的例子,但请耐心等待。)该组件将onClick作为其API的一部分(因为它需要作为道具)。这是有问题的,因为它告诉想要使用它的组件,&#34;嘿,我在这里有一个按钮!&#34;您当前的Name实现没问题,但是当您需要更改Name以获得其他行为时会发生什么? onClick可能没有任何意义,如果你想改变那个道具名称,那么这个改变就会在你的其余代码中涟漪。相反,您可以考虑使用名称&#39;更新&#39;这对于Name组件来说似乎是一种合理的操作,同时隐藏它在内部包含按钮的事实。当然,这可以看作是一个&#34;在命名的时候要小心&#34;参数,但我认为良好的命名是构建易于使用且以后易于修改的组件的有用工具。

另一个问题是Name组件对父实现了解太多,以后在其他组件中使用起来更加困难。而不是运行&#39;绑定&#39;在Name组件中,让我们假设拥有容器传递一个带有单个参数的函数 - newName。在name组件中,您只需使用所需的新名称调用该函数。这对任何其他州的影响不是名称所关注的问题,并且在您没有其他选择之前,您已经有效地推迟了解决该问题。然后,Name组件如下所示:

 var Name = React.createClass({
    render: function() {
        return <button onClick={() => this.props.update('John')}>{this.props.name}</button>;
    }
});

注意你不再需要id;你只需接受并更新名称。

由于您实际上不得不担心更新NameContainer组件中的名称(因为数据所在的位置),您可以担心。为了避免遍历所有id,让我们创建一个带有名称对象和新名称的函数,然后将每个名称对象绑定到函数,类似于Name组件中的名称。 (剩下的是一个带有单个参数newName的函数,我们可以将其用作Name的更新函数!)。代码如下:

var NameContainer = React.createClass({
    getInitialState: function() {
        return {
            names: [
                { id: 1, name: 'Fred' },
                { id: 2, name: 'George' }
            ]
        };
    },
    update: function(obj, newName) {
        obj.name = newName;
        this.setState({ names: this.state.names });
    },
    render: function() {    
        var children = this.state.names.map(function(obj){
            return <Name key={obj.id} name={obj.name} update={this.update.bind(null, obj)} />;
        }, this);

        return (
            <div>
                {children}
            </div>
        );
    }
});

这确实简化了您的组件,现在Name可用于更多种类的上下文中。我已使用here所述的更改更新了您的示例,并且我已经添加了名称here的不同实现。 NameContainer没有必要改变,因为Name API没有改变,甚至 虽然Name的实现在两者之间有很大不同。

使用这种自下而上的方法有助于解决您通过一系列子组件传递道具的问题。在构建和测试组件时,您只需要关注使用该组件解决的非常具体的问题;确保将适当的道具传递给其子组件。由于您专注于一个非常具体的问题,因此您不太可能错过传递关键数据。

你可能会看到这一点,但仍然想知道&#34;为什么这么复杂?&#34;。考虑一个应用程序,您希望同时以不同的方式显示相同的数据(例如,图形及其对应的数据表)。 React为您提供了一种跨组件共享数据并使其保持同步的方法。如果您保存所有数据并尽可能在本地操作(尽可能远离子组件链),那么您必须自己实现组件之间的同步。通过将数据放在尽可能高的组件链中,您可以(有效地)使这些组件具有全局性,并且React可以担心其余的细节。您担心传递数据以及如何呈现数据,React负责处理其余数据。

最后请注意,如果您的应用程序的复杂性不断增加,您可以考虑查看Flux。它是一种架构模式,有助于管理应用程序的复杂性。它不是灵丹妙药,但是当我在一些中等复杂的应用上使用它时,我发现它非常好。

答案 1 :(得分:1)

看起来,在您的情况下,您使用道具传递初始值,然后您使用按钮更改该值。这个用例并不属于设置状态的道具&#34;反模式。

来自React&#39; documentation

  

但是,如果您明确表示同步不是此处的目标,那么它不是反模式

他们举例说明:

var Counter = React.createClass({
  getInitialState: function() {
    // naming it initialX clearly indicates that the only purpose
    // of the passed down prop is to initialize something internally
    return {count: this.props.initialCount};
  },

  handleClick: function() {
    this.setState({count: this.state.count + 1});
  },

  render: function() {
    return <div onClick={this.handleClick}>{this.state.count}</div>;
  }
});

React.render(<Counter initialCount={7}/>, mountNode);

所以我认为,在这种情况下,您只需使用props传递Name组件的初始值,在getInitialState中设置该值,然后按下按钮this.setState将其改为别的东西。

我相信这个解决方案可以解答您提出的大部分问题。

您有正确的想法使用父组件来检索服务器的信息,以便确定要创建的Name组件的数量。完成该过程后,使用map编译要创建的所有Name组件,并让render()从那里获取它。这就是我怎么做的。请记住,使用props为组件提供默认值并让组件的状态包含当前值没有任何问题。

相关问题