如何在React中更新父状态


348

我的结构如下所示:

Component 1  

 - |- Component 2


 - - |- Component 4


 - - -  |- Component 5  

Component 3

组件3应该根据组件5的状态显示一些数据。由于道具是不可变的,因此我不能简单地将其状态保存在组件1中并转发它,对吗?是的,我已经阅读了有关redux的内容,但不想使用它。我希望有可能通过反应来解决。我错了吗?


20
超级简单:通过属性将parent-setState-Function传递给子组件:<MyChildComponent setState = {p => {this.setState(p)}} />在子组件中,通过this.props对其进行调用。 setState({myObj,...});
Marcel Ennix

@MarcelEnnix,您的评论节省了我的时间。谢谢。
Dinith Minura

<MyChildComponent setState={(s,c)=>{this.setState(s, c)}} />如果您打算使用此技巧,请确保您支持回调。
Barkermn01 '19

4
传递回调以设置父级状态是一种非常糟糕的做法,它可能导致维护问题。它破坏了封装并使组件2 4和5与1紧密耦合。如果沿着这条路走,那么您将无法在其他地方重用这些子组件中的任何一个。最好有特定的道具,以便子组件可以在发生任何事情时触发事件,然后父组件可以正确处理该事件。
Pato Loco

@MarcelEnnix,为什么用大括号括起来this.setState(p)?我在没有它们的情况下进行了尝试,但它似乎可以正常工作(我对React很
陌生

Answers:


675

对于儿童与父母之间的通信,您应该传递一个将状态从父母更改为孩子的函数,如下所示

class Parent extends React.Component {
  constructor(props) {
    super(props)

    this.handler = this.handler.bind(this)
  }

  handler() {
    this.setState({
      someVar: 'some value'
    })
  }

  render() {
    return <Child handler = {this.handler} />
  }
}

class Child extends React.Component {
  render() {
    return <Button onClick = {this.props.handler}/ >
  }
}

这样,孩子可以通过调用通过props传递的函数来更新父母的状态。

但是您将不得不重新考虑组件的结构,因为据我了解,组件5和3并不相关。

一种可能的解决方案是将它们包装在更高级别的组件中,该组件将同时包含组件1和3的状态。此组件将通过prop设置较低级别的状态。


6
为什么需要this.handler = this.handler.bind(this),而不仅仅是需要设置状态的处理函数?
chemook78

34
ES6 React类方法中的@ chemook78不会自动绑定到类。因此,如果不添加this.handler = this.handler.bind(this)构造函数,thishandler函数内部将引用函数闭包,而不是类。如果不想将所有函数绑定到构造函数中,则还有两种使用箭头函数处理此问题的方法。你可以只写点击处理程序onClick={()=> this.setState(...)},或者你可以用箭头在此用作描述一起使用属性initialisers babeljs.io/blog/2015/06/07/react-on-es6-plus下“箭头功能”
伊万

1
这是一个实际的例子:plnkr.co/edit/tGWecotmktae8zjS5yEr?p=preview
Tamb

5
一切都说得通,为什么要使用e.preventDefault?那需要jQuery吗?
文森特·布斯卡洛(Fincent Buscarello)'17年

1
快速提问,这是否不允许在孩子中分配本地状态?
ThisGuyCantEven

50

我发现以下工作解决方案将onClick函数参数从子级传递到父级组件:

带有method()的版本

//ChildB component
class ChildB extends React.Component {

    render() {

        var handleToUpdate  =   this.props.handleToUpdate;
        return (<div><button onClick={() => handleToUpdate('someVar')}>
            Push me
          </button>
        </div>)
    }
}

//ParentA component
class ParentA extends React.Component {

    constructor(props) {
        super(props);
        var handleToUpdate  = this.handleToUpdate.bind(this);
        var arg1 = '';
    }

    handleToUpdate(someArg){
            alert('We pass argument from Child to Parent: ' + someArg);
            this.setState({arg1:someArg});
    }

    render() {
        var handleToUpdate  =   this.handleToUpdate;

        return (<div>
                    <ChildB handleToUpdate = {handleToUpdate.bind(this)} /></div>)
    }
}

if(document.querySelector("#demo")){
    ReactDOM.render(
        <ParentA />,
        document.querySelector("#demo")
    );
}

看一下JSFIDDLE

带有箭头功能的版本

//ChildB component
class ChildB extends React.Component {

    render() {

        var handleToUpdate  =   this.props.handleToUpdate;
        return (<div>
          <button onClick={() => handleToUpdate('someVar')}>
            Push me
          </button>
        </div>)
    }
}

//ParentA component
class ParentA extends React.Component { 
    constructor(props) {
        super(props);
    }

    handleToUpdate = (someArg) => {
            alert('We pass argument from Child to Parent: ' + someArg);
    }

    render() {
        return (<div>
            <ChildB handleToUpdate = {this.handleToUpdate} /></div>)
    }
}

if(document.querySelector("#demo")){
    ReactDOM.render(
        <ParentA />,
        document.querySelector("#demo")
    );
}

看一下JSFIDDLE


这个好!您能解释一下这句话:<ChildB handleToUpdate = {handleToUpdate.bind(this)} />为什么必须再次绑定?
丹妮(Dane)

@Dane-必须将其上下文绑定为父级,以便this在子级内部调用时,this引用父级的状态而不是子级的状态。这是最好的关闭!
凯西

@Casey但是我们不是在构造函数中这样做吗?而且还不够吗?
丹妮(Dane)'18

你完全正确!我错过了。是的,如果您已经在构造函数中做到了,那么您就很好了!
Casey

你是传奇伴侣!这将使组件保持良好的自包含状态,而不必强迫创建父组件来处理状态交换
adamj

13

我要感谢最赞同的答案,它给了我自己的问题的想法,基本上是通过箭头功能和从子组件传递参数来实现的:

 class Parent extends React.Component {
  constructor(props) {
    super(props)
    // without bind, replaced by arrow func below
  }

  handler = (val) => {
    this.setState({
      someVar: val
    })
  }

  render() {
    return <Child handler = {this.handler} />
  }
}

class Child extends React.Component {
  render() {
    return <Button onClick = {() => this.props.handler('the passing value')}/ >
  }
}

希望它可以帮助某人。


直接调用的箭头功能有何特别之处?
Ashish Kamble,

@AshishKamble中的thisin箭头函数引用父级的上下文(即Parent类)。
CPHPython

这是重复的答案。您可以在接受的答案中添加评论,并提及此实验功能以在课堂上使用箭头功能。
Arashsoft

10

我喜欢关于传递函数的答案,这是一种非常方便的技术。

另一方面,您也可以像Flux一样使用pub / sub或使用变体(调度程序)来实现此目的。理论非常简单,让组件5发送一个消息,组件3正在监听。然后,组件3更新其状态,从而触发重新渲染。这需要有状态的组件,根据您的观点,这些组件可能是反模式,也可能不是。我个人反对他们,而是希望其他人从上到下都在监听调度和更改状态(Redux这样做了,但增加了其他术语)。

import { Dispatcher } from flux
import { Component } from React

const dispatcher = new Dispatcher()

// Component 3
// Some methods, such as constructor, omitted for brevity
class StatefulParent extends Component {
  state = {
    text: 'foo'
  } 

  componentDidMount() {
    dispatcher.register( dispatch => {
      if ( dispatch.type === 'change' ) {
        this.setState({ text: 'bar' })
      }
    }
  }

  render() {
    return <h1>{ this.state.text }</h1>
  }
}

// Click handler
const onClick = event => {
  dispatcher.dispatch({
    type: 'change'
  })
}

// Component 5 in your example
const StatelessChild = props => {
  return <button onClick={ onClick }>Click me</button> 
}

与Flux捆绑在一起的分派器非常简单,它只注册回调并在发生任何分派时调用它们,并通过分派上的内容进行传递(在上面的简短示例中payload,分派没有消息,只是消息ID)。如果您觉得更有意义,则可以轻松地使其适应传统的发布/订阅(例如,使用事件中的EventEmitter或其他版本)。


我的Reacts组件正在浏览器中“运行”,就像在官方教程(facebook.github.io/react/docs/tutorial.html)中一样,我试图将Flux包含在browserify中,但是浏览器说找不到Dispatcher :(
wklm

2
我使用的语法是ES2016模块语法,需要进行编译(我使用Babel,但还有其他语法,babelify转换可以与browserify一起使用),它编译为var Dispatcher = require( 'flux' ).Dispatcher
Matt Styles

8

我找到了以下可行的解决方案,以通过参数将onClick函数参数从子级传递到父级组件:

家长班:

class Parent extends React.Component {
constructor(props) {
    super(props)

    // Bind the this context to the handler function
    this.handler = this.handler.bind(this);

    // Set some state
    this.state = {
        messageShown: false
    };
}

// This method will be sent to the child component
handler(param1) {
console.log(param1);
    this.setState({
        messageShown: true
    });
}

// Render the child component and set the action property with the handler as value
render() {
    return <Child action={this.handler} />
}}

子班:

class Child extends React.Component {
render() {
    return (
        <div>
            {/* The button will execute the handler function set by the parent component */}
            <Button onClick={this.props.action.bind(this,param1)} />
        </div>
    )
} }

2
任何人都可以告诉我这是否是可以接受的解决方案(特别是对按建议传递参数感兴趣)。
ilans

param1只是在控制台上显示而不得到分配而总是分配true
Ashish Kamble

我无法说出解决方案的质量,但这对我来说成功地超越了参数。
詹姆斯

6

每当您需要在任何级别的子级与父级之间进行交流时,最好利用 上下文。在父组件中,定义可以由子组件调用的上下文,例如

在您的案例组件的父组件中3

static childContextTypes = {
        parentMethod: React.PropTypes.func.isRequired
      };

       getChildContext() {
        return {
          parentMethod: (parameter_from_child) => this.parentMethod(parameter_from_child)
        };
      }

parentMethod(parameter_from_child){
// update the state with parameter_from_child
}

现在在子组件(您的情况下为组件5)中,只需告诉该组件它想使用其父组件的上下文即可。

 static contextTypes = {
       parentMethod: React.PropTypes.func.isRequired
     };
render(){
    return(
      <TouchableHighlight
        onPress={() =>this.context.parentMethod(new_state_value)}
         underlayColor='gray' >   

            <Text> update state in parent component </Text>              

      </TouchableHighlight>
)}

您可以在回购中找到演示项目


我无法理解这个答案,您能解释一下吗
Ashish Kamble

5

似乎我们只能将数据从父级传递到子级,因为react促进了单向数据流,但是要使父级在其“子级组件”中发生某些事情时进行自身更新,通常使用所谓的“回调函数”。

我们将在父级中定义的函数作为“ props”传递给子级,并从子级中调用该函数,以在父级组件中触发该函数。


class Parent extends React.Component {
  handler = (Value_Passed_From_SubChild) => {
    console.log("Parent got triggered when a grandchild button was clicked");
    console.log("Parent->Child->SubChild");
    console.log(Value_Passed_From_SubChild);
  }
  render() {
    return <Child handler = {this.handler} />
  }
}
class Child extends React.Component {
  render() {
    return <SubChild handler = {this.props.handler}/ >
  }
}
class SubChild extends React.Component { 
  constructor(props){
   super(props);
   this.state = {
      somethingImp : [1,2,3,4]
   }
  }
  render() {
     return <button onClick = {this.props.handler(this.state.somethingImp)}>Clickme<button/>
  }
}
React.render(<Parent />,document.getElementById('app'));

 HTML
 ----
 <div id="app"></div>

在此示例中,我们可以通过将函数传递给其直接子级来使数据从SubChild-> Child-> Parent传递。


4

我已经多次使用此页面上评分最高的答案,但是在学习React的过程中,我发现了一种更好的方法,无需在props中绑定也没有内联函数。

看看这里:

class Parent extends React.Component {

  constructor() {
    super();
    this.state={
      someVar: value
    }
  }

  handleChange=(someValue)=>{
    this.setState({someVar: someValue})
  }

  render() {
    return <Child handler={this.handleChange} />
  }

}

export const Child = ({handler}) => {
  return <Button onClick={handler} />
}

该键位于箭头功能中:

handleChange=(someValue)=>{
  this.setState({someVar: someValue})
}

您可以在这里阅读更多内容。希望这对某人有用=)


这对我来说意义非凡。谢谢!
Brett Rowberry

3

-我们可以创建ParentComponent并使用handleInputChange方法来更新ParentComponent状态。导入ChildComponent,我们将两个道具从父组件传递到子组件,即handleInputChange函数和count。

import React, { Component } from 'react';
import ChildComponent from './ChildComponent';

class ParentComponent extends Component {
  constructor(props) {
    super(props);
    this.handleInputChange = this.handleInputChange.bind(this);
    this.state = {
      count: '',
    };
  }

  handleInputChange(e) {
    const { value, name } = e.target;
    this.setState({ [name]: value });
  }

  render() {
    const { count } = this.state;
    return (
      <ChildComponent count={count} handleInputChange={this.handleInputChange} />
    );
  }
}
  • 现在,我们创建ChildComponent文件并将其另存为ChildComponent.jsx。该组件是无状态的,因为子组件没有状态。我们使用prop-types库进行props类型检查。

    import React from 'react';
    import { func, number } from 'prop-types';
    
    const ChildComponent = ({ handleInputChange, count }) => (
      <input onChange={handleInputChange} value={count} name="count" />
    );
    
    ChildComponent.propTypes = {
      count: number,
      handleInputChange: func.isRequired,
    };
    
    ChildComponent.defaultProps = {
      count: 0,
    };
    
    export default ChildComponent;

当孩子有一个影响其父母财产的孩子时,该如何工作?
Bobort

3

如果这种情况并没有遍及每个地方,那么您可以使用React的上下文,特别是如果您不想引入状态管理库引入的所有开销时。另外,它更容易学习。但是要小心,您可能会过度使用它并开始编写错误的代码。基本上,您定义了一个Container组件(将为您保留并保持该状态),从而使所有对写入/读取该数据段感兴趣的组件成为其子级(不一定是直接子级)

https://reactjs.org/docs/context.html

您也可以适当地使用普通React。

<Component5 onSomethingHappenedIn5={this.props.doSomethingAbout5} />

将doSomethingAbout5传递给组件1

    <Component1>
        <Component2 onSomethingHappenedIn5={somethingAbout5 => this.setState({somethingAbout5})}/>
        <Component5 propThatDependsOn5={this.state.somethingAbout5}/>
    <Component1/>

如果这是一个常见问题,则应开始考虑将应用程序的整个状态转移到其他地方。您有几种选择,最常见的是:

https://redux.js.org/

https://facebook.github.io/flux/

基本上,不是在组件中管理应用程序状态,而是在发生某些事情来更新状态时发送命令。组件也从此容器中获取状态,因此所有数据都被集中。这并不意味着不能再使用本地状态,而是一个更高级的主题。


2

因此,如果您想更新父组件,

 class ParentComponent extends React.Component {
        constructor(props){
            super(props);
            this.state = {
               page:0
            }
        }

        handler(val){
            console.log(val) // 1
        }

        render(){
          return (
              <ChildComponent onChange={this.handler} />
           )
       }
   }


class ChildComponent extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
             page:1
        };
    }

    someMethod = (page) => {
        this.setState({ page: page });
        this.props.onChange(page)
    }

    render() {
        return (
       <Button
            onClick={() => this.someMethod()} 
       > Click
        </Button>
      )
   }
}

