Reactjs: Wie ändere ich den Status einer dynamischen untergeordneten Komponente oder die Requisiten der übergeordneten Komponente?

89

Ich versuche im Wesentlichen, Tabs in Reaktion zu bringen, aber mit einigen Problemen.

Hier ist eine Datei page.jsx

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

Wenn Sie auf die Schaltfläche A klicken, muss die RadioGroup-Komponente die Schaltfläche B deaktivieren .

"Ausgewählt" bedeutet nur einen Klassennamen aus einem Status oder einer Eigenschaft

Hier ist 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>);
    }

});

Die Quelle von Button.jsxspielt keine Rolle, es gibt ein reguläres HTML-Optionsfeld, das das native DOM- onChangeEreignis auslöst

Der erwartete Durchfluss ist:

  • Klicken Sie auf die Schaltfläche "A"
  • Die Schaltfläche "A" löst das native DOM-Ereignis onChange aus, das an RadioGroup weitergeleitet wird
  • RadioGroup onChange Listener wird aufgerufen
  • RadioGroup muss die Schaltfläche B abwählen . Das ist meine Frage.

Hier ist das Hauptproblem , ich bin die Begegnung: Ich kann nicht mehr bewegen <Button>s inRadioGroup , weil die Struktur dieses so ist , dass die Kinder sind willkürlich . Das heißt, das Markup könnte sein

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

oder

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

Ich habe ein paar Dinge ausprobiert.

Versuch: In RadioGroup's onChange-Handler:

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

});

Problem:

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

Versuch: In RadioGroup's onChange-Handler:

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

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

});

Problem: Es passiert nichts, auch wenn ich der ButtonKlasse eine componentWillReceivePropsMethode gebe


Versuch: Ich habe versucht, einen bestimmten Status des Elternteils an die Kinder zu übergeben, sodass ich einfach den übergeordneten Status aktualisieren und die Kinder automatisch antworten lassen kann. In der Renderfunktion von RadioGroup:

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

Problem:

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.

Schlechte Lösung Nr. 1 : Verwenden Sie die cloneWithProps- Methode von react-addons.js , um die untergeordneten Elemente beim Rendern in zu klonenRadioGroup ihnen Eigenschaften zu übergeben

Schlechte Lösung Nr. 2 : Implementieren Sie eine Abstraktion um HTML / JSX, damit ich die Eigenschaften dynamisch übergeben kann (töte mich):

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

Und dann rein RadioGroup bauen Sie diese Schaltflächen dynamisch auf.

Diese Frage hilft mir nicht, weil ich meine Kinder rendern muss, ohne zu wissen, was sie sind

Andy Ray
quelle
Wenn die Kinder willkürlich sein können, wie könnte dann der RadioGroupmöglicherweise wissen, dass er auf ein Ereignis seiner willkürlichen Kinder reagieren muss? Es muss unbedingt etwas über seine Kinder wissen.
Ross Allen
1
Wenn Sie die Eigenschaften einer Komponente ändern möchten, die Sie nicht besitzen, klonen Sie sie im Allgemeinen mit React.addons.cloneWithPropsund übergeben Sie die neuen Requisiten , die Sie haben möchten . propssind unveränderlich, also erstellen Sie einen neuen Hash, führen ihn mit den aktuellen Requisiten zusammen und übergeben den neuen Hash propsan eine neue Instanz der untergeordneten Komponente.
Ross Allen

Antworten:

41

Ich bin mir nicht sicher, warum Sie sagen, dass die Verwendung cloneWithPropseine schlechte Lösung ist, aber hier ist ein funktionierendes Beispiel für die Verwendung.

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

Hier ist eine jsFiddle die es in Aktion zeigt.

BEARBEITEN : Hier ist ein vollständigeres Beispiel mit dynamischem Tab-Inhalt: jsFiddle

