Bagaimana cara mengakses keadaan anak di React?

214

Saya memiliki struktur berikut:

FormEditor- memegang beberapa FieldEditor FieldEditor- mengedit bidang formulir dan menyimpan berbagai nilai tentang hal itu dalam keadaan itu

Ketika sebuah tombol diklik di dalam FormEditor, saya ingin dapat mengumpulkan informasi tentang bidang dari semua FieldEditorkomponen, informasi yang dalam keadaan mereka, dan memiliki semuanya di dalam FormEditor.

Saya mempertimbangkan untuk menyimpan informasi tentang bidang di luar FieldEditornegara bagian dan meletakkannya FormEditordi negara bagian sebagai gantinya. Namun, itu perlu FormEditormendengarkan setiap FieldEditorkomponennya saat mereka mengubah dan menyimpan informasi mereka dalam keadaan itu.

Tidak bisakah saya mengakses status anak-anak saja? Apakah ini ideal?

Moshe Revah
sumber
4
"Tidak bisakah aku hanya mengakses kondisi anak-anak saja? Apakah ini ideal?" Tidak. Negara adalah sesuatu yang internal dan tidak boleh bocor ke luar. Anda dapat menerapkan metode accessor untuk komponen Anda, tetapi itu pun tidak ideal.
Felix Kling
@FelixKling Kemudian Anda menyarankan bahwa cara ideal untuk komunikasi anak ke orang tua hanya peristiwa?
Moshe Revah
3
Ya, acara adalah satu arah. Atau miliki aliran data satu arah seperti yang dipromosikan Flux: facebook.github.io/flux
Felix Kling
1
Jika Anda tidak akan menggunakan FieldEditorsecara terpisah, menyimpan statusnya dalam FormEditorsuara bagus. Jika ini masalahnya, mesin FieldEditorvirtual Anda akan dibuat berdasarkan yang propsdisahkan oleh editor formulir mereka, bukan milik mereka state. Cara yang lebih kompleks namun fleksibel adalah membuat serializer yang melewati semua wadah anak-anak dan menemukan semua FormEditorinstance di antara mereka dan membuat serial mereka menjadi objek JSON. Objek JSON dapat secara opsional bersarang (lebih dari satu tingkat) berdasarkan tingkat bersarang instance di editor formulir.
Meglio
4
Saya pikir React Docs 'Lifting State Up' mungkin cara yang paling 'Reacty' untuk melakukan ini
icc97

Jawaban:

135

Jika Anda sudah memiliki penangan onChange untuk masing-masing FieldEditors, saya tidak melihat mengapa Anda tidak bisa hanya memindahkan status ke komponen FormEditor dan hanya meneruskan panggilan balik dari sana ke FieldEditor yang akan memperbarui negara induk. Itu sepertinya cara yang lebih React-y untuk melakukannya, bagiku.

Sesuatu di sepanjang garis ini mungkin:

const FieldEditor = ({ value, onChange, id }) => {
  const handleChange = event => {
    const text = event.target.value;
    onChange(id, text);
  };

  return (
    <div className="field-editor">
      <input onChange={handleChange} value={value} />
    </div>
  );
};

const FormEditor = props => {
  const [values, setValues] = useState({});
  const handleFieldChange = (fieldId, value) => {
    setValues({ ...values, [fieldId]: value });
  };

  const fields = props.fields.map(field => (
    <FieldEditor
      key={field}
      id={field}
      onChange={handleFieldChange}
      value={values[field]}
    />
  ));

  return (
    <div>
      {fields}
      <pre>{JSON.stringify(values, null, 2)}</pre>
    </div>
  );
};

// To add abillity to dynamically add/remove fields keep the list in state
const App = () => {
  const fields = ["field1", "field2", "anotherField"];

  return <FormEditor fields={fields} />;
};

Asli - versi pra-kait:

