Pendahuluan
Saat mengerjakan React.js, saya sering harus berurusan dengan pemrosesan formulir. Redux-Form , React-Redux-Form melewati tangan saya, tetapi tidak ada satu pun perpustakaan yang benar-benar memuaskan saya. Saya tidak suka bahwa keadaan formulir disimpan dalam peredam , dan setiap peristiwa melewati pembuat tindakan . Juga, menurut Dan Abramov, "keadaan formulir secara inheren fana dan lokal, jadi Anda tidak perlu melacaknya di Redux (atau perpustakaan Flux apa pun)."
Saya perhatikan bahwa dalam React-Redux-Form ada komponen LocalForm yang memungkinkan Anda untuk bekerja tanpa redux, tetapi menurut pendapat saya, tidak masuk akal untuk menginstal perpustakaan 21.9kB dan menggunakannya kurang dari setengah.
Saya tidak menentang perpustakaan bernama, dalam kasus tertentu mereka tidak tergantikan. Misalnya, ketika komponen pihak ketiga yang tidak terkait dengan formulir tergantung pada data yang dimasukkan. Tetapi dalam artikel saya, saya ingin mempertimbangkan bentuk yang tidak perlu redux.
Saya mulai menggunakan status komponen lokal, dan kesulitan baru muncul: jumlah kode meningkat, komponen hilang keterbacaan, banyak duplikasi muncul.
Solusinya adalah konsep Komponen Orde Tinggi. Singkatnya, HOC adalah fungsi yang menerima input komponen dan mengembalikannya diperbarui dengan integrasi alat peraga tambahan atau modifikasi. Baca lebih lanjut tentang HOC di situs web resmi React.js . Tujuan menggunakan konsep HOC adalah untuk membagi komponen menjadi dua bagian, salah satunya akan bertanggung jawab untuk logika, dan yang kedua - untuk tampilan.
Pembuatan formulir
Sebagai contoh, kami akan membuat formulir umpan balik sederhana di mana akan ada 3 bidang: nama, email, telepon.
Untuk kesederhanaan, kami menggunakan Create-React-App . Instal secara global:
npm i -g create-react-app
kemudian buat aplikasi Anda di folder bentuk murni
create-react-app pure-form
Selain itu, instal tipe-prop dan nama kelas, mereka akan berguna bagi kita di masa depan:
npm i prop-types classnames -S
Buat dua folder / komponen dan / wadah . Folder / components akan berisi semua komponen yang bertanggung jawab untuk tampilan. Di folder / container , komponen yang bertanggung jawab untuk logika.
Di folder / components , buat file Input.jsx di mana kami mendeklarasikan komponen umum untuk semua input. Adalah penting pada tahap ini untuk tidak lupa meresepkan ProptTypes dan defaultProps dengan cara yang berkualitas, menyediakan kemungkinan untuk menambahkan kelas kustom, dan juga mewarisinya dari PureComponent untuk optimisasi.
Hasilnya adalah:
import React, { PureComponent } from 'react'; import cx from 'classnames'; import PropTypes from 'prop-types'; class Input extends PureComponent { render() { const { name, error, labelClass, inputClass, placeholder, ...props } = this.props; return ( <label className={cx('label', !!labelClass && labelClass)} htmlFor={`id-${name}`} > <span className="span">{placeholder}</span> <input className={cx( 'input', !!inputClass && inputClass, !!error && 'error' )} name={name} id={`id-${name}`} onFocus={this.handleFocus} onBlur={this.handleBlur} {...props} /> {!!error && <span className="errorText">{error}</span>} </label> ); } } Input.defaultProps = { type: 'text', error: '', required: false, autoComplete: 'off', labelClass: '', inputClass: '', }; Input.propTypes = { value: PropTypes.string.isRequired, name: PropTypes.string.isRequired, onChange: PropTypes.func.isRequired, placeholder: PropTypes.string.isRequired, error: PropTypes.string, type: PropTypes.string, required: PropTypes.bool, autoComplete: PropTypes.string, labelClass: PropTypes.string, inputClass: PropTypes.string, }; export default Input;
Selanjutnya, di folder / komponen , buat file Form.jsx , di mana komponen yang berisi formulir akan dideklarasikan. Kami akan menerima semua metode untuk bekerja dengan itu melalui alat peraga, serta nilai untuk input, jadi negara tidak diperlukan di sini. Kami mendapatkan:
import React, { Component } from 'react'; import PropTypes from 'prop-types'; import Input from './Input'; import FormWrapper from '../containers/FormWrapper'; class Form extends Component { render() { const { data: { username, email, phone }, errors, handleInput, handleSubmit, } = this.props; return ( <div className="openBill"> <form className="openBillForm" onSubmit={handleSubmit}> <Input key="username" value={username} name="username" onChange={handleInput} placeholder="" error={errors.username} required /> <Input key="phone" value={phone} name="phone" onChange={handleInput} placeholder="" error={errors.phone} required /> <Input key="email" value={email} type="email" name="email" onChange={handleInput} placeholder=" " error={errors.email} required /> <button type="submit" className="submitBtn"> </button> </form> </div> ); } } Form.propTypes = { data: PropTypes.shape({ username: PropTypes.string.isRequired, phone: PropTypes.string.isRequired, email: PropTypes.string.isRequired, }).isRequired, errors: PropTypes.shape({ username: PropTypes.string.isRequired, phone: PropTypes.string.isRequired, email: PropTypes.string.isRequired, }).isRequired, handleInput: PropTypes.func.isRequired, handleSubmit: PropTypes.func.isRequired, }; export default FormWrapper(Form);
Penciptaan HOC
Di folder / penampung , buat file FormWrapper.jsx . Kami mendeklarasikan fungsi di dalam, yang mengambil komponen WrappedComponent sebagai argumen dan mengembalikan kelas WrappedForm . Metode render kelas ini mengembalikan WrappedComponent dengan alat peraga yang diintegrasikan ke dalamnya. Cobalah untuk menggunakan deklarasi fungsi klasik, ini akan menyederhanakan proses debugging.
Di kelas WrappedForm, buat status : isFetching - bendera untuk mengendalikan permintaan asinkron, data - objek dengan nilai input, kesalahan - objek untuk menyimpan kesalahan. Status yang dideklarasikan diteruskan ke WrappedComponent . Dengan demikian, implementasi penyimpanan negara formulir ke tingkat atas diimplementasikan, yang membuat kode lebih mudah dibaca dan transparan.
export default function Wrapper(WrappedComponent) { return class FormWrapper extends Component { state = { isFetching: false, data: { username: '', phone: '', email: '', }, errors: { username: '', phone: '', email: '', }, }; render() { return <WrappedComponent {...this.state} />; } }; }
Tetapi implementasi seperti itu tidak universal, karena untuk setiap formulir Anda harus membuat bungkus sendiri. Anda dapat meningkatkan sistem ini dan menanamkan HOC di dalam fungsi lain yang akan membentuk nilai-nilai keadaan awal.
import React, { Component } from 'react'; export default function getDefaultValues(initialState, requiredFields) { return function Wrapper(WrappedComponent) { return class WrappedForm extends Component { state = { isFetching: false, data: initialState, errors: requiredFields, }; render() { return <WrappedComponent {...this.state} {...this.props} />; } }; }; }
Dalam fungsi ini, Anda dapat melewati tidak hanya nilai awal negara , tetapi umumnya semua parameter. Misalnya, atribut dan metode yang dengannya dimungkinkan untuk membuat formulir di Form.jsx . Contoh implementasi seperti itu akan menjadi topik untuk artikel selanjutnya.
Di file Form.jsx, kami mendeklarasikan nilai status awal dan meneruskannya ke HOC:
const initialState = { username: '', phone: '', email: '', }; export default FormWrapper(initialState, initialState)(Form);
Mari kita buat metode handleInput untuk memproses nilai yang dimasukkan ke dalam input. Dia menerima acara dari mana kami mengambil nilai dan nama dan kami mentransfernya ke setState . Karena nilai input disimpan dalam objek data , kami memanggil fungsi di setState . Bersamaan dengan menyimpan nilai yang diperoleh, kami nol penyimpanan kesalahan bidang variabel. Kami mendapatkan:
handleInput = event => { const { value, name } = event.currentTarget; this.setState(({ data, errors }) => ({ data: { ...data, [name]: value, }, errors: { ...errors, [name]: '', }, })); };
Sekarang kita akan membuat metode handeSubmit untuk memproses formulir dan menampilkan data di konsol, tetapi sebelum itu kita harus melewati validasi. Kami hanya akan memvalidasi bidang yang wajib diisi, yaitu semua kunci objek this.state.errors. Kami mendapatkan:
handleSubmit = e => { e.preventDefault(); const { data } = this.state; const isValid = Object.keys(data).reduce( (sum, item) => sum && this.validate(item, data[item]), true ); if (isValid) { console.log(data); } };
Menggunakan metode pengurangan, kami memilah-milah semua bidang yang diperlukan. Pada setiap iterasi, metode validasi dipanggil, di mana kita memberikan sepasang nama , nilai . Di dalam metode, pemeriksaan dilakukan untuk kebenaran data yang dimasukkan, yang hasilnya mengembalikan tipe Boolean. Jika setidaknya satu pasangan nilai tidak lulus validasi, maka variabel isValid akan menjadi salah dan data tidak akan ditampilkan ke konsol, mis. Formulir tidak akan diproses. Kasus sederhana dipertimbangkan di sini - cek untuk formulir yang tidak kosong. Metode validasi :
validate = (name, value) => { if (!value.trim()) { this.setState( ({ errors }) => ({ errors: { ...errors, [name]: ' ', }, }), () => false ); } else { return true; } };
Baik metode handleSubmit dan handleInput harus diteruskan ke WrappedComponent :
render() { return ( <WrappedComponent {...this.state} {...this.props} handleInput={this.handleInput} handleSubmit={this.handleSubmit} /> ); }
Sebagai hasilnya, kami mendapatkan formulir umpan balik siap pakai, dengan validasi sederhana dan output kesalahan. Pada saat yang sama, kami menghapus bagian logis dari komponen yang bertanggung jawab untuk tampilan.
Kesimpulan
Jadi, kami melihat contoh dasar pembuatan HOC untuk formulir pemrosesan. Saat membuat formulir, hanya input sederhana yang digunakan, tanpa elemen kompleks, seperti daftar drop-down, kotak centang, tombol radio, dan lainnya. Jika tersedia, Anda mungkin harus membuat metode pemrosesan acara tambahan.
Tulis pertanyaan dan komentar di komentar ke artikel atau ke email saya.
Contoh lengkap dapat ditemukan di sini: bentuk reaksi murni .
Terima kasih atas perhatian anda!