Як оновити стан батьків у React?


351

Моя структура виглядає так:

Component 1  

 - |- Component 2


 - - |- Component 4


 - - -  |- Component 5  

Component 3

Компонент 3 повинен відображати деякі дані залежно від стану компонента 5. Оскільки реквізит є незмінним, я не можу просто зберегти його стан у компоненті 1 і переслати його, правда? І так, я читав про скорочення, але не хочу його використовувати. Я сподіваюся, що це можна вирішити просто реагуючи. Я помиляюся?


20
супер-легко: передайте батьківський набірState-Function через властивість дочірньому компоненту: <MyChildComponent setState = {p => {this.setState (p)}} /> У дочірньому компоненті викликайте його через this.props. setState ({myObj, ...});
Марсель Еннікс

@MarcelEnnix, Ваш коментар економить мій час. Дякую.
Дініт Мінура

<MyChildComponent setState={(s,c)=>{this.setState(s, c)}} />якщо ви збираєтеся використовувати цей злом, переконайтеся, що ви підтримуєте зворотний дзвінок.
Barkermn01

4
Передача зворотного дзвінка для встановлення стану батьків - це дійсно погана практика, яка може призвести до проблем з технічним обслуговуванням. Він порушує інкапсуляцію, і це робить компоненти 2 4 і 5 щільно з'єднані з 1. Якщо пройти цей шлях, ви не зможете повторно використовувати жоден з цих дочірніх компонентів в іншому місці. Краще у вас є конкретні реквізити, щоб дочірні компоненти могли викликати події, коли щось трапляється, тоді батьківський компонент буде правильно поводитися з цією подією.
Пато Локо

@MarcelEnnix, чому фігурні дужки навколо this.setState(p)? Я спробував без них, і, схоже, це спрацює (я дуже новачок в React)
Біганон

Відповіді:


679

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

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

Таким чином дитина може оновити стан батьків при виклику функції, переданої з реквізитом.

Але вам доведеться переосмислити структуру своїх компонентів, тому що, як я розумію, компоненти 5 і 3 не пов'язані між собою.

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


6
навіщо вам потрібна this.handler = this.handler.bind (це), а не лише функція обробника, яка встановлює стан?
chemook78

35
@ chemook78 в ES6 Реактивні методи класів не автоматично прив'язуються до класу. Тож якщо ми не додамо this.handler = this.handler.bind(this)в конструктор, thisвсерединіhandler функції буде посилатися на закриття функції, а не на клас. Якщо ви не хочете пов'язувати всі свої функції в конструкторі, є ще два способи вирішити це за допомогою функцій стрілок. Ви можете просто написати обробник кліків як onClick={()=> this.setState(...)}, або ви можете використовувати ініціалізатори властивостей разом із функціями зі стрілками, як описано тут babeljs.io/blog/2015/06/07/react-on-es6-plus у розділі "Функції стрілки"
Іван

1
Ось приклад цього в дії: plnkr.co/edit/tGWecotmktae8zjS5yEr?p=preview
Tamb

5
Це все має сенс, чому e.preventDefault? а для цього потрібен jquery?
Вінсент Бускарелло

1
Швидке запитання, чи це забороняє присвоєння місцевої держави всередині дитини?
ЦеGuyCantEven

50

Я знайшов таке робоче рішення для передачі аргументу функції 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

Версія з передачею функції стрілки

//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 - ви повинні зв’язати контекст цього з батьком, щоб, коли thisвін викликається всередині дитини, thisстосувався стану батьків, а не дитини. Це закриття якнайкраще!
Кейсі

@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

@AshishKamble thisфункції стрілки вказує на контекст батьків (тобто Parentклас).
CPHPython

Це повторна відповідь. Ви можете додати коментар до прийнятої відповіді та згадати про цю експериментальну функцію, щоб використовувати стрілку у класі.
Arashsoft

10

Мені подобається відповідь щодо передачі функцій навколо, дуже зручна техніка.

З іншого боку, ви також можете досягти цього, використовуючи паб / суб або варіант, використовуючи диспетчер, як це робить 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 для подій чи іншу версію) дуже легко, якщо це має більше сенсу для вас.


Компоненти "Моїх реактів" "працюють" у браузері, як в офіційному підручнику ( facebook.github.io/react/docs/tutorial.html ) Я намагався включити Flux до браузера, але браузер говорить, що Dispatcher не знайдено :(
wklm

2
Синтаксис, який я використав, був синтаксисом модуля ES2016, який вимагає транспіляції (я використовую Babel , але є й інші, перетворення babelify можна використовувати за допомогою перегляду), він перекладається на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

Я не можу говорити про якість рішення, але це успішно передає параметр для мене.
Джеймс

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

5

Здається, що ми можемо передавати дані лише від батьків до дитини, оскільки реакція сприяє однонаправленому потоку даних, але для того, щоб зробити оновлення батьків, коли щось відбувається в його "дочірньому компоненті", ми зазвичай використовуємо те, що називається "функцією зворотного виклику".

Ми передаємо дитині функцію, визначену у батьків, як "реквізит" і викликаємо цю функцію від дитини, яка запускає її в батьківському компоненті.


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, я знайшов кращий спосіб зробити це, без прив’язки та без вбудованої функції всередині реквізитів.

Подивіться тут:

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

Більше ви можете прочитати тут . Сподіваюся, це комусь стане в нагоді =)


Це має для мене набагато більше сенсу. Дякую!
Бретт Роуберрі

3

-Ми можемо створити ParentComponent і за допомогою методу handleInputChange оновити стан ParentComponent. Імпортуємо ChildComponent, і ми передаємо два реквізити від батьківського до дочірнього компонента, тобто функцію.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. Цей компонент без стану, оскільки дочірній компонент не має стану. Ми використовуємо бібліотеку типів опор для перевірки типу реквізиту.

    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;

Як це працює, коли у дитини є дитина, що впливає на опору його батька?
Бобборт

3

Якщо цей самий сценарій не розповсюджений скрізь, ви можете використовувати контекст React, особливо якщо ви не хочете ввести всі накладні витрати, які вводять бібліотеки управління державою. Плюс до цього простіше вчитися. Але будьте обережні, ви можете цим перенапружитись і почати писати поганий код. В основному ви визначаєте компонент Container (який буде містити та зберігати цей фрагмент стану для вас), роблячи всі компоненти, зацікавлені в написанні / читанні цієї частини даних її дітям (не обов'язково направляти дітей)

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

Ви також можете замість цього використати звичайний React.

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

передайте doSomething About5 до компонента 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 - атрибут із методом "обробник", пов'язаний з його екземпляром. ми передали обробник методу до компонента класу Child, щоб отримати через властивість onChange в аргументі реквізит.

Атрибут onChange буде встановлений у такому об’єкті реквізиту:

props ={
onChange : this.handler
}

і перейшов до дочірнього компонента

Таким чином, дочірній компонент може отримати доступ до значення імені в об'єкті реквізиту, як цей props.onChange

Це робиться за допомогою рендерінгу.

Тепер у дочірнього компонента є кнопка "Click" із подією onclick, встановлена ​​для виклику методу обробника, переданого йому через onChnge, в його об'єкт аргументу реквізиту. Отож тепер this.props.onChange in Child містить метод виведення у батьківському класі Посилання та кредити: Біти та шматки


Вибачте ... за затримку, тут onChange є атрибутом із методом "обробник", прив'язаним до його екземпляра. ми передали обробник методу до компонента класу Child, щоб отримати через властивість onChange в аргументі реквізит. Атрибут onChange буде встановлений у такому об’єкті реквізиту, як цей: props = {onChange: this.handler} і передається дочірньому компоненту. Таким чином, дочірній компонент може отримати доступ до значення імені в об'єкті реквізиту, як цей props.onChange Його виконано через використання рендери. Довідка та кредити: [ 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. Якщо ви використовуєте useStateв останніх оновленнях бібліотеки React, дотримуйтесь цієї відповіді


-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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.