Markus-ipse
sumber
2
Ini berguna untuk onChange, tetapi tidak sebanyak di onSubmit.
190290000 Ruble Man
1
@ 190290000RubleMan Saya tidak yakin apa yang Anda maksud, tetapi karena penangan onChange di masing-masing bidang memastikan bahwa Anda selalu memiliki data formulir lengkap yang tersedia di negara Anda di komponen FormEditor Anda, onSubmit Anda hanya akan mencakup sesuatu seperti myAPI.SaveStuff(this.state);. Jika Anda menguraikan lebih banyak, mungkin saya bisa memberikan jawaban yang lebih baik :)
Markus-ipse
Katakanlah saya memiliki formulir dengan 3 bidang tipe yang berbeda, masing-masing dengan validasinya sendiri. Saya ingin memvalidasi masing-masing sebelum mengirim. Jika validasi gagal, setiap bidang yang memiliki kesalahan harus dirender ulang. Saya belum menemukan cara untuk melakukan ini dengan solusi yang Anda usulkan, tetapi mungkin ada caranya!
190290000 Ruble Man
1
@ 190290000RubleMan Sepertinya kasus yang berbeda dari pertanyaan aslinya. Buka pertanyaan baru dengan rincian lebih lanjut dan mungkin beberapa kode sampel, dan saya dapat melihatnya nanti :)
Markus-ipse
Anda mungkin menemukan jawaban ini berguna : ia menggunakan kait negara, mencakup di mana negara harus dibuat, bagaimana cara menghindari formulir re-render pada setiap keystroke, cara melakukan validasi lapangan, dll.
Andrew
189

Tepat sebelum saya masuk ke detail tentang bagaimana Anda dapat mengakses status komponen anak, pastikan untuk membaca jawaban Markus-ipse mengenai solusi yang lebih baik untuk menangani skenario khusus ini.

Jika Anda memang ingin mengakses status anak-anak komponen, Anda dapat menetapkan properti yang dipanggil refuntuk setiap anak. Sekarang ada dua cara untuk mengimplementasikan referensi: Menggunakan React.createRef()dan ref callback.

Menggunakan React.createRef()

Saat ini cara yang direkomendasikan untuk menggunakan referensi pada Bereaksi 16.3 (Lihat dokumen untuk info lebih lanjut). Jika Anda menggunakan versi sebelumnya, lihat di bawah tentang referensi panggilan balik.

Anda harus membuat referensi baru di konstruktor komponen induk Anda dan kemudian memberikannya kepada anak melalui ref atribut.

class FormEditor extends React.Component {
  constructor(props) {
    super(props);
    this.FieldEditor1 = React.createRef();
  }
  render() {
    return <FieldEditor ref={this.FieldEditor1} />;
  }
}

Untuk mengakses referensi semacam ini, Anda harus menggunakan:

const currentFieldEditor1 = this.FieldEditor1.current;

Ini akan mengembalikan instance komponen yang dipasang sehingga Anda dapat menggunakannya currentFieldEditor1.stateuntuk mengakses keadaan.

Hanya catatan singkat untuk mengatakan bahwa jika Anda menggunakan referensi ini pada simpul DOM bukan komponen (mis <div ref={this.divRef} /> ) Maka this.divRef.currentakan mengembalikan elemen DOM yang mendasarinya alih-alih instance komponen.

Referensi Callback

Properti ini mengambil fungsi panggilan balik yang diberikan referensi ke komponen terlampir. Callback ini dijalankan segera setelah komponen dipasang atau dilepas.

Sebagai contoh:

