我应该如何在父级的渲染方法中重新创建有状态子组件?

时间:2016-05-21 05:21:02

标签: reactjs flux reactjs-flux

Facebook says我不应该将React组件保留在其父级状态中。相反,我应该在每次运行时在render方法中重新创建子项。

  

什么不应该进入州?

     

反应组件:基于底层道具和渲染()构建它们   状态。

现在我的问题是:我该怎么做?它甚至可能吗?如果我从头开始重新创建子组件,那么状态是否会丢失?

我能想到的这种情况唯一可行的方法是,只有一个状态对象,它属于根组件。其余的组件只有props,每当他们想要更新他们的某些状态时,他们需要调用一些父级的处理程序一直到根组件,因为它是唯一具有状态对象的组件!一旦更新,root将为子组件返回其状态props。我认为这根本不实用!

[UPDATE]

这是一个示例代码,我发现很难不将组件存储在父级状态中:

http://codepen.io/mehranziadloo/pen/XdLvgq

class BlackBox extends React.Component
{
    constructor() {
        super();
        this.state = {
            counter: 0
        };
    }

    increment() {
        this.setState({ counter: this.state.counter+1 });
    }

    render() {
        return (
            <span onClick={this.increment.bind(this)} style={{
                fontSize: '24pt',
                border: '1px solid black',
                margin: 10,
                padding: 10,
            }}>
                {this.state.counter}
            </span>
        );
    }
}

class RedBox extends React.Component
{
    constructor() {
        super();
        this.state = {
            counter: 0
        };
    }

    increment() {
        this.setState({ counter: this.state.counter+1 });
    }

    render() {
        return (
            <span onClick={this.increment.bind(this)} style={{
                fontSize: '24pt',
                border: '1px solid red',
                margin: 10,
                padding: 10,
            }}>
                {this.state.counter}
            </span>
        );
    }
}

class Parent extends React.Component
{
    constructor() {
        super();
        this.state = {
            childCmps: [],
        };
    }

    addBlackBox() {
        let newState = this.state.childCmps.slice();
        newState.push(<BlackBox key={newState.length} />);
        this.setState({
            childCmps: newState
        });
    }

    addRedBox() {
        let newState = this.state.childCmps.slice();
        newState.push(<RedBox key={newState.length} />);
        this.setState({
            childCmps: newState
        });
    }

    render() {
        let me = this;

        return (
            <div>
                <button onClick={this.addBlackBox.bind(this)}>Add Black Box</button> 
                <button onClick={this.addRedBox.bind(this)}>Add Red Box</button>
                <br /><br />
                {this.state.childCmps}
            </div>
        );
    }
}

ReactDOM.render(
    <Parent />,
    document.getElementById('root')
);

4 个答案:

答案 0 :(得分:3)

  

如果从头开始重新创建子组件,那么状态是否会丢失?

不,因为React在内部管理支持实例(保持状态),并且如果两次调用render()说要呈现该组件,则不会替换它们。

换句话说:

ReactDOM.render(<MyComponent />, div);
ReactDOM.render(<MyComponent />, div);

这将创建MyComponent两次,但只有一次。它将渲染两次:它第一次不存在,所以它创建它,第二次它已经存在,所以它会更新它。可以保留在两个渲染过程之间设置的任何内部状态。

React经过优化,允许您简单地创建完整的声明性渲染函数,并确定实现渲染所需的更改。

<强>更新

您发布的示例是在动态子级列表中使用键。键是识别特定子项(以及它们存在的位置)的一种方法,因此您需要小心来更改维护状态元素的渲染过程之间的键。

而不是将实际呈现的组件存储在状态中,例如<BlackBox key={i} />,而是存储必要的数据以呈现组件,例如组件类BlackBox和{{1}的唯一标识符}。 (仅供参考,你不应该使用索引作为密钥,因为索引可以改变。我建议使用一个总是递增的计数器。)

这里修改的key类没有将呈现的组件存储在状态中(其他组件可以保持原样):

Parent

Example in CodePen.

