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

91

Я по сути пытаюсь сделать вкладки в реакции, но с некоторыми проблемами.

Вот файл 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

Ожидаемый поток:

  • Нажмите кнопку "A"
  • Кнопка «A» запускает 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.
Чен-Цу Линь
8
Это невероятно сложно сделать что-то, что D3 / JQuery мог бы сделать, используя только селектор: not this. Есть ли какое-то реальное преимущество, кроме использования React?
Статистика обучения на примере
FYI ... это не «обновление дочернего состояния из родительского состояния», это дочернее обновление родительского состояния для обновления дочернего состояния. Здесь есть разница в словах. В случае, если это смущает людей.
sksallaj
1
@Incodeveritas, вы правы, родственные отношения, родитель-ребенок и ребенок-родитель немного сложны. Это модульный способ работы. Но имейте в виду, что JQuery - это быстрое средство для достижения цели, ReactJS хранит вещи в соответствии с оркестровкой.
sksallaj
18

Кнопки не должны иметь состояния. Вместо того, чтобы явно обновлять свойства кнопки, просто обновите собственное состояние группы и выполните повторную визуализацию. Затем метод рендеринга группы должен смотреть на свое состояние при рендеринге кнопок и передавать «активный» (или что-то еще) только активной кнопке.

Рыгу
источник
Чтобы развернуть, кнопка onClick или другие соответствующие методы должны быть установлены из родительского элемента, чтобы любое действие возвращалось в родительский элемент для установки состояния там, а не в самой кнопке. Таким образом, кнопка является просто представлением текущего состояния родителей без сохранения состояния.
David Mårtensson
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)
Джош
удалите первый, это вырезано и вставлено из вопроса (первый вопрос). Дело не в этом, это не рабочий код, да и не было бы.
Даниэле
0

Создайте объект, который действует как посредник между родителем и потомком. Этот объект содержит ссылки на функции как в родительском, так и в дочернем. Затем объект передается как опора от родителя к потомку. Пример кода здесь:

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

AndroidDev
источник