Reactjs: bagaimana cara mengubah status komponen anak dinamis atau props dari induk?

90

Saya pada dasarnya mencoba membuat tab bereaksi, tetapi dengan beberapa masalah.

Ini file page.jsx

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

Ketika Anda mengklik tombol A, kebutuhan komponen RadioGroup ke tombol B de-pilih .

"Dipilih" berarti nama kelas dari negara bagian atau properti

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

});

Sumber dari Button.jsxtidak terlalu penting, ia memiliki tombol radio HTML biasa yang memicu onChangekejadian DOM asli

Aliran yang diharapkan adalah:

  • Klik pada Tombol "A"
  • Tombol "A" memicu onChange, peristiwa DOM asli, yang melembung ke RadioGroup
  • Pendengar onChange RadioGroup dipanggil
  • RadioGroup perlu de-pilih tombol B . Ini pertanyaan saya.

Inilah masalah utama saya hadapi: Saya tidak bisa bergerak <Button>s keRadioGroup , karena struktur ini adalah sedemikian rupa sehingga anak-anak yang sewenang-wenang . Artinya, markup bisa jadi

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

atau

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

Saya sudah mencoba beberapa hal.

Percobaan: Dalam RadioGrouppenangan onChange:

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

});

Masalah:

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

Percobaan: Dalam RadioGrouppenangan onChange:

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

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

});

Masalah: Tidak ada yang terjadi, bahkan saya memberikan metode kepada ButtonkelascomponentWillReceiveProps


Percobaan: Saya mencoba meneruskan beberapa keadaan tertentu dari orang tua ke anak-anak, jadi saya bisa memperbarui keadaan induk dan meminta anak-anak merespons secara otomatis. Dalam fungsi render RadioGroup:

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

Masalah:

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.

Solusi buruk # 1 : Gunakan metode react-addons.js cloneWithProps untuk mengkloning anak-anak pada waktu render RadioGroupagar dapat meneruskan properti kepada mereka

Solusi buruk # 2 : Terapkan abstraksi di sekitar HTML / JSX sehingga saya dapat meneruskan properti secara dinamis (bunuh saya):

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

Dan kemudian RadioGroupsecara dinamis membangun tombol-tombol ini.

Pertanyaan ini tidak membantu saya karena saya perlu membuat anak-anak saya tanpa mengetahui apa mereka

Andy Ray
sumber
Jika anak-anak dapat menjadi sewenang-wenang, lalu bagaimana RadioGroupmungkin yang tahu itu perlu bereaksi terhadap peristiwa anak-anaknya yang sewenang-wenang? Ia harus mengetahui sesuatu tentang anak-anaknya.
Ross Allen
1
Secara umum, jika Anda ingin mengubah properti komponen yang tidak Anda miliki, klon menggunakan React.addons.cloneWithPropsdan berikan props baru yang Anda inginkan. propstidak dapat diubah, jadi Anda membuat hash baru, menggabungkannya dengan props saat ini, dan meneruskan hash baru tersebut sebagai propsinstance baru dari komponen anak.
Ross Allen

Jawaban:

42

Saya tidak yakin mengapa Anda mengatakan bahwa menggunakan cloneWithPropsadalah solusi yang buruk, tetapi berikut adalah contoh cara menggunakannya.

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

Ini jsFiddle yang menunjukkan aksinya.

EDIT : inilah contoh yang lebih lengkap dengan konten tab dinamis: jsFiddle

Claude Précourt
sumber
15
Catatan: cloneWithProps tidak digunakan lagi. Gunakan React.cloneElement sebagai gantinya.
Chen-Tsu Lin
8
Ini sangat rumit untuk melakukan sesuatu yang D3 / Jquery dapat lakukan hanya dengan a: not selector on this. Apakah ada keuntungan nyata selain menggunakan React?
Statistik pembelajaran dengan contoh
FYI .. ini bukan "memperbarui status anak dari keadaan induk", ini adalah anak memperbarui keadaan induk untuk memperbarui keadaan anak. Ada perbedaan verbage di sini. Dalam hal ini membingungkan orang.
sksallaj
1
@Incodeveritas Anda benar, hubungan saudara, orang tua-anak, dan anak-orang tua agak rumit. Ini adalah cara modular dalam melakukan sesuatu. Namun perlu diingat JQuery adalah peretasan cepat untuk menyelesaikan sesuatu, ReactJS menyimpan berbagai hal sesuai dengan orkestrasi.
sksallaj
18

Tombol harus tanpa kewarganegaraan. Alih-alih memperbarui properti tombol secara eksplisit, cukup perbarui status Grup dan render ulang. Metode render Grup kemudian harus melihat statusnya saat merender tombol dan meneruskan "aktif" (atau sesuatu) hanya ke tombol aktif.

Rygu
sumber
Untuk meluaskan, tombol onClick atau metode lain yang relevan harus disetel dari induk sehingga setiap tindakan memanggil kembali ke induk untuk menyetel status di sana, bukan di tombol itu sendiri. Dengan begitu, tombol tersebut hanyalah representasi tanpa kewarganegaraan dari keadaan orang tua saat ini.
David Mårtensson
6

Mungkin solusi saya adalah solusi yang aneh, tetapi mengapa tidak menggunakan pola pengamat?

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

});

mungkin Anda memerlukan sesuatu yang lain, tapi menurut saya ini sangat kuat,

Saya lebih suka menggunakan model luar yang menyediakan metode register pengamat untuk berbagai tugas

Daniele Cruciani
sumber
Bukankah itu bentuk yang buruk untuk sebuah node secara eksplisit memanggil setState pada node lain? Apakah cara yang lebih reaktif adalah memperbarui beberapa status di RadioGroup, memicu render pass lain di mana Anda dapat memperbarui properti turunan seperlunya?
qix
1
Secara eksplisit? Tidak, pengamat hanya memanggil callback, itu kebetulan setState, tetapi sebenarnya saya bisa menetapkan this.setState.bind (this) dan karenanya mengikat secara eksklusif ke dirinya sendiri. Ok, jika saya harus jujur, saya harus mendefinisikan fungsi / metode lokal yang memanggil setState dan mendaftarkannya di pengamat
Daniele Cruciani
1
Apakah saya salah membaca ini, atau apakah Anda memiliki dua bidang dengan nama yang sama dalam satu objek Button.jsx?
JohnK
Button.jsx termasuk onChange()danonChange(e)
Josh
hapus yang pertama, itu potong & tempel dari pertanyaan (pertanyaan pertama). Bukan itu intinya, ini bukan kode yang berfungsi, juga bukan.
Daniele Cruciani
0

Buat objek yang bertindak sebagai perantara antara orang tua dan anak. Objek ini berisi referensi fungsi baik di induk maupun anak. Objek tersebut kemudian diteruskan sebagai prop dari induk ke anaknya. Contoh kode di sini:

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

AndroidDev
sumber