ReactJs:响应状态更改而更改状态

7
我有一个带有输入框的React组件,还有一个可选的“高级输入框”:
[ basic ]
Hide Advanced...
[ advanced ]

当你点击“隐藏高级”时,底部的高级选项将消失,同时按钮上的文本变成“显示高级”。这很直观且有效。状态中有一个名为showAdvanced的键,它控制文本和高级输入是否被呈现。
然而,外部JS代码可能会更改高级选项的值,此时如果高级选项当前是隐藏的并且值与默认值不同,则应该显示高级选项。用户也可以点击“隐藏高级”来再次关闭它。
所以,当外部调用cmp.setState({advanced: "20"})时,我想要显示高级选项。最简单的方法就是更新我的状态中的showAdvanced。然而,在React中似乎没有一种方法可以根据其他状态更改来更新某些状态。我可以想到几种行为略有不同的解决方法,但我真的希望具有这种特定行为。
我应该把showAdvanced移到props中吗?这样做有意义吗?能否在响应状态更改时更改props内容?谢谢。

根据另一个状态设置一个状态似乎不是一个好的模式。为什么不在渲染方法中检查“高级”状态呢? - David Hellsing
你有 jsfiddle 吗?如果我理解正确,你想从组件内部和外部控制元素的可见性,对吗?那么我会使用一个 prop,并使用 if 条件来检查是否应该显示它。 - Jeremy D
@DavidHellsing 我有同样的问题,而且不认为它是反模式:考虑一下如果状态更改响应需要从网络检索数据或进行复杂计算的情况。每次渲染运行时都检索它将是相反的反模式! - porton
2个回答

9
首先,你提到一个在组件之外的第三方可能会调用 cmp.setState()?这是一个巨大的反模式。一个组件只应该调用自己的 setState 函数 - 没有任何外部内容应该访问它。
另外一件需要记住的事情是,如果你试图为了响应状态变化而再次更改状态 - 这意味着你正在做错误的事情。
当你以这种方式构建东西时,它会使问题比必要的困难得多。原因是,如果你接受没有任何外部内容可以设置组件状态的事实 - 那么你唯一的选择就是允许外部内容更新你的组件属性 - 然后在组件内部对其进行响应。这简化了问题。
例如,你应该考虑让任何之前调用 cmp.setState() 的外部内容改为再次调用你的组件上的 React.renderComponent,并传递一个新的属性或属性值,例如将 showAdvanced 设置为 true。然后你的组件可以在 componentWillReceiveProps 中对此进行响应并相应地设置状态。以下是一个示例代码:
var MyComponent = React.createClass({
    getInitialState: function() {
        return {
            showAdvanced: this.props.showAdvanced || false
        }
    },
    componentWillReceiveProps: function(nextProps) {
        if (typeof nextProps.showAdvanced === 'boolean') {
            this.setState({
                showAdvanced: nextProps.showAdvanced
            })
        }
    },
    toggleAdvancedClickHandler: function(e) {
        this.setState({
            showAdvanced: !this.state.showAdvanced
        })
    },
    render: function() {
        return (
            <div>
                <div>Basic stuff</div>
                <div>
                    <button onClick={this.toggleAdvancedClickHandler}>
                        {(this.state.showAdvanced ? 'Hide' : 'Show') + ' Advanced'}
                    </button>
                </div>
                <div style={{display: this.state.showAdvanced ? 'block' : 'none'}}>
                    Advanced Stuff
                </div>
            </div>
        );
    }
});

因此,第一次调用React.renderComponent(MyComponent({}), elem)时,组件将挂载,高级div将被隐藏。如果您单击组件内的按钮,它将切换并显示。如果您需要强制组件从外部显示高级div,只需再次调用render,如下所示:React.renderComponent(MyComponent({showAdvanced: true}), elem),它将显示它,无论内部状态如何。同样,如果您想从外部隐藏它,只需使用showAdvanced: false调用它。
上面代码示例的额外奖励是,在componentWillReceiveProps中调用setState不会导致另一个渲染周期,因为它会在render被调用之前捕获和更改状态。查看这里的文档以获取更多信息:http://facebook.github.io/react/docs/component-specs.html#updating-componentwillreceiveprops 不要忘记,对已经挂载的组件再次调用renderComponent不会重新挂载它,它只是告诉react更新组件的props,然后react将进行更改,运行组件的生命周期和渲染函数,并执行其dom差异化魔法。

谢谢,这绝对是正确的答案。我还遇到了一些额外的问题(外部组件设置和读取的值实际上是序列化/反序列化以分离高级/基本部分),但通过使用props完成所有操作而不是调用外部的setState方法解决了我的问题。 - Emoses
1
在React组件中,将状态设置在子组件上和传递一个回调函数给子组件来设置状态之间是否存在真正的区别?为什么第一种方法是“绝对不可取”的,而第二种方法则被推荐并且广泛使用(例如:https://medium.com/@ruthmpardee/passing-data-between-react-components-103ad82ebd17#3698,https://material-ui.com/demos/dialogs/)? - ThadeuLuz
Mike,这真的很有见地。所以,你的意思是说React组件不应该直接改变另一个组件的状态?理想情况下,当状态改变时,应将props传递给嵌套组件。然而,当状态需要在多个地方改变时,我不确定该怎么办。例如,如果有一个<Input />组件,我可能需要将其值传递到<Form />的状态中,在提交之前将所有值分组。也许我搞混了,但对我来说解决这些问题确实是一个挑战。谢谢。 - csalmeida
我有同样的问题,并不认为它是反模式:考虑一下如果状态改变响应需要从网络检索数据或进行复杂计算的情况。每次渲染运行时都检索它将是反模式的相反! - porton

0

请查看下方评论中的修订答案。


我的初始错误答案:

当接收到新的状态或属性时,生命周期函数componentWillUpdate将被运行。您可以在此处找到有关它的文档:http://facebook.github.io/react/docs/component-specs.html#updating-componentwillupdate

如果在调用外部setState时,您在componentWillUpdate中将showAdvanced设置为true,则应该会得到所需的结果。

编辑:另一个选择是在其新状态中包含showAdvanced: true,使外部调用setState


文档说明不能在ComponentWillUpdate中使用setState,这就是我开始遇到问题的原因。我考虑让外部JS处理状态,但这会将组件特定的逻辑推给不应该处理它的消费者。 - Emoses
啊,没错。你可能想要通过 props 来设置 showAdvanced 的初始值。同样的,也可以这样设置 advanced_text。然后如果另一个组件设置了 advanced_text 状态,你可以在你的 render 中使用这样的逻辑:this.state.advanced_text || this.props.initial_advanced_text。如果设置了 this.state.advanced_text,你还可以隐藏 Hide Advanced 按钮。 - returneax

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接