注意:

  • 我使用class Parent extends React.Component { static blackCount = 0; static redCount = 0; state = { childCmps: [], }; constructor(props, context) { super(props, context); } addBlackBox = () => { this.setState({ childCmps: [...this.state.childCmps, { Component: BlackBox, id: "black" + (++Parent.blackCount) }] }); }; addRedBox = () => { this.setState({ childCmps: [...this.state.childCmps, { Component: RedBox, id: "red" + (++Parent.redCount) }] }); }; render() { return ( <div> <button onClick={this.addBlackBox}>Add Black Box</button> <button onClick={this.addRedBox}>Add Red Box</button> <br /><br /> {this.state.childCmps.map(child => <child.Component key={child.id} />)} </div> ); } } (又名全局)道具来计算添加了多少个黑色和红色框,并结合字符串&#34; red&#34;和&#34;黑&#34;形成独特的键。 (如果您没有support for class properties,可以使用static等来初始化静态类属性。)
  • 我使用胖箭头函数属性作为事件处理程序回调,以确保Parent.blackCount = 0在正确的范围内。 (如果您不支持类属性,则可以在构造函数中使用this。)
  • 我将this.addBlackBox = this.addBlackBox.bind(this)初始化移动到了一个类属性。您可以猜测,我强烈建议您使用类属性。 :)
  • 我使用ES6 spread进行数组文字初始化,以附加一个新框并创建一个新数组。
  • 最后,在state函数中,每个框组件始终使用Parent/render() map()的{​​{1}}状态重新呈现。

答案 1 :(得分:1)

您只需要在父状态下保留渲染子组件所需的任何数据。通常,这只是您想要传递的道具或组件的类型 在您的情况下,这只是组件的颜色&#34;红色&#34;或&#34;黑&#34;。
所以在父状态中,包含值为&#34; Red&#34;的字符串的数组。或&#34;黑&#34;足够。

每次单击其中一个按钮时,只需将另一个项添加到数组中,然后再次设置状态。这样的事情。

addRedBox() {
    let newChildList = this.state.childList.slice();
    newChildList.push("Red");
    this.setState({
        childList: newChildList
    });
}

然后在render()函数中执行此操作:

{this.state.childList.map(function(color,i) {
  if (color=="Black") {
    return <BlackBox key={i} />
  } else {
    return <RedBox key={i} />
  }
})}

在重新渲染时,您只需将新道具(如果有)传递给子组件,然后每个子组件也将使用新道具重新渲染。
将新道具传递给孩子将重置子组件。它将再次运行所有生命周期方法(包括render())。

您可以找到working version in this codepen

答案 2 :(得分:0)

组件仅在状态更改(更新)时呈现,并且您应该保持状态简单,使用NSObject+Extensions.h - (NSString *)className; NSObject+Extensions.m - (NSString *)className { return NSStringFromClass(self.class); } 与子组件通信。

当您的应用程序变大时,您可以使用Flux或Redux来管理您的状态

答案 3 :(得分:0)

您正试图在React中看到面向对象的方法。别。有OO,然后就是Facebook所做的一切。

不,根据您引用的文档,您无法将组件存储在状态中。你可以尝试一下,但你会发现一些东西都不起作用。

以下是OO类(伪代码)的示例:

class Parent {
  list children

  temporarilyAbondonChildren() {
    for each child in children {
      Orphanage.leaveAtDoorStep( child )
    }

    doStuffForTenYears()

    for each child in Children {
     output child.isOk()
    }
  }
}

这里是React中最接近它的东西:

class Parent {
  temporarilyAbandonChildren() {
    doStuffForTenYears()
    child_properties = Orphanage.whatWouldveHappenedToMyKidHadIGivenBirthAndLeftThemForTenYears()

    children = renderImaginaryChildren( child_properties )

    for each child in children {
      output child.isOk()
    }
  }

}

  

我能想到的这种情况唯一可行的方法是,只有一个状态对象,它属于根组件。其余的组件只有道具,每当他们想要更新他们的某些状态时,他们需要调用一些父级的处理程序一直到根组件,因为它是唯一具有状态对象的组件!一旦更新,root将为子组件返回其状态作为道具。我认为这根本不实用!

我同意。