这里的onChange是一个属性,该属性的实例绑定了“ handler”方法。我们将方法处理程序传递给Child类组件,以通过其props参数中的onChange属性进行接收。

属性onChange将在props对象中设置,如下所示:

props ={
onChange : this.handler
}

并传递给子组件

因此,Child组件可以像这样的props.onChange一样访问props对象中的name值。

它是通过使用渲染道具来完成的。

现在,Child组件具有设置为onclick事件的按钮“ Click”,以调用在其props参数对象中通过onChnge传递给它的处理程序方法。所以现在this.props.onChange儿童持有父类的输出方法 参考和学分: 点点滴滴


抱歉,延迟,这里的onChange是一个属性,该属性的实例绑定了“处理程序”方法。我们将方法处理程序传递给Child类组件,以通过其props参数中的onChange属性进行接收。属性onChange将在以下props对象中设置:props = {onChange:this.handler}并传递给子组件,因此Child组件可以像在此props.onChange中那样访问props对象中name的值。使用渲染道具。 参考和信贷: [ blog.bitsrc.io/...
Preetham NT

0

我这样做的。

type ParentProps = {}
type ParentState = { someValue: number }
class Parent extends React.Component<ParentProps, ParentState> {
    constructor(props: ParentProps) {
        super(props)
        this.state = { someValue: 0 }

        this.handleChange = this.handleChange.bind(this)
    }

    handleChange(value: number) {
        this.setState({...this.state, someValue: value})
    }

    render() {
        return <div>
            <Child changeFunction={this.handleChange} defaultValue={this.state.someValue} />
            <p>Value: {this.state.someValue}</p>
        </div>
    }
}

type ChildProps = { defaultValue: number, changeFunction: (value: number) => void}
type ChildState = { anotherValue: number }
class Child extends React.Component<ChildProps, ChildState> {
    constructor(props: ChildProps) {
        super(props)
        this.state = { anotherValue: this.props.defaultValue }

        this.handleChange = this.handleChange.bind(this)
    }

    handleChange(value: number) {
        this.setState({...this.state, anotherValue: value})
        this.props.changeFunction(value)
    }

    render() {
        return <div>
            <input onChange={event => this.handleChange(Number(event.target.value))} type='number' value={this.state.anotherValue}/>
        </div>
    }
}

0

上面给出的大多数答案都是基于React.Component的设计。如果您正在使用useStateReact库的最新升级,请遵循以下答案


-3
<Footer 
  action={()=>this.setState({showChart: true})}
/>

<footer className="row">
    <button type="button" onClick={this.props.action}>Edit</button>
  {console.log(this.props)}
</footer>

Try this example to write inline setState, it avoids creating another function.

它会导致性能问题:stackoverflow.com/q/36677733/3328979
Arashsoft
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.