Claude Précourt
quelle
15
Hinweis: cloneWithProps ist veraltet. Verwenden Sie stattdessen React.cloneElement.
Chen-Tsu Lin
8
Dies ist so unglaublich kompliziert, etwas zu tun, was D3 / Jquery nur mit einem: nicht-Selektor tun könnte this. Gibt es einen wirklichen Vorteil, außer React verwendet zu haben?
Lernstatistiken
Zu Ihrer Information: Dies ist nicht "Aktualisieren des untergeordneten Status vom übergeordneten Status", sondern das untergeordnete Aktualisieren des übergeordneten Status, um den untergeordneten Status zu aktualisieren. Hier gibt es einen Unterschied in der Aussprache. Für den Fall, dass dies die Leute verwirrt.
sksallaj
1
@Incodeveritas Sie haben Recht, Geschwisterbeziehung, Eltern-Kind und Kind-Eltern ist ein bisschen kompliziert. Dies ist eine modulare Vorgehensweise. Aber denken Sie daran, JQuery ist ein schneller Hack, um Dinge zu erledigen. ReactJS hält die Dinge gemäß einer Orchestrierung.
sksallaj
18

Die Tasten sollten zustandslos sein. Anstatt die Eigenschaften einer Schaltfläche explizit zu aktualisieren, aktualisieren Sie einfach den eigenen Status der Gruppe und rendern Sie erneut. Die Rendermethode der Gruppe sollte dann beim Rendern der Schaltflächen ihren Status überprüfen und "aktiv" (oder etwas anderes) nur an die aktive Schaltfläche übergeben.

Rygu
quelle
Zum Erweitern sollte die Schaltfläche onClick oder andere relevante Methoden vom übergeordneten Element aus festgelegt werden, damit jede Aktion das übergeordnete Element zurückruft, um dort den Status festzulegen, nicht in der Schaltfläche selbst. Auf diese Weise ist die Schaltfläche nur eine zustandslose Darstellung des aktuellen Status der Eltern.
David Mårtensson
6

Vielleicht ist meine eine seltsame Lösung, aber warum nicht Beobachtermuster verwenden?

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

});

Vielleicht benötigen Sie etwas anderes, aber ich fand das sehr mächtig,

Ich bevorzuge es wirklich, ein äußeres Modell zu verwenden, das Beobachterregistermethoden für verschiedene Aufgaben bereitstellt

Daniele Cruciani
quelle
Ist es nicht eine schlechte Form für einen Knoten, setState explizit auf einem anderen Knoten aufzurufen? Wäre es reaktiver, einen Status in der RadioGroup zu aktualisieren und einen weiteren Renderpass auszulösen, in dem Sie die Requisiten der Nachkommen nach Bedarf aktualisieren können?
Qix
1
Ausdrücklich? Nein, Beobachter rufen einfach einen Rückruf auf, es ist zufällig der setState, aber tatsächlich hätte ich this.setState.bind (this) angeben und so ausschließlich an sich selbst binden können. Ok, wenn ich ehrlich sein müsste, hätte ich eine lokale Funktion / Methode definieren sollen, die setState aufruft und sie im Beobachter registriert
Daniele Cruciani
1
Lese ich das falsch oder haben Sie zwei Felder mit demselben Namen in einem Objekt für Button.jsx?
JohnK
Button.jsx enthält onChange()undonChange(e)
Josh
Entfernen Sie die erste, sie wird aus der Frage ausgeschnitten und eingefügt (erste Frage). Es ist nicht der Punkt, dies ist kein funktionierender Code, und es wäre auch nicht so.
Daniele Cruciani
0

Erstellen Sie ein Objekt, das als Vermittler zwischen Eltern und Kind fungiert. Dieses Objekt enthält Funktionsreferenzen sowohl im übergeordneten als auch im untergeordneten Objekt. Das Objekt wird dann als Requisite vom Elternteil an das Kind übergeben. Codebeispiel hier:

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

AndroidDev
quelle