首页 文章

React - 将表单元素状态传递给兄弟/父元素的正确方法?

提问于
浏览
152
  • 假设我有一个React类P,它呈现两个子类C1和C2 .

  • C1包含一个输入字段 . 我将这个输入字段称为Foo .

  • 我的目标是让C2对Foo的变化作出反应 .

我想出了两个解决方案,但他们都没有感觉到对 .

第一解决方案

  • 分配P状态, state.input .

  • 在P中创建 onChange 函数,该函数接收事件并设置 state.input .

  • 将此 onChange 传递给C1作为 props ,并让C1将 this.props.onChange 绑定到Foo的 onChange .

这很有效 . 每当Foo的值改变时,它在P中触发 setState ,因此P将使输入传递给C2 .

但它没有't feel quite right for the same reason: I'm设置父元素的状态来自子元素 . 这似乎背叛了React的设计原则:单向数据流 .
Is this how I'm supposed to do it, or is there a more React-natural solution?

二解决方案:

把Foo放在P.

But is this a design principle I should follow when I structure my app—putting all form elements in the render of the highest-level class?

就像在我的例子中,如果我有一个大的C1渲染,我真的不想把整个 render 的C1放到P的 render 只是因为C1有一个表单元素 .

我该怎么办?

7 回答

  • 0

    使用keeping the state in parent component的第一个解决方案是正确的 . 但是,对于更复杂的问题,你应该考虑一些状态管理库,redux是最常用的反应 .

  • 4

    所以,如果我正确理解你,你的第一个解决方案是建议你在你的根组件中保持状态?我不能代表React的创造者,但一般来说,我发现这是一个合适的解决方案 .

    维持状态是React创建的原因之一(至少我认为) . 如果您曾经实现过自己的状态模式客户端来处理具有大量相互依赖的移动部分的动态UI,那么您会喜欢React,因为它可以减轻很多状态管理的痛苦 .

    通过将状态保持在层次结构中并通过事件更新它,您的数据流仍然是单向的,您只是响应Root组件中的事件,您实际上并不是通过双向绑定获取数据,你告诉Root组件“嘿,这里发生的事情,检查值”或者你正在传递子组件中某些数据的状态以更新状态 . 你改变了C1中的状态,你希望C2知道它,所以,通过更新Root组件中的状态并重新渲染,C2的道具现在是同步的,因为状态在Root组件中更新并传递 .

    class Example extends React.Component {
      constructor (props) {
        super(props)
        this.state = { data: 'test' }
      }
      render () {
        return (
          <div>
            <C1 onUpdate={this.onUpdate.bind(this)}/>
            <C2 data={this.state.data}/>
          </div>
        )
      }
      onUpdate (data) { this.setState({ data }) }
    }
    
    class C1 extends React.Component {
        render () {
          return (
            <div>
              <input type='text' ref='myInput'/>
              <input type='button' onClick={this.update.bind(this)} value='Update C2'/>
            </div>
          )
        }
        update () {
          this.props.onUpdate(this.refs.myInput.getDOMNode().value)
        }
    })
    
    class C2 extends React.Component {
        render () {
          return <div>{this.props.data}</div>
        }
    })
    
    ReactDOM.renderComponent(<Example/>, document.body)
    
  • 31

    解释了将数据从父传递到子传输的概念,反之亦然 .

    import React, { Component } from "react";
    import ReactDOM from "react-dom";
    
    // taken refrence from https://gist.github.com/sebkouba/a5ac75153ef8d8827b98
    
    //example to show how to send value between parent and child
    
    //  props is the data which is passed to the child component from the parent component
    
    class Parent extends Component {
      constructor(props) {
        super(props);
    
        this.state = {
          fieldVal: ""
        };
      }
    
      onUpdateParent = val => {
        this.setState({
          fieldVal: val
        });
      };
    
      render() {
        return (
          // To achieve the child-parent communication, we can send a function
          // as a Prop to the child component. This function should do whatever
          // it needs to in the component e.g change the state of some property.
          //we are passing the function onUpdateParent to the child
          <div>
            <h2>Parent</h2>
            Value in Parent Component State: {this.state.fieldVal}
            
    <Child onUpdate={this.onUpdateParent} />
    <OtherChild passedVal={this.state.fieldVal} /> </div> ); } } class Child extends Component { constructor(props) { super(props); this.state = { fieldValChild: "" }; } updateValues = e => { console.log(e.target.value); this.props.onUpdate(e.target.value); // onUpdateParent would be passed here and would result // into onUpdateParent(e.target.value) as it will replace this.props.onUpdate //with itself. this.setState({ fieldValChild: e.target.value }); }; render() { return ( <div> <h4>Child</h4> <input type="text" placeholder="type here" onChange={this.updateValues} value={this.state.fieldVal} /> </div> ); } } class OtherChild extends Component { render() { return ( <div> <h4>OtherChild</h4> Value in OtherChild Props: {this.props.passedVal} <h5> the child can directly get the passed value from parent by this.props{" "} </h5> </div> ); } } ReactDOM.render(<Parent />, document.getElementById("root"));
  • 170

    现在使用React构建应用程序,我想与半年前我问过的这个问题分享一些想法 .

    我建议你阅读

    第一篇文章非常有助于了解如何构建React应用程序 .

    如果您以这种方式构建您的React应用程序(而不是 how 来构建它),Flux会回答问题 why . React只占系统的50%,通过Flux,您可以看到整个画面,看看它们如何构成一个连贯的系统 .

    回到问题 .

    至于我的第一个解决方案,让 handler 反方向是完全可以的,因为 data 仍然是单向的 .

    但是,根据您的情况,是否允许处理程序在P中触发setState可能是对还是错 .

    如果应用程序是一个简单的Markdown转换器,C1是原始输入,C2是HTML输出,可以让C1在P中触发setState,但有些人可能认为这不是推荐的方法 .

    但是,如果应用程序是todo列表,C1是用于创建新待办事项的输入,C2是HTML中的待办事项列表,您可能希望处理程序比P更高两级到 dispatcher ,这让 store 更新 data store ,然后将数据发送到P并填充视图 . 看看Flux的文章 . 这是一个例子:Flux - TodoMVC

    一般来说,我更喜欢todo列表示例中描述的方式 . 您在应用中的状态越少越好 .

  • 1

    您应该学习Redux和ReactRedux库 . 它将在一个商店中构建您的状态和道具,您可以稍后在组件中访问它们 .

  • 0

    令我感到惊讶的是,在我写作的那一刻,没有一个直截了当的惯用React解决方案的答案 . 所以这是一个(比较其他人的大小和复杂性):

    class P extends React.Component {
        state = { foo : "" };
    
        render(){
            <C1 value={ foo } onChange={ x => this.setState({ foo : x })} />
            <C2 value={ foo } />
        }
    }
    
    const C1 = ({ value, onChange }) => (
        <input type="text"
               value={ value }
               onChange={ e => onChange( e.target.value ) } />
    );
    
    const C2 = ({ value }) => (
        <div>Reacting on value change: { value }</div>
    );
    

    我正在从子元素设置父元素的状态 . 这似乎背叛了React的设计原则:单向数据流 .

    任何controlled input(在React中处理表单的惯用方法)都会在其 onChange 回调中更新父状态,但仍然不会背叛任何内容 .

    例如,仔细查看C1组件 . 您是否看到 C1 和内置 input 组件处理状态变化的方式有何重大差异?你不应该,因为没有 . Lifting up the state and passing down value/onChange pairs is idiomatic for raw React. 没有使用refs,正如一些答案所暗示的那样 .

  • -1
    • 正确的做法是拥有 state in the parent component ,以避免参考,什么不是

    • 在输入字段时,问题是要避免 constantly updating all children

    • 因此,每个子节点都应该是一个Component(不是PureComponent)并实现 shouldComponentUpdate(nextProps, nextState)

    • 这样,在表单字段中键入时,只有该字段更新

    下面的代码使用来自 ES.Next babel-plugin-transform-decorators-legacyBabelJS 6 和类属性的 @bound 注释(注释在成员函数上设置此值类似于bind):

    /*
    © 2017-present Harald Rudell <harald.rudell@gmail.com> (http://www.haraldrudell.com)
    All rights reserved.
    */
    import React, {Component} from 'react'
    import {bound} from 'class-bind'
    
    const m = 'Form'
    
    export default class Parent extends Component {
      state = {one: 'One', two: 'Two'}
    
      @bound submit(e) {
        e.preventDefault()
        const values = {...this.state}
        console.log(`${m}.submit:`, values)
      }
    
      @bound fieldUpdate({name, value}) {
        this.setState({[name]: value})
      }
    
      render() {
        console.log(`${m}.render`)
        const {state, fieldUpdate, submit} = this
        const p = {fieldUpdate}
        return (
          <form onSubmit={submit}> {/* loop removed for clarity */}
            <Child name='one' value={state.one} {...p} />
            <Child name='two' value={state.two} {...p} />
            <input type="submit" />
          </form>
        )
      }
    }
    
    class Child extends Component {
      value = this.props.value
    
      @bound update(e) {
        const {value} = e.target
        const {name, fieldUpdate} = this.props
        fieldUpdate({name, value})
      }
    
      shouldComponentUpdate(nextProps) {
        const {value} = nextProps
        const doRender = value !== this.value
        if (doRender) this.value = value
        return doRender
      }
    
      render() {
        console.log(`Child${this.props.name}.render`)
        const {value} = this.props
        const p = {value}
        return <input {...p} onChange={this.update} />
      }
    }
    

相关问题