Reactjs: jak zmodyfikować stan dynamicznego komponentu potomnego lub właściwości rodzica?


90

Zasadniczo staram się, aby karty reagowały, ale z pewnymi problemami.

Oto plik page.jsx

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

Po kliknięciu w przycisk A, potrzeby elementów RadioGroup de-wybierz przycisk B .

„Wybrane” oznacza po prostu nazwę klasy ze stanu lub właściwości

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

});

Źródło Button.jsxnie ma tak naprawdę znaczenia, ma zwykły przycisk opcji HTML, który wyzwala natywne onChangezdarzenie DOM

Oczekiwany przepływ to:

  • Kliknij przycisk „A”
  • Przycisk „A” uruchamia natywne zdarzenie onChange DOM, które przechodzi do RadioGroup
  • Wywołuje się odbiornik RadioGroup onChange
  • RadioGroup musi de-wybierz przycisk B . To jest moje pytanie.

Oto główny problem mam napotykając: I nie można przenieść <Button>s doRadioGroup , ponieważ struktura tego jest taki, że dzieci są arbitralne . Oznacza to, że znaczniki mogą być

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

lub

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

Próbowałem kilku rzeczy.

Próba: W RadioGroup„s onChange obsługi:

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(...)

Próba: W RadioGroup„s onChange obsługi:

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

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

});

Problem: Nic się nie dzieje, nawet ja daję Buttonklasie componentWillReceivePropsmetodę


Próba: próbowałem przekazać dzieciom określony stan rodzica, więc mogę po prostu zaktualizować stan rodzica i sprawić, by dzieci zareagowały automatycznie. W funkcji renderowania 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.

Złe rozwiązanie nr 1 : użyj metody reag-addons.js cloneWithProps , aby sklonować elementy podrzędne w czasie renderowania, RadioGroupaby móc przekazać im właściwości

Złe rozwiązanie nr 2 : Zaimplementuj abstrakcję wokół HTML / JSX, aby móc dynamicznie przekazywać właściwości (zabij mnie):

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

A następnie RadioGroupdynamicznie buduj te przyciski.

To pytanie nie pomaga, ponieważ muszę wydać moje dzieci, nie wiedząc, czym one są


Jeśli dzieci mogą być arbitralne, to skąd RadioGroupmoże wiedzieć, że musi zareagować na zdarzenie swoich arbitralnych dzieci? Musi koniecznie wiedzieć coś o swoich dzieciach.
Ross Allen,

1
Ogólnie rzecz biorąc, jeśli chcesz zmodyfikować właściwości komponentu, którego nie jesteś właścicielem, sklonuj go za pomocą React.addons.cloneWithPropsi przekaż nowe właściwości, które chcesz, aby miał. propssą niezmienne, więc tworzysz nowy hash, scalasz go z bieżącymi właściwościami i przekazujesz nowy hash jako propsnową instancję składnika potomnego.
Ross Allen,

Odpowiedzi:


42

Nie jestem pewien, dlaczego mówisz, że używanie cloneWithPropsjest złym rozwiązaniem, ale oto działający przykład z jego użyciem.

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

Oto jsFiddle pokazujący to w akcji.

EDYCJA : oto bardziej kompletny przykład z dynamiczną zawartością karty: jsFiddle


15
Uwaga: cloneWithProps jest przestarzałe. Zamiast tego użyj React.cloneElement.
Chen-Tsu Lin

8
Jest to tak niewiarygodnie skomplikowane, aby zrobić coś, co D3 / Jquery mogłoby zrobić z włączonym tylko selektorem: not this. Czy jest jakaś realna korzyść poza używaniem React?
Statystyki uczenia się na przykładzie

FYI .. to nie jest „aktualizowanie stanu dzieci ze stanu nadrzędnego”, to jest aktualizowanie stanu potomnego w celu zaktualizowania stanu dzieci. Tutaj jest różnica w słowie. Na wypadek, gdyby to zmyliło ludzi.
sksallaj

1
@Incodeveritas masz rację, związek rodzeństwa, rodzic-dziecko i dziecko-rodzic jest nieco skomplikowany. To jest modułowy sposób robienia rzeczy. Ale pamiętaj, że JQuery to szybki hack do załatwiania spraw, ReactJS utrzymuje wszystko zgodnie z orkiestracją.
sksallaj

18

Przyciski powinny być bezpaństwowe. Zamiast jawnie aktualizować właściwości przycisku, po prostu zaktualizuj stan grupy i wyrenderuj ponownie. Metoda renderowania grupy powinna następnie sprawdzać jej stan podczas renderowania przycisków i przekazywać „aktywny” (lub coś takiego) tylko do aktywnego przycisku.


Aby rozwinąć, przycisk onClick lub inne odpowiednie metody powinny być ustawione od rodzica, tak aby każda akcja wywoływała z powrotem do rodzica, aby ustawić stan tam, a nie w samym przycisku. W ten sposób przycisk jest jedynie bezpaństwową reprezentacją aktualnego stanu rodziców.
David Mårtensson

6

Może moje jest dziwne rozwiązanie, ale dlaczego nie użyć wzorca obserwatora?

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

});

może potrzebujesz czegoś innego, ale uznałem to za bardzo potężne,

Naprawdę wolę używać modelu zewnętrznego, który zapewnia metody rejestracji obserwatorów dla różnych zadań


Czy nie jest złą formą, aby węzeł jawnie wywoływał funkcję setState w innym węźle? Czy bardziej reaktywnym sposobem byłoby zaktualizowanie jakiegoś stanu w RadioGroup, wyzwolenie kolejnego przejścia renderowania, w którym można by w razie potrzeby zaktualizować rekwizyty potomków?
qix

1
Wyraźnie? Nie, obserwator po prostu wywołuje wywołanie zwrotne, tak się składa, że ​​jest to setState, ale w rzeczywistości mogłem określić this.setState.bind (this) i być wyłącznym bindend do siebie. Ok, gdybym miał być szczery, powinienem był zdefiniować lokalną funkcję / metodę, która wywołuje setState i zarejestrować ją w obserwatorze
Daniele Cruciani

1
Czy źle to czytam, czy masz dwa pola o tej samej nazwie w jednym obiekcie Button.jsx?
JohnK

Button.jsx zawiera onChange()ionChange(e)
Josh

usuń pierwszą, jest to wytnij i wklej z pytania (pierwsze pytanie). Nie o to chodzi, to nie jest działający kod, ani nie byłoby.
Daniele Cruciani

0

Utwórz obiekt, który będzie pośrednikiem między rodzicem a dzieckiem. Ten obiekt zawiera odwołania do funkcji w obiektach nadrzędnych i podrzędnych. Obiekt jest następnie przekazywany jako rekwizyt przez rodzica dziecku. Przykład kodu tutaj:

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

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.