<FieldEditor
    ref={(fieldEditor1) => {this.fieldEditor1 = fieldEditor1;}
    {...props}
/>

Dalam contoh-contoh ini referensi disimpan pada komponen induk. Untuk memanggil komponen ini dalam kode Anda, Anda dapat menggunakan:

this.fieldEditor1

dan kemudian gunakan this.fieldEditor1.stateuntuk mendapatkan status.

Satu hal yang perlu diperhatikan, pastikan komponen anak Anda telah dirender sebelum Anda mencoba mengaksesnya ^ _ ^

Seperti di atas, jika Anda menggunakan referensi ini pada simpul DOM alih-alih komponen (mis. <div ref={(divRef) => {this.myDiv = divRef;}} />) Maka this.divRefakan mengembalikan elemen DOM yang mendasarinya alih-alih instance komponen.

Informasi lebih lanjut

Jika Anda ingin membaca lebih lanjut tentang properti ref React, periksa halaman ini dari Facebook.

Pastikan Anda membaca bagian " Don't Overuse Refs " yang mengatakan bahwa Anda tidak boleh menggunakan anak itustate untuk "mewujudkan sesuatu".

Semoga ini bisa membantu ^ _ ^

Sunting: Menambahkan React.createRef()metode untuk membuat referensi. Kode ES5 dihapus.

Martoid Prime
sumber
5
Juga berita menarik kecil, Anda dapat memanggil fungsi dari this.refs.yourComponentNameHere. Saya merasa bermanfaat untuk mengubah status melalui fungsi. Contoh: this.refs.textInputField.clearInput();
Dylan Pierce
7
Waspadalah (dari dokumen): Referensi adalah cara yang bagus untuk mengirim pesan ke instance anak tertentu dengan cara yang tidak nyaman untuk dilakukan melalui streaming Reaktif propsdan state. Namun, mereka seharusnya tidak menjadi abstraksi Anda untuk mengalirkan data melalui aplikasi Anda. Secara default, gunakan aliran data Reaktif dan simpan refuntuk kasus penggunaan yang secara inheren non-reaktif.
Lynn
2
Saya hanya mendapatkan status yang ditentukan di dalam konstruktor (bukan status terkini)
Vlado Pandžić
3
Menggunakan referensi sekarang diklasifikasikan sebagai menggunakan ' Komponen Tidak Terkendali ' dengan rekomendasi untuk menggunakan 'Komponen Terkendali'
icc97
Apakah mungkin untuk melakukan ini jika komponen anak adalah connectedkomponen Redux ?
jmancherje
6

Ini tahun 2020 dan banyak dari Anda akan datang ke sini mencari solusi yang sama tetapi dengan Hooks (Mereka hebat!) Dan dengan pendekatan terbaru dalam hal kebersihan kode dan sintaksis.

Jadi seperti jawaban sebelumnya telah menyatakan, pendekatan terbaik untuk masalah semacam ini adalah mempertahankan keadaan di luar komponen anak fieldEditor. Anda dapat melakukannya dengan berbagai cara.

Yang paling "kompleks" adalah dengan konteks global (keadaan) yang dapat diakses dan dimodifikasi oleh orang tua dan anak-anak. Ini adalah solusi hebat ketika komponen sangat dalam di hierarki pohon dan jadi mahal untuk mengirim alat peraga di setiap tingkat.

Dalam hal ini saya pikir itu tidak layak, dan pendekatan yang lebih sederhana akan membawa kita hasil yang kita inginkan, hanya menggunakan yang kuat React.useState().

Pendekatan dengan kait React.useState (), jauh lebih sederhana daripada dengan komponen Class

Seperti yang dikatakan, kami akan menangani perubahan dan menyimpan data komponen anak fieldEditorkami di induk kami fieldForm. Untuk melakukan itu, kami akan mengirimkan referensi ke fungsi yang akan menangani dan menerapkan perubahan pada fieldFormkondisi, Anda dapat melakukannya dengan:

function FieldForm({ fields }) {
  const [fieldsValues, setFieldsValues] = React.useState({});
  const handleChange = (event, fieldId) => {
    let newFields = { ...fieldsValues };
    newFields[fieldId] = event.target.value;

    setFieldsValues(newFields);
  };

  return (
    <div>
      {fields.map(field => (
        <FieldEditor
          key={field}
          id={field}
          handleChange={handleChange}
          value={fieldsValues[field]}
        />
      ))}
      <div>{JSON.stringify(fieldsValues)}</div>
    </div>
  );
}

Catat itu React.useState({}) akan mengembalikan array dengan posisi 0 menjadi nilai yang ditentukan pada panggilan (objek kosong dalam kasus ini), dan posisi 1 menjadi referensi ke fungsi yang mengubah nilai.

Sekarang dengan komponen anak FieldEditor,, Anda bahkan tidak perlu membuat fungsi dengan pernyataan kembali, konstanta lean dengan fungsi panah akan dilakukan!

const FieldEditor = ({ id, value, handleChange }) => (
  <div className="field-editor">
    <input onChange={event => handleChange(event, id)} value={value} />
  </div>
);

Aaaa dan kita selesai, tidak lebih, hanya dengan dua komponen fungsional lendir ini kita memiliki tujuan akhir kita "mengakses" FieldEditornilai anak kita dan memamerkannya di orangtua kita.

Anda dapat memeriksa jawaban yang diterima dari 5 tahun yang lalu dan melihat bagaimana Hooks membuat React code leaner (By a lot!).

Semoga jawaban saya membantu Anda mempelajari dan memahami lebih lanjut tentang Hooks, dan jika Anda ingin memeriksa contoh yang berfungsi di sini, itu .

Josep Vidal
sumber
4

Sekarang Anda dapat mengakses status InputField yang merupakan anak dari FormEditor.

Pada dasarnya setiap kali ada perubahan dalam keadaan bidang input (anak) kami mendapatkan nilai dari objek acara dan kemudian meneruskan nilai ini ke Parent di mana dalam keadaan di Parent diatur.

Pada klik tombol kita hanya mencetak keadaan bidang Input.

Poin kuncinya di sini adalah bahwa kami menggunakan properti untuk mendapatkan id / nilai Field Input dan juga untuk memanggil fungsi yang ditetapkan sebagai atribut pada Field Input sementara kami menghasilkan field Input anak yang dapat digunakan kembali.

class InputField extends React.Component{
  handleChange = (event)=> {
    const val = event.target.value;
    this.props.onChange(this.props.id , val);
  }

  render() {
    return(
      <div>
        <input type="text" onChange={this.handleChange} value={this.props.value}/>
        <br/><br/>
      </div>
    );
  }
}       


class FormEditorParent extends React.Component {
  state = {};
  handleFieldChange = (inputFieldId , inputFieldValue) => {
    this.setState({[inputFieldId]:inputFieldValue});
  }
  //on Button click simply get the state of the input field
  handleClick = ()=>{
    console.log(JSON.stringify(this.state));
  }

  render() {
    const fields = this.props.fields.map(field => (
      <InputField
        key={field}
        id={field}
        onChange={this.handleFieldChange}
        value={this.state[field]}
      />
    ));

    return (
      <div>
        <div>
          <button onClick={this.handleClick}>Click Me</button>
        </div>
        <div>
          {fields}
        </div>
      </div>
    );
  }
}

const App = () => {
  const fields = ["field1", "field2", "anotherField"];
  return <FormEditorParent fields={fields} />;
};

ReactDOM.render(<App/>, mountNode);
Dhana
sumber
7
Tidak yakin saya bisa membatalkan yang ini. Apa yang Anda sebutkan di sini yang belum dijawab oleh @ Markus-ipse?
Alexander Bird
3

Seperti jawaban sebelumnya mengatakan, cobalah untuk memindahkan status ke komponen teratas dan memodifikasi status melalui panggilan balik yang diteruskan ke anak-anaknya.

Dalam hal ini Anda benar-benar perlu akses ke keadaan anak yang dinyatakan sebagai komponen fungsional (kait) Anda dapat mendeklarasikan ref dalam komponen induk, kemudian menyebarkannya sebagai ref atribut untuk anak tetapi Anda perlu menggunakan React.forwardRef dan kemudian hook useImperativeHandle untuk mendeklarasikan fungsi yang bisa Anda panggil di komponen induk.

Lihatlah contoh berikut:

const Parent = () => {
    const myRef = useRef();
    return <Child ref={myRef} />;
}

const Child = React.forwardRef((props, ref) => {
    const [myState, setMyState] = useState('This is my state!');
    useImperativeHandle(ref, () => ({getMyState: () => {return myState}}), [myState]);
})

Maka Anda harus bisa mendapatkan myState di komponen Induk dengan menelepon: myRef.current.getMyState();

Mauricio Avendaño
sumber