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

问题描述 投票:161回答:8
  • 假设我有一个React类P,它呈现两个子类C1和C2。
  • C1包含一个输入字段。我将此输入字段称为Foo。
  • 我的目标是让C2对食物的变化做出反应。

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

第一解决方案

  1. 分配P状态,state.input
  2. 在P中创建一个onChange函数,它接收一个事件并设置state.input
  3. 将这个onChange传递给C1作为props,让C1将this.props.onChange绑定到Foo的onChange

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

但出于同样的原因,它感觉不太正确:我正在从子元素设置父元素的状态。这似乎背叛了React的设计原则:单向数据流。 这是我应该怎么做的,还是有更多的React-natural解决方案?

二解决方案:

把Foo放在P.

但是,当我构建我的应用程序时,我应该遵循这个设计原则 - 将所有表单元素放在最高级别的render中吗?

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

我该怎么办?

reactjs
8个回答
178
投票

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

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

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

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

我建议你阅读

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

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

回到问题。

至于我的第一个解决方案,让处理程序反向运行是完全可以的,因为数据仍然是单向的。

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

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

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

通常,我更喜欢todo列表示例中描述的方式。您在应用中的状态越少越好。


4
投票

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


1
投票

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


1
投票

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

class P extends React.Component {
    state = { foo : "" };

    render(){
        const { foo } = this.state;

        return (
            <div>
                <C1 value={ foo } onChange={ x => this.setState({ foo : x })} />
                <C2 value={ foo } />
            </div>
        )
    }
}

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组件处理状态变化的方式有任何显着差异?你不应该,因为没有。提升状态并传递值/ onChange对是原始React的惯用语。一些答案表明,不使用refs。


0
投票
  1. 正确的做法是让父组件中的状态,以避免ref和什么不是
  2. 问题是避免在键入字段时不断更新所有孩子
  3. 因此,每个子节点应该是一个Component(不是PureComponent)并实现shouldComponentUpdate(nextProps, nextState)
  4. 这样,在表单字段中键入时,只有该字段更新

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

/*
© 2017-present Harald Rudell <[email protected]> (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} />
  }
}

0
投票

使用React> = 16.3,您可以使用ref和forwardRef从父级访问子级DOM。不要再使用旧的参考方式了。 以下是使用您的案例的示例:

import React, { Component } from 'react';

export default class P extends React.Component {
   constructor (props) {
      super(props)
      this.state = {data: 'test' }
      this.onUpdate = this.onUpdate.bind(this)
      this.ref = React.createRef();
   }

   onUpdate(data) {
      this.setState({data : this.ref.current.value}) 
   }

   render () {
      return (
        <div>
           <C1 ref={this.ref} onUpdate={this.onUpdate}/>
           <C2 data={this.state.data}/>
        </div>
      )
   }
}

const C1 = React.forwardRef((props, ref) => (
    <div>
        <input type='text' ref={ref} onChange={props.onUpdate} />
    </div>
));

class C2 extends React.Component {
    render () {
       return <div>C2 reacts : {this.props.data}</div>
    }
}

有关refs和forwardRef的详细信息,请参阅RefsForwardRef


-1
投票

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

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}
        <br />
        <Child onUpdate={this.onUpdateParent} />
        <br />
        <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"));
© www.soinside.com 2019 - 2024. All rights reserved.