Antipatterns dalam React atau Bad Tips for Beginners

Hai, Habr.

Tepat satu tahun telah berlalu sejak saya mulai belajar Bereaksi. Selama waktu ini, saya berhasil merilis beberapa aplikasi seluler kecil yang ditulis dalam React Native, dan untuk berpartisipasi dalam pengembangan aplikasi web menggunakan ReactJS. Menyimpulkan dan melihat kembali semua garu yang berhasil saya injak, saya memiliki ide untuk mengekspresikan pengalaman saya dalam bentuk artikel. Saya akan membuat reservasi bahwa sebelum memulai studi reaksi, saya memiliki 3 tahun pengalaman pengembangan dalam c ++, python, serta pendapat bahwa tidak ada yang rumit dalam pengembangan front-end dan tidak akan sulit untuk memahami semuanya. Karena itu, pada bulan-bulan pertama saya lalai membaca literatur pendidikan dan pada dasarnya hanya google contoh kode yang sudah jadi. Oleh karena itu, seorang pengembang teladan yang mempelajari dokumentasi pertama-tama, kemungkinan besar, tidak akan menemukan sesuatu yang baru untuk dirinya di sini, tetapi saya masih berpikir bahwa beberapa orang ketika mempelajari teknologi baru lebih memilih cara dari praktik ke teori. Jadi jika artikel ini menyelamatkan seseorang dari garu, maka saya berusaha tidak sia-sia.

Kiat 1. Bekerja dengan formulir


Situasi klasik: ada formulir dengan beberapa bidang di mana pengguna memasukkan data, lalu mengklik tombol, dan data yang dimasukkan dikirim ke api eksternal / disimpan dalam keadaan / ditampilkan di layar - garis bawahi yang diperlukan.

Opsi 1. Bagaimana tidak melakukannya


Di Bereaksi, Anda dapat membuat tautan ke simpul DOM atau komponen Bereaksi.

this.myRef = React.createRef(); 

Dengan menggunakan atribut ref, tautan yang dibuat dapat dilampirkan ke komponen / simpul yang diperlukan.

 <input id="data" type="text" ref={this.myRef} /> 

Dengan demikian, masalah di atas dapat diselesaikan dengan membuat referensi untuk setiap bidang formulir, dan di tubuh fungsi yang disebut ketika tombol diklik, dapatkan data dari formulir dengan menghubungi tautan yang diperlukan.

 class BadForm extends React.Component { constructor(props) { super(props); this.myRef = React.createRef(); this.onClickHandler = this.onClickHandler.bind(this); } onClickHandler() { const data = this.myRef.current.value; alert(data); } render() { return ( <> <form> <label htmlFor="data">Bad form:</label> <input id="data" type="text" ref={this.myRef} /> <input type="button" value="OK" onClick={this.onClickHandler} /> </form> </> ); } } 

Bagaimana monyet batin dapat mencoba membenarkan keputusan ini:

  1. Hal utama yang berhasil, Anda masih memiliki tugas 100500, dan acara TV tidak ditonton, tiket tidak ditutup. Biarkan seperti itu, lalu ubah
  2. Lihat betapa sedikit kode yang diperlukan untuk memproses formulir. Menyatakan referensi dan mengakses data di mana pun Anda inginkan.
  3. Jika Anda menyimpan nilai dalam keadaan, maka setiap kali Anda mengubah data input, seluruh aplikasi akan diberikan lagi, dan Anda hanya membutuhkan data akhir. Jadi metode ini juga ternyata bagus untuk optimasi, biarkan saja seperti itu.

Mengapa monyet itu salah:

Contoh di atas adalah antipattern klasik dalam React, yang melanggar konsep aliran data searah. Dalam hal ini, aplikasi Anda tidak akan dapat menanggapi perubahan data selama input, karena mereka tidak disimpan dalam keadaan.

Opsi 2. Solusi klasik


Untuk setiap bidang formulir, variabel dibuat dalam keadaan di mana hasil input akan disimpan. Atribut nilai ditugaskan variabel ini. Atribut onhange ditugaskan fungsi di mana nilai variabel dalam keadaan diubah melalui setState (). Dengan demikian, semua data diambil dari status, dan ketika data berubah, status berubah dan aplikasi diberikan lagi.

 class GoodForm extends React.Component { constructor(props) { super(props); this.state = { data: '' }; this.onChangeData = this.onChangeData.bind(this); this.onClickHandler = this.onClickHandler.bind(this); } onChangeData(event) { this.setState({ data: event.target.value }); } onClickHandler(event) { const { data } = this.state; alert(data); } render() { const { data } = this.state; return ( <> <form> <label htmlFor="data">Good form:</label> <input id="data" type="text" value={data} onChange={this.onChangeData} /> <input type="button" value="OK" onClick={this.onClickHandler} /> </form> </> ); } } 

