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

How should I be recreating a stateful child component in a parent's render method?

Facebook says 我不应该将 React 组件保持在 parent 的状态。相反,我应该在每次 运行.

时在渲染方法中重新创建 child

What Shouldn't Go in State?

React components: Build them in render() based on underlying props and state.

现在我的问题是:我该怎么做?有可能吗?如果我从头开始重新创建一个 child 组件,状态不会丢失吗?

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

[更新]

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

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')
);

Isn't the state lost if I recreate a child component from scratch?

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

换句话说:

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

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

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


更新

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

不是将实际渲染的组件存储在状态中,例如 <BlackBox key={i} />,而是存储渲染组件所需的数据,例如组件 class BlackBox 和唯一标识符对于 key。 (仅供参考,您不应该使用索引作为键,因为索引可以更改。我建议使用始终递增的计数器。)

这里是 Parent class 修改后的工作状态而不存储渲染的组件(其他组件可以保持原样):

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>
        );
    }
}

Example in CodePen.

备注:

  • 我使用 static(又名全局)道具来计算添加了多少个黑色和红色框,并结合字符串 "red" 和 "black" 形成唯一键。 (如果没有 support for class properties,可以使用 Parent.blackCount = 0 等来初始化静态 class 属性。)
  • 我使用粗箭头函数属性作为事件处理程序回调以确保 this 在正确的范围内。 (如果不支持 class 属性,可以在构造函数中使用 this.addBlackBox = this.addBlackBox.bind(this)。)
  • 我将 state 初始化移动到 class 属性。您可以猜到,我强烈建议您使用 class 属性。 :)
  • 我使用 ES6 spread 和数组文字初始化来附加一个新框并创建一个新数组。
  • 最后,在 Parent/render() 函数中,每个盒子组件总是使用状态 map()<child.Component> 重新渲染。

组件仅在状态更改(更新)时呈现,您应该保持状态简单,使用 props 与子组件通信。

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

您只需将呈现子组件所需的任何数据保留在父级状态。通常,这只是您要传递的道具或组件的类型。
在您的情况下,这只是组件 "Red" 或 "Black".
的颜色 因此,在父状态下,包含值为 "Red" 或 "Black" 的字符串的数组就足够了。

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

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.

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

不,根据您引用的文档,您不能将组件存储在状态中。你可以尝试一下,但你会发现事情就是行不通。

这是一个面向对象的例子 class(伪代码):

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()
    }
  }

}

The only way I can think of that this scenario will work in, is that there's only one state object and it belongs to the root component. The rest of components will only have props and whenever they want to update some state of theirs, they need to call some parent's handler all the way up to root component, since it's the only component with an state object! And once updated, the root will give the child components back their state as props. Which I don't think it is practical at all!

我同意。