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

Tra*_*par 8 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>
        );
    }
});
Run Code Online (Sandbox Code Playgroud)

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

为什么这么复杂?

看起来这将作出很大的只是设置的名称组件的一些状态,让他们修改自己简单.不幸的是,据我所知,唯一的方法就是设置基于道具的初始状态,这违反了"getInitialState中的道具是一种反模式"的东西.

为什么不手动制作组件?

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

事情变得复杂起来

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

复杂性加剧......

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

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

所以答案是什么?

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

小智 5

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

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

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

回到你的例子中的问题.

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

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

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

注意你不再需要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>
        );
    }
});
Run Code Online (Sandbox Code Playgroud)

这确实简化了您的组件,现在Name可以在更广泛的环境中使用.我已经使用此处描述的更改更新了您的示例,并且我已经在这里添加了一个不同的Name实现.NameContainer不必更改,因为Name API没有更改,即使Name的实现在两者之间有很大差异.

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

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

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