Как обновить состояние родителя в React?

204

Моя структура выглядит следующим образом:

Component 1  

 - |- Component 2


 - - |- Component 4


 - - -  |- Component 5  

Component 3

Компонент 3 должен отображать некоторые данные в зависимости от состояния компонента 5. Поскольку реквизит неизменен, я не могу просто сохранить его в компоненте 1 и переслать его, правильно? И да, я читал о редуксе, но не хочу его использовать. Надеюсь, что это можно решить, просто отреагируя. Я не прав?

  • 5
    super-easy: передать parent-setState-Function через свойство дочернему компоненту: <MyChildComponent setState = {p => {this.setState (p)}} /> В дочернем компоненте вызовите его через this.props. SetState ({myObj, ...});
  • 0
    @MarcelEnnix, Ваш комментарий сэкономит мое время. Благодарю.
Теги:
react-native
web-deployment

8 ответов

405
Лучший ответ

Для общения между родителями и детьми вы должны передать функцию, устанавливающую состояние от родителя к ребенку, например:

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

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

  handler() {
    this.setState({
      someVar: someValue
    })
  }

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

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

Таким образом, ребенок может обновить родительское состояние с помощью вызова функции, переданной с помощью реквизита.

Но вам придется переосмыслить структуру ваших компонентов, потому что, как я понимаю, компоненты 5 и 3 не связаны между собой.

Одно из возможных решений - заключить их в компонент более высокого уровня, который будет содержать состояние обоих компонентов 1 и 3. Этот компонент будет устанавливать состояние более низкого уровня с помощью подпорок.

  • 5
    зачем вам this.handler = this.handler.bind (this), а не только функция-обработчик, которая устанавливает состояние?
  • 22
    @ chemook78 в ES6 Методы классов React не привязываются к классу автоматически. Поэтому, если мы не добавим this.handler = this.handler.bind(this) в конструктор, this внутри функции- handler будет ссылаться на замыкание функции, а не на класс. Если вы не хотите связывать все свои функции в конструкторе, есть еще два способа справиться с этим, используя функции стрелок. Вы можете просто написать обработчик клика как onClick={()=> this.setState(...)} , или вы можете использовать инициализаторы свойств вместе с функциями стрелок, как описано здесь babeljs.io/blog/2015/06/07/ response-on-es6-plus в разделе «Функции стрелок»
Показать ещё 10 комментариев
32

Я нашел следующее рабочее решение для передачи аргумента функции onClick от дочернего к родительскому компоненту:

Версия с передачей метода()

//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

Версия с передачей функции Arrow

//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

  • 0
    Это хорошо! Не могли бы вы объяснить этот кусок: <ChildB handleToUpdate = {handleToUpdate.bind(this)} /> Почему пришлось снова связываться?
  • 0
    @Dane - вы должны связать контекст это быть родителем , так что , когда this вызывается внутри ребенка, this относится к состоянию родителей , а не ребенка. Это закрытие во всей красе!
Показать ещё 4 комментария
6

Мне нравится ответ о передаче функций вокруг, его очень удобный метод.

С обратной стороны вы также можете добиться этого, используя pub/sub или используя вариант, диспетчер, как Flux. Теория супер проста, компонент 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 с отправкой, просто сообщение Я бы). Вы можете легко адаптировать его к традиционному паб/суб (например, с помощью EventEmitter из событий или какой-либо другой версии), если это имеет для вас больше смысла.

  • 0
    Мои компоненты Reacts «работают» в браузере, как в официальном учебнике ( facebook.github.io/react/docs/tutorial.html ). Я попытался включить Flux в browserify, но браузер сообщает, что Dispatcher не найден :(
  • 2
    Синтаксис, который я использовал, был синтаксис модуля ES2016, который требует транспиляции (я использую Babel , но есть и другие, трансформацию babelify можно использовать с browserify), она переносится в var Dispatcher = require( 'flux' ).Dispatcher
4

Я нашел следующее рабочее решение для передачи аргумента функции 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
    Может кто-нибудь сказать, является ли это приемлемым решением (особенно интересует передача параметра, как предложено).
2

Когда вам требуется установить связь между дочерним и родительским на любом уровне вниз, лучше использовать контекст. В родительском компоненте определяют контекст, который может быть вызван дочерним элементом, например

В родительском компоненте в вашем случае компонент 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>
)}

вы можете найти демонстрационный проект repo

1

-We может создать ParentComponent и с помощью метода handleInputChange для обновления состояния ParentComponent. Импортируйте ChildComponent, и мы передаем два реквизита от родительского к дочернему компоненту, т.е. функции и count.handleInputChange.

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 для проверки типов реквизита.

    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;
    
  • 0
    Как это работает, когда у ребенка есть ребенок, который влияет на опору его родителя?
0

Я хочу поблагодарить наиболее одобренный ответ за то, что он дал мне представление о моей собственной проблеме, в основном, о ее изменении с помощью функции стрелки и передачи параметра из дочернего компонента:

 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')}/ >
  }
}

Надеюсь, это поможет кому-то.

-2
<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.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню