Reactjs: як змінити динамічний стан дочірнього компонента або реквізити від батьківського?


90

По суті, я намагаюся робити вкладки як реакцію, але з деякими проблемами.

Ось файл page.jsx

<RadioGroup>
    <Button title="A" />
    <Button title="B" />
</RadioGroup>

При натисканні на кнопку A, потреба RadioGroup компонент для скасування вибору кнопки B .

"Вибране" просто означає className із стану або властивості

Ось RadioGroup.jsx:

module.exports = React.createClass({

    onChange: function( e ) {
        // How to modify children properties here???
    },

    render: function() {
        return (<div onChange={this.onChange}>
            {this.props.children}
        </div>);
    }

});

Джерело Button.jsxнасправді не має значення, у нього є звичайний перемикач HTML, який запускає власну onChangeподію DOM

Очікуваний потік:

  • Натисніть кнопку "А"
  • Кнопка "А" запускає onChange, власну подію DOM, яка переходить до RadioGroup
  • Викликається слухач RadioGroup onChange
  • RadioGroup потрібно скасувати вибір кнопки B . Це моє запитання.

Ось основна проблема, з якою я стикаюся: я не можу перейти до <Button>sRadioGroup , оскільки структура цього така, що діти є довільними . Тобто розмітка могла бути

<RadioGroup>
    <Button title="A" />
    <Button title="B" />
</RadioGroup>

або

<RadioGroup>
    <OtherThing title="A" />
    <OtherThing title="B" />
</RadioGroup>

Я спробував кілька речей.

Спроба: В RadioGroupобробник OnChange «s:

React.Children.forEach( this.props.children, function( child ) {

    // Set the selected state of each child to be if the underlying <input>
    // value matches the child's value

    child.setState({ selected: child.props.value === e.target.value });

});

Проблема:

Invalid access to component property "setState" on exports at the top
level. See react-warning-descriptors . Use a static method
instead: <exports />.type.setState(...)

Спроба: В RadioGroupобробник OnChange «s:

React.Children.forEach( this.props.children, function( child ) {

    child.props.selected = child.props.value === e.target.value;

});

Проблема: Нічого не відбувається, навіть я даю Buttonкласу componentWillReceivePropsметод


Спроба: Я намагався передати певний стан батьків батьків дітям, щоб я міг просто оновити батьківський стан і мати автоматичну відповідь дітей. У функції рендеринга RadioGroup:

React.Children.forEach( this.props.children, function( item ) {
    this.transferPropsTo( item );
}, this);

Проблема:

Failed to make request: Error: Invariant Violation: exports: You can't call
transferPropsTo() on a component that you don't own, exports. This usually
means you are calling transferPropsTo() on a component passed in as props
or children.

Погане рішення # 1 : Використовуйте метод response-addons.js cloneWithProps, щоб клонувати дітей під час відтворення, RadioGroupщоб мати можливість передавати їм властивості

Погане рішення # 2 : Реалізуйте абстракцію навколо HTML / JSX, щоб я міг динамічно передавати властивості (вбити мене):

<RadioGroup items=[
    { type: Button, title: 'A' },
    { type: Button, title: 'B' }
]; />

А потім в RadioGroupдинамічно будувати ці кнопки.

Це запитання мені не допомагає, тому що мені потрібно виховувати своїх дітей, не знаючи, що вони є


Якщо діти можуть бути довільними, то як, RadioGroupможливо, знати, що йому потрібно реагувати на подію своїх довільних дітей? Вона обов’язково повинна щось знати про своїх дітей.
Росс Аллен,

1
Загалом, якщо ви хочете змінити властивості компонента, яким ви не володієте, клонуйте його за допомогою React.addons.cloneWithPropsта передайте новий реквізит, який ви хотіли б мати. propsє незмінними, тож ви створюєте новий хеш, зливаючи його з поточним реквізитом та передаючи новий хеш propsу новий екземпляр дочірнього компонента.
Росс Аллен,

Відповіді:


42

Я не впевнений, чому ви говорите, що використання cloneWithProps- це погане рішення, але ось робочий приклад його використання.

var Hello = React.createClass({
    render: function() {
        return <div>Hello {this.props.name}</div>;
    }
});

var App = React.createClass({
    render: function() {
        return (
            <Group ref="buttonGroup">
                <Button key={1} name="Component A"/>
                <Button key={2} name="Component B"/>
                <Button key={3} name="Component C"/>
            </Group>
        );
    }
});

var Group = React.createClass({
    getInitialState: function() {
        return {
            selectedItem: null
        };
    },

    selectItem: function(item) {
        this.setState({
            selectedItem: item
        });
    },

    render: function() {
        var selectedKey = (this.state.selectedItem && this.state.selectedItem.props.key) || null;
        var children = this.props.children.map(function(item, i) {
            var isSelected = item.props.key === selectedKey;
            return React.addons.cloneWithProps(item, {
                isSelected: isSelected,
                selectItem: this.selectItem,
                key: item.props.key
            });
        }, this);

        return (
            <div>
                <strong>Selected:</strong> {this.state.selectedItem ? this.state.selectedItem.props.name : 'None'}
                <hr/>
                {children}
            </div>
        );
    }

});

var Button = React.createClass({
    handleClick: function() {
        this.props.selectItem(this);
    },

    render: function() {
        var selected = this.props.isSelected;
        return (
            <div
                onClick={this.handleClick}
                className={selected ? "selected" : ""}
            >
                {this.props.name} ({this.props.key}) {selected ? "<---" : ""}
            </div>
        );
    }

});


React.renderComponent(<App />, document.body);

Ось jsFiddle, який показує його в дії.

EDIT : ось більш повний приклад із вмістом динамічної вкладки: jsFiddle


15
Примітка: cloneWithProps застаріло. Замість цього використовуйте React.cloneElement.
Chen-Tsu Lin

8
Це настільки неймовірно складно зробити щось, що D3 / Jquery міг би зробити, просто увімкнувши селектор: not this. Чи є якась реальна перевага, крім використання React?
Вивчення статистики на прикладі

FYI .. це не "оновлення дочірнього стану від батьківського стану", це дочірнє оновлення батьківського стану з метою оновлення дочірнього стану. Тут є різниця у вербежі. На випадок, якщо це бентежить людей.
sksallaj

1
@Incodeveritas ви маєте рацію, стосунки між братами та сестрами, батько-дитина та дитина-батько трохи складні. Це модульний спосіб робити щось. Але майте на увазі, JQuery - це швидкий хак для того, щоб щось зробити, ReactJS зберігає речі згідно з оркестрацією.
sksallaj

18

Кнопки повинні бути без громадянства. Замість того, щоб явно оновлювати властивості кнопки, просто оновіть власний стан Групи та повторно рендеріть. Потім метод візуалізації Групи повинен переглядати його стан при отриманні кнопок і передавати "активний" (або щось інше) лише активній кнопці.


Щоб розгорнути, кнопку onClick або інші відповідні методи слід встановити від батьківського елемента, щоб будь-яка дія викликала назад батьківський статус для встановлення там стану, а не в самій кнопці. Таким чином, кнопка - це лише подання без стану громадян поточного стану батьків.
Девід Мортенссон,

6

Можливо, моє дивне рішення, але чому б не використовувати шаблон спостерігача?

RadioGroup.jsx

module.exports = React.createClass({
buttonSetters: [],
regSetter: function(v){
   buttonSetters.push(v);
},
handleChange: function(e) {
   // ...
   var name = e.target.name; //or name
   this.buttonSetters.forEach(function(v){
      if(v.name != name) v.setState(false);
   });
},
render: function() {
  return (
    <div>
      <Button title="A" regSetter={this.regSetter} onChange={handleChange}/>
      <Button title="B" regSetter={this.regSetter} onChange={handleChange} />
    </div>
  );
});

Button.jsx

module.exports = React.createClass({

    onChange: function( e ) {
        // How to modify children properties here???
    },
    componentDidMount: function() {
         this.props.regSetter({name:this.props.title,setState:this.setState});
    },
    onChange:function() {
         this.props.onChange();
    },
    render: function() {
        return (<div onChange={this.onChange}>
            <input element .../>
        </div>);
    }

});

можливо, вам потрібно щось інше, але я знайшов це дуже потужне,

Я справді волію використовувати зовнішню модель, яка надає методи реєстрації спостерігачів для різних завдань


Хіба це не погана форма для того, щоб вузол явно викликав setState на іншому вузлі? Чи був би більш реактивним способом оновлення деякого стану на RadioGroup, викликаючи черговий прохід рендерингу, в якому ви могли б оновлювати реквізити нащадків за необхідності?
qix

1
Явно? Ні, спостерігач просто викликає зворотний виклик, це буває setState, але насправді я міг би вказати this.setState.bind (this) і, таким чином, бути виключно прив'язаним до себе. Гаразд, якщо я повинен був бути чесним, я мав би визначити локальну функцію / метод, який викликає setState, та зареєструвати його у спостерігачі
Даніеле Кручані

1
Я читаю це неправильно, чи у вас є два поля з однаковим ім'ям в одному об'єкті для Button.jsx?
JohnK

Button.jsx включає onChange()іonChange(e)
Джош

видалити перший, його вирізати та вставити з питання (перше питання). Справа не в цьому, це не робочий код, і він також не був би.
Daniele Cruciani

0

Створіть об’єкт, який виконує роль посередника між батьком та дитиною. Цей об'єкт містить посилання на функції як батьківського, так і дочірнього. Потім об’єкт передається як допомога від батьків до дитини. Приклад коду тут:

https://stackoverflow.com/a/61674406/753632

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