我应该如何在父级的render方法中重新创建有状态的子级组件? [英] How should I be recreating a stateful child component in a parent's render method?

查看:63
本文介绍了我应该如何在父级的render方法中重新创建有状态的子级组件?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

Facebook说,我不应该将React组件保持在其父级的状态.相反,每次运行时,我都应该在render方法中重新创建该子代.

Facebook says that I should not keep a React component in its parent's state. Instead I should be recreating the child in render method each time it is run.

什么不应该进入状态?

反应组件:根据基础道具在render()中构建它们,并 状态.

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

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

Now my question is: How can I do that? Is it even possible? Isn't the state lost if I recreate a child component from scratch?

我能想到的这种情况的唯一方法是,只有一个状态对象,它属于根组件.其余组件仅具有props,并且每当要更新其状态时,都需要一直调用某些父级的处理程序,直到根组件为止,因为这是唯一带有状态对象的组件!并且一旦更新,根目录将把子组件的状态恢复为props.我认为这根本不实用!

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!

[UPDATE]

这是一个示例代码,我发现很难不以父项状态存储组件:

Here's a sample code that I find hard not to store components in the parent's state:

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()表示要渲染该组件,则不会替换后台实例.

No, because React internally manages the backing instances (which hold the state) and does not replace them if two calls to render() say to render that component.

换句话说:

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

这将不会创建两次,但只会创建一次.它将渲染两次:第一次不存在,因此创建它,第二次已经存在,因此将对其进行更新.可以在两次渲染过程之间设置的任何内部状态都将保留.

This will not create MyComponent twice, but only once. It will render it twice: the first time it doesn't exist, so it creates it, and the second time it already exists, so it will update it. Any internal state that may be set between the two render passes will be preserved.

React经过优化,可以让您简单地创建完整的声明式渲染函数,并指出实现渲染所需的更改.

React is optimized to allow you to simply create complete, declarative render functions, and it figures out what changes are needed to actualize the rendering.

更新

您发布的示例在动态子级列表上使用键.键是一种识别特定子项(及其存在的位置)的方法,因此您需要小心 not 更改渲染状态之间的键,以保持状态.

The example you posted is using keys on a dynamic list of children. Keys are a way to identify specific children (and where they exist), so you need to be careful not to change keys between render passes for elements that maintain state.

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

Instead of storing the actual rendered components in state, such as <BlackBox key={i} />, store the necessary data to render the component, such as the component class BlackBox and a unique identifier for the key. (FYI you shouldn't use index as key, since index can change. I recommend using an always incrementing counter.)

这里是Parent类,其修改后可以在不将渲染组件存储为状态的情况下工作(其他组件可以保持原样):

Here is the Parent class modified to work without storing rendered components in state (the other components can remain as is):

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

CodePen中的示例.

注意:

  • 我使用了static(又名全局)道具来计算已添加的黑盒和红盒的数量,并与字符串"red"和"black"组合形成唯一的键. (如果没有 ES6传播中使用了数组文字初始化以添加一个新框并创建一个新数组.
  • 最后,在Parent/render()函数中,始终使用状态map()
  • I used static (aka global) props to count how many black and red boxes have been added, combined with the strings "red" and "black" to form unique keys. (You can use Parent.blackCount = 0, etc, to initialize static class properties if you don't have support for class properties.)
  • I used fat arrow function properties as event handler callbacks to ensure this is in the correct scope. (You can use this.addBlackBox = this.addBlackBox.bind(this) in the constructor if you don't have support for class properties.)
  • I moved state initialization to a class property. As you can guess, I highly recommend you make use of class properties. :)
  • I used ES6 spread with array literal initialization to append a new box and create a new array.
  • Finally, in the Parent/render() function each box component is always re-rendered using a map() of the state with dynamic component type rendering of <child.Component>.

这篇关于我应该如何在父级的render方法中重新创建有状态的子级组件?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