Opsi 3. Lanjutan. Ketika bentuk menjadi banyak


Opsi kedua memiliki sejumlah kelemahan: sejumlah besar kode standar, untuk setiap bidang perlu mendeklarasikan metode onhange dan menambahkan variabel ke negara. Ketika datang untuk memvalidasi data yang dimasukkan dan menampilkan pesan kesalahan, jumlah kode semakin bertambah. Untuk memfasilitasi pekerjaan dengan formulir, ada perpustakaan Formik yang sangat baik yang menangani masalah terkait dengan pemeliharaan formulir, dan juga membuatnya mudah untuk menambahkan skema validasi.

 import React from 'react'; import { Formik } from 'formik'; import * as Yup from 'yup'; const SigninSchema = Yup.object().shape({ data: Yup.string() .min(2, 'Too Short!') .max(50, 'Too Long!') .required('Data required'), }); export default () => ( <div> <Formik initialValues={{ data: '' }} validationSchema={SigninSchema} onSubmit={(values) => { alert(values.data); }} render={(props) => ( <form onSubmit={props.handleSubmit}> <label>Formik form:</label> <input type="text" onChange={props.handleChange} onBlur={props.handleBlur} value={props.values.data} name="data" /> {props.errors.data && props.touched.data ? ( <div>{props.errors.data}</div> ) : null} <button type="submit">Ok</button> </form> )} /> </div> ); 

Tip 2. Hindari Mutasi


Pertimbangkan aplikasi daftar tugas yang sederhana. Di konstruktor, kami mendefinisikan dalam keadaan variabel di mana daftar yang harus dilakukan akan disimpan. Dalam metode render (), tampilkan formulir di mana kami akan menambahkan kasus ke daftar. Sekarang pertimbangkan bagaimana kita dapat mengubah status.

Opsi yang salah mengarah ke mutasi array:

 this.state.data.push(item); 

Dalam hal ini, array benar-benar berubah, tetapi Bereaksi tidak tahu apa-apa tentang itu, yang berarti metode render () tidak akan dipanggil, dan perubahan kami tidak akan ditampilkan. Faktanya adalah bahwa dalam JavaScript, saat membuat array atau objek baru, tautan disimpan dalam variabel, dan bukan objek itu sendiri. Dengan demikian, menambahkan elemen baru ke array data, kami mengubah array itu sendiri, tetapi bukan tautannya, yang berarti bahwa nilai data yang disimpan dalam keadaan tidak akan berubah.

Mutasi dalam JavaScript dapat ditemukan di setiap kesempatan. Untuk menghindari mutasi data, gunakan operator spread untuk array, metode filter () dan map (), dan untuk objek, gunakan operator spread atau metode assign ().

 const newData = [...data, item]; const copy = Object.assign({}, obj); 

Kembali ke aplikasi kita, perlu dikatakan bahwa opsi yang benar untuk mengubah keadaan adalah dengan menggunakan metode setState (). Jangan mencoba mengubah keadaan secara langsung di mana saja selain dari konstruktor, karena ini bertentangan dengan ideologi Bereaksi.

Jangan lakukan itu!

 this.state.data = [...data, item]; 

Hindari juga mutasi negara. Bahkan jika Anda menggunakan setState (), mutasi dapat menyebabkan bug ketika mencoba mengoptimalkan. Misalnya, jika Anda melewatkan objek bermutasi melalui alat peraga ke anak PureComponent, maka komponen ini tidak akan dapat memahami bahwa alat peraga yang diterima telah berubah, dan tidak akan merender ulang.

Jangan lakukan itu!

 this.state.data.push(item); this.setState({ data: this.state.data }); 

Opsi yang benar:

 const { data } = this.state; const newData = [...data, item]; this.setState({ data: newData }); 

Tetapi bahkan opsi di atas dapat menyebabkan bug halus. Faktanya adalah bahwa tidak ada yang menjamin bahwa selama waktu berlalu antara menerima variabel data dari negara dan menulis nilai baru ke negara, negara itu sendiri tidak akan berubah. Dengan demikian, Anda berisiko kehilangan beberapa perubahan yang dilakukan. Oleh karena itu, dalam kasus ketika Anda perlu memperbarui nilai variabel dalam keadaan menggunakan nilai sebelumnya, lakukan sebagai berikut:

Pilihan yang benar, jika kondisi berikut ini tergantung pada saat ini:

 this.setState((state) => { return {data: [...state.data, item]}; }); 

Tip 3. Meniru aplikasi multi-halaman


Aplikasi Anda sedang berkembang, dan pada titik tertentu Anda menyadari bahwa Anda memerlukan multi-halaman. Tetapi apa yang harus dilakukan, karena Bereaksi adalah aplikasi satu halaman? Pada titik ini, ide gila berikut mungkin muncul di benak Anda. Anda memutuskan bahwa Anda akan menjaga pengidentifikasi halaman saat ini dalam keadaan global aplikasi Anda, misalnya, menggunakan toko redux. Untuk menampilkan halaman yang diinginkan, Anda akan menggunakan rendering bersyarat, dan beralih di antara halaman, memanggil tindakan dengan payload yang diinginkan, sehingga mengubah nilai-nilai di toko redux.

App.js

 import React from 'react'; import { connect } from 'react-redux'; import './App.css'; import Page1 from './Page1'; import Page2 from './Page2'; const mapStateToProps = (state) => ({ page: state.page }); function AppCon(props) { if (props.page === 'Page1') { return ( <div className="App"> <Page1 /> </div> ); } return ( <div className="App"> <Page2 /> </div> ); } const App = connect(mapStateToProps)(AppCon); export default App; 

Page1.js

 import React from 'react'; import { connect } from 'react-redux'; import { setPage } from './redux/actions'; function mapDispatchToProps(dispatch) { return { setPageHandle: (page) => dispatch(setPage(page)), }; } function Page1Con(props) { return ( <> <h3> Page 1 </h3> <input type="button" value="Go to page2" onClick={() => props.setPageHandle('Page2')} /> </> ); } const Page1 = connect(null, mapDispatchToProps)(Page1Con); export default Page1; 

Kenapa ini buruk?

  1. Solusi ini adalah contoh dari sepeda primitif. Jika Anda tahu cara membuat sepeda sedemikian kompeten dan memahami apa yang Anda inginkan, maka bukan bagi saya untuk memberi tahu Anda. Jika tidak, kode Anda akan tersirat, membingungkan, dan terlalu rumit.
  2. Anda tidak akan dapat menggunakan tombol kembali di browser, karena riwayat kunjungan tidak akan disimpan.

Bagaimana cara mengatasinya?

Cukup gunakan react-router . Ini adalah paket hebat yang dapat dengan mudah mengubah aplikasi Anda menjadi satu halaman multi.

Kiat 4. Tempat menempatkan permintaan api


Pada titik tertentu, Anda perlu menambahkan permintaan ke api eksternal di aplikasi Anda. Dan di sini Anda bertanya-tanya: di mana dalam aplikasi Anda, Anda perlu menjalankan permintaan?
Saat ini, ketika memasang komponen Bereaksi, siklus hidupnya adalah sebagai berikut:

  • constructor ()
  • getDerivedStateFromProps statis ()
  • render ()
  • componentDidMount ()

Mari kita menganalisis semua opsi secara berurutan.

Dalam metode constructor (), dokumentasi tidak merekomendasikan melakukan apa pun selain:

  • Menginisialisasi keadaan internal melalui penugasan objek this.state.
  • Ikatan penangan acara ke sebuah instance.

Panggilan ke api tidak termasuk dalam daftar ini, jadi mari kita lanjutkan.

Metode getDerivedStateFromProps () sesuai dengan dokumentasi yang ada untuk situasi langka ketika negara tergantung pada perubahan props. Sekali lagi, bukan kasus kami.

Kesalahan paling umum adalah lokasi kode yang mengeksekusi permintaan api dalam metode render (). Ini mengarah pada fakta bahwa begitu permintaan Anda berhasil dieksekusi, Anda kemungkinan besar akan menyimpan hasil dalam keadaan komponen, dan ini akan mengarah pada panggilan baru ke metode render (), di mana permintaan Anda ke api akan dieksekusi lagi. Dengan demikian, komponen Anda akan berakhir dalam rendering tanpa akhir, dan ini jelas bukan yang Anda butuhkan.

Jadi metode componentDidMount () adalah tempat yang ideal untuk mengakses api eksternal.

Kesimpulan


Contoh kode dapat ditemukan di github .

Source: https://habr.com/ru/post/id468799/


All Articles