Menavigasi Ekosistem React.js
Diterbitkan: 2022-03-11Kecepatan inovasi di JavaScript Land sangat tinggi, bahkan beberapa orang menganggapnya kontra-produktif. Perpustakaan dapat berubah dari mainan pengguna awal, menjadi yang canggih, hingga usang dalam beberapa bulan. Mampu mengidentifikasi alat yang akan tetap relevan setidaknya selama satu tahun lagi menjadi seni itu sendiri.
Ketika React.js dirilis dua tahun lalu, saya baru belajar Angular, dan saya dengan cepat mengabaikan React sebagai perpustakaan template lain yang tidak jelas. Selama dua tahun itu, Angular benar-benar mendapatkan pijakan di antara pengembang JavaScript, dan hampir menjadi identik dengan pengembangan JS modern. Saya bahkan mulai melihat Angular di lingkungan perusahaan yang sangat konservatif, dan saya menerima begitu saja masa depan cerahnya.
Namun tiba-tiba, hal aneh terjadi. Tampaknya Angular menjadi korban efek Osborne, atau "mati karena pengumuman sebelumnya". Tim mengumumkan bahwa, pertama, Angular 2 akan benar-benar berbeda, tanpa jalur migrasi yang jelas dari Angular 1, dan, kedua, Angular 2 tidak akan tersedia selama satu tahun atau lebih. Apa artinya bagi seseorang yang ingin memulai proyek web baru? Apakah Anda ingin menulis proyek baru Anda dalam kerangka kerja yang akan menjadi usang oleh rilis versi baru?
Kecemasan di antara para pengembang ini memainkan peran React, yang berusaha untuk memantapkan dirinya di komunitas. Tapi React selalu memasarkan dirinya sebagai "V" di "MVC." Ini menyebabkan sejumlah frustrasi di antara pengembang web, yang terbiasa bekerja dengan kerangka kerja yang lengkap. Bagaimana cara mengisi bagian yang hilang? Haruskah saya menulis sendiri? Haruskah saya menggunakan perpustakaan yang ada? Jika demikian, yang mana?
Benar saja, Facebook (pencipta React.js) memiliki satu lagi ace di lubang: Alur kerja Flux, yang berjanji untuk mengisi fungsi "M" dan "C" yang hilang. Untuk membuat segalanya lebih menarik, Facebook menyatakan bahwa Flux adalah "pola", bukan kerangka kerja, dan implementasi Flux hanyalah salah satu contoh polanya. Sesuai dengan kata-kata mereka, implementasinya sangat sederhana, dan melibatkan banyak penulisan verbose, boilerplate berulang untuk menyelesaikan sesuatu.
Komunitas open source datang untuk menyelamatkan, dan setahun kemudian, kami memiliki lusinan perpustakaan Flux, dan bahkan beberapa proyek meta yang bertujuan untuk membandingkannya. Ini adalah hal yang baik; alih-alih merilis beberapa kerangka kerja perusahaan yang sudah jadi, Facebook telah berhasil membangkitkan minat masyarakat, dan mendorong orang untuk menemukan solusi mereka sendiri.
Ada satu efek samping yang menarik dari pendekatan ini: ketika Anda harus menggabungkan banyak perpustakaan yang berbeda untuk mendapatkan kerangka kerja lengkap Anda, Anda secara efektif lolos dari penguncian vendor, dan inovasi yang muncul selama konstruksi kerangka kerja Anda sendiri dapat dengan mudah digunakan kembali di tempat lain.
Itulah mengapa hal-hal baru di sekitar React sangat menarik; sebagian besar dapat dengan mudah digunakan kembali di lingkungan JavaScript lainnya. Bahkan jika Anda tidak berencana menggunakan React, melihat ekosistemnya sangat menginspirasi. Anda mungkin ingin menyederhanakan sistem pembangunan Anda menggunakan Webpack bundler modul yang kuat, namun relatif mudah dikonfigurasi, atau mulai menulis ECMAScript 6 dan bahkan ECMAScript 7 hari ini dengan kompiler Babel.
Dalam artikel ini, saya akan membahas beberapa fitur dan pustaka menarik yang tersedia. Jadi, mari kita jelajahi ekosistem React!
Membangun Sistem
Sistem build bisa dibilang hal pertama yang harus Anda perhatikan saat membuat aplikasi web baru. Sistem build tidak hanya alat untuk menjalankan skrip, tetapi di dunia JavaScript, biasanya membentuk struktur umum aplikasi Anda. Tugas terpenting yang harus dicakup oleh sistem build adalah sebagai berikut:
- Mengelola ketergantungan eksternal dan internal
- Menjalankan kompiler dan praprosesor
- Mengoptimalkan aset untuk produksi
- Menjalankan server web pengembangan, pengamat file, dan pemuat ulang browser
Dalam beberapa tahun terakhir, alur kerja Yeoman dengan Bower dan Grunt disajikan sebagai trinitas suci pengembangan frontend modern, memecahkan masalah generasi boilerplate, manajemen paket, dan tugas umum yang berjalan masing-masing, dengan peralihan rakyat yang lebih progresif dari Grunt ke Gulp baru-baru ini.
Di lingkungan React, Anda dapat dengan aman melupakan ini. Bukannya Anda tidak bisa menggunakannya, tetapi kemungkinan Anda bisa lolos menggunakan Webpack dan NPM lama yang bagus. Bagaimana mungkin? Webpack adalah bundler modul, yang mengimplementasikan sintaks modul CommonJS, umum di dunia Node.js, di browser juga. Ini sebenarnya membuat segalanya lebih sederhana karena Anda tidak perlu mempelajari manajer paket lain untuk front end; Anda cukup menggunakan NPM dan berbagi dependensi antara server dan front end. Anda juga tidak perlu berurusan dengan masalah memuat file JS dalam urutan yang benar karena itu disimpulkan dari impor ketergantungan yang ditentukan di setiap file, dan seluruh gerombolan digabungkan dengan benar ke satu skrip yang dapat dimuat.
Untuk membuatnya lebih menarik, Webpack, tidak seperti sepupunya yang lebih tua, Browserify, juga dapat menangani jenis aset lainnya. Misalnya, dengan loader, Anda dapat mengubah file aset apa pun menjadi fungsi JavaScript yang menyejajarkan, atau memuat, file yang direferensikan. Jadi, lupakan prapemrosesan dan referensi aset secara manual dari HTML. Cukup require
file CSS/SASS/LESS Anda dari JavaScript, dan Webpack akan menangani sisanya dengan file konfigurasi sederhana. Webpack juga menyertakan server web pengembangan, dan pengamat file. Plus, Anda dapat menggunakan kunci "scripts"
di package.json
untuk mendefinisikan satu baris shell:
{ "name": "react-example-filmdb", "version": "0.0.1", "description": "Isomorphic React + Flux film database example", "main": "server/index.js", "scripts": { "build": "./node_modules/.bin/webpack --progress --stats --config ./webpack/prod.config.js", "dev": "node --harmony ./webpack/dev-server.js", "prod": "NODE_ENV=production node server/index.js", "test": "./node_modules/.bin/karma start --single-run", "postinstall": "npm run build" } ... }
Dan hanya itu yang Anda butuhkan untuk mengganti Gulp dan Bower. Tentu saja, Anda masih dapat menggunakan Yeoman untuk membuat boilerplate aplikasi. Jangan berkecil hati ketika tidak ada generator Yeoman untuk hal-hal yang Anda inginkan (perpustakaan paling mutakhir sering kali tidak memilikinya). Anda masih bisa mengkloning beberapa boilerplate dari GitHub, dan meretasnya.
ECMAScript Besok, Hari Ini
Laju pengembangan bahasa JavaScript telah meningkat secara substansial dalam beberapa tahun terakhir, dan setelah periode menghilangkan kebiasaan dan menstabilkan bahasa, kami sekarang melihat fitur baru yang kuat masuk. Draf spesifikasi ECMAScript 6 (ES6) telah diselesaikan, dan meskipun itu belum dibuat resmi, itu sudah menemukan adopsi luas. Pekerjaan pada ECMAScript 7 (ES7) sedang berlangsung, tetapi banyak fiturnya sudah diadopsi oleh perpustakaan yang lebih mutakhir.
Bagaimana ini mungkin? Mungkin Anda berpikir bahwa Anda tidak dapat memanfaatkan fitur JavaScript baru yang mengkilap ini hingga fitur tersebut didukung di Internet Explorer, tetapi pikirkan lagi. Transpiler ES telah menjadi begitu umum sehingga kita bahkan dapat melakukannya tanpa dukungan browser yang tepat. Transpiler ES terbaik yang tersedia saat ini adalah Babel: ia akan mengambil kode ES6+ terbaru Anda, dan mengubahnya menjadi vanilla ES5, jadi, Anda dapat menggunakan fitur ES baru segera setelah ditemukan (dan diimplementasikan di Babel, yang biasanya terjadi cukup lama). dengan cepat).
Fitur JavaScript terbaru berguna di semua kerangka kerja front-end, dan React baru-baru ini diperbarui agar berfungsi dengan baik dengan spesifikasi ES6 dan ES7. Fitur-fitur baru ini akan menghilangkan banyak sakit kepala saat berkembang dengan React. Mari kita lihat beberapa tambahan yang paling berguna, dan bagaimana mereka bisa bermanfaat bagi proyek React. Nanti, kita akan melihat bagaimana menggunakan beberapa alat dan pustaka yang berguna dengan React sambil memanfaatkan sintaks yang ditingkatkan ini.
kelas ES6
Pemrograman berorientasi objek adalah paradigma yang kuat dan diadopsi secara luas, tetapi JavaScript mengambilnya sedikit eksotis. Sebagian besar kerangka kerja front end, baik itu Backbone, Ember, Angular, atau React, telah mengadopsi cara mereka sendiri dalam mendefinisikan kelas dan membuat objek. Tetapi dengan ES6, kami sekarang memiliki kelas tradisional dalam JavaScript, dan masuk akal untuk menggunakannya daripada menulis implementasi kami sendiri. Jadi, alih-alih:
React.createClass({ displayName: 'HelloMessage', render() { return <div>Hello {this.props.name}</div>; } })
kita dapat menulis:
class HelloMessage extends React.Component { render() { return <div>Hello {this.props.name}</div>; } }
Untuk contoh yang lebih rumit, pertimbangkan kode ini, menggunakan sintaks lama:
React.createClass({ displayName: 'Counter', getDefaultProps: function(){ return {initialCount: 0}; }, getInitialState: function() { return {count: this.props.initialCount} }, propTypes: {initialCount: React.PropTypes.number}, tick() { this.setState({count: this.state.count + 1}); }, render() { return ( <div onClick={this.tick}> Clicks: {this.state.count} </div> ); } });
Dan bandingkan dengan versi ES6:
class Counter extends React.Component { static propTypes = {initialCount: React.PropTypes.number}; static defaultProps = {initialCount: 0}; constructor(props) { super(props); this.state = {count: props.initialCount}; } state = {count: this.props.initialCount}; tick() { this.setState({count: this.state.count + 1}); } render() { return ( <div onClick={this.tick.bind(this)}> Clicks: {this.state.count} </div> ); } }
Di sini, metode siklus hidup React getDefaultProps
dan getInitialState
tidak lagi diperlukan. getDefaultProps
menjadi variabel kelas statis defaultProps
, dan status awal hanya didefinisikan dalam konstruktor. Satu-satunya kelemahan adalah, metode tidak lagi terikat secara otomatis, jadi Anda harus menggunakan bind
saat memanggil penangan dari JSX.
dekorator
Dekorator adalah fitur yang berguna dari ES7. Mereka memungkinkan Anda untuk menambah perilaku fungsi atau kelas dengan membungkusnya di dalam fungsi lain. Sebagai contoh, mari kita asumsikan bahwa Anda ingin memiliki pengendali perubahan yang sama pada beberapa komponen Anda, tetapi Anda tidak ingin berkomitmen pada antipattern warisan. Anda dapat menggunakan dekorator kelas sebagai gantinya. Mari kita definisikan dekorator sebagai berikut:
addChangeHandler: function(target) { target.prototype.changeHandler = function(key, attr, event) { var state = {}; state[key] = this.state[key] || {}; state[key][attr] = event.currentTarget.value; this.setState(state); }; return target; }
Yang penting di sini adalah fungsi addChangeHandler
menambahkan fungsi changeHandler
ke prototipe kelas target.
Untuk menerapkan dekorator, kita dapat menulis:
MyClass = changeHandler(MyClass)
atau lebih elegan, dengan sintaks ES7:
@addChangeHandler class MyClass { ... }
Adapun isi dari fungsi changeHandler
itu sendiri, dengan tidak adanya React dari data binding dua arah, bekerja dengan input di React bisa jadi membosankan. Fungsi changeHandler
mencoba membuatnya lebih mudah. Parameter pertama menentukan key
pada objek status, yang akan berfungsi sebagai objek data untuk input. Parameter kedua adalah atribut, yang nilai dari bidang input akan disimpan. Kedua parameter tersebut ditetapkan dari BEJ menggunakan kata kunci bind
.
@addChangeHandler class LoginInput extends React.Component { constructor(props) { super(props); this.state = { login: {} }; } render() { return ( <input type='text' value={this.state.login.username} onChange={this.changeHandler.bind(this, 'login', 'username')} /> <input type='password' value={this.state.login.password} onChange={this.changeHandler.bind(this, 'login', 'password')} /> ) } }
Saat pengguna mengubah bidang nama pengguna, nilainya disimpan ke this.state.login.username
, tanpa perlu mendefinisikan lebih banyak penangan khusus.
Fungsi Panah
Dinamika JavaScript konteks this
telah menjadi rasa sakit yang konstan bagi pengembang karena, secara tidak sengaja, konteks fungsi bersarang this
diatur ulang ke global, bahkan di dalam kelas. Untuk memperbaikinya, biasanya perlu menyimpan this
ke beberapa variabel lingkup luar (biasanya _this
) dan menggunakannya di fungsi dalam:
class DirectorsStore { onFetch(directors) { var _this = this; this.directorsHash = {}; directors.forEach(function(x){ _this.directorsHash[x._id] = x; }) } }
Dengan sintaks ES6 baru, function(x){
dapat ditulis ulang sebagai (x) => {
. Definisi metode "panah" ini tidak hanya mengikat this
dengan benar ke lingkup luar, tetapi juga jauh lebih pendek, yang pasti diperhitungkan saat menulis banyak kode asinkron.
onFetch(directors) { this.directorsHash = {}; directors.forEach((x) => { this.directorsHash[x._id] = x; }) }
Menghancurkan Tugas
Penghancuran tugas, diperkenalkan di ES6, memungkinkan Anda untuk memiliki objek gabungan di sisi kiri tugas:
var o = {p: 42, q: true}; var {p, q} = o; console.log(p); // 42 console.log(q); // true
Ini bagus, tapi bagaimana itu benar-benar membantu kita di React? Perhatikan contoh berikut:
function makeRequest(url, method, params) { var config = { url: url, method: method, params: params }; ... }
Dengan destrukturisasi, Anda dapat menyimpan beberapa penekanan tombol. Kunci literal {url, method, params}
secara otomatis diberi nilai dari ruang lingkup dengan nama yang sama dengan kunci. Idiom ini cukup sering digunakan, dan menghilangkan pengulangan membuat kode kurang rawan kesalahan.
function makeRequest(url, method, params) { var config = {url, method, params}; ... }
Penghancuran juga dapat membantu Anda memuat hanya sebagian dari modul:
const {clone, assign} = require('lodash'); function output(data, optional) { var payload = clone(data); assign(payload, optional); }
Argumen: Default, Istirahat, dan Spread
Argumen fungsi lebih kuat di ES6. Terakhir, Anda dapat mengatur argumen default :
function http(endpoint, method='GET') { console.log(method) ... } http('/api') // GET
Bosan bermain-main dengan objek arguments
yang berat? Dengan spesifikasi baru, Anda bisa mendapatkan sisa argumen sebagai array:
function networkAction(context, method, ...rest) { // rest is an array return method.apply(context, rest); }
Dan jika Anda tidak suka memanggil apply()
, Anda bisa menyebarkan array ke dalam argumen fungsi:
myArguments = ['foo', 'bar', 123]; myFunction(...myArguments);
Generator dan Fungsi Async
ES6 memperkenalkan generator JavaScript. Generator pada dasarnya adalah fungsi JavaScript yang eksekusinya dapat dijeda dan kemudian dilanjutkan lagi nanti, mengingat statusnya. Setiap kali kata kunci yield
ditemukan, eksekusi dihentikan sementara, dan argumen yield
diteruskan kembali ke objek panggilan:
function* sequence(from, to) { console.log('Ready!'); while(from <= to) { yield from++; } }
Berikut adalah contoh generator ini beraksi:
> var cursor = sequence(1,3) Ready! > cursor.next() { value: 1, done: false } > cursor.next() { value: 2, done: false } > cursor.next() { value: 3, done: false } > cursor.next() { value: undefined, done: true }
Ketika kita memanggil fungsi generator, itu dijalankan sampai yield
pertama , dan kemudian berhenti. Setelah kita memanggil next()
, ia mengembalikan nilai pertama, dan melanjutkan eksekusi. Setiap yield
mengembalikan nilai lain, tetapi setelah panggilan ketiga, fungsi generator berakhir, dan setiap panggilan berikutnya ke next()
akan mengembalikan { value: undefined, done: true }
.
Tentu saja, tujuan dari generator bukanlah untuk membuat urutan numerik yang rumit. Bagian yang menarik adalah kemampuannya untuk menghentikan dan melanjutkan eksekusi fungsi, yang dapat digunakan untuk mengontrol aliran program asinkron dan akhirnya menyingkirkan fungsi panggilan balik yang mengganggu itu.
Untuk mendemonstrasikan ide ini, pertama-tama kita membutuhkan fungsi asinkron. Biasanya, kita akan memiliki beberapa operasi I/O, tetapi untuk kesederhanaan, mari kita gunakan setTimeout
dan mengembalikan sebuah janji. (Perhatikan bahwa ES6 juga memperkenalkan janji asli ke JavaScript.)
function asyncDouble(x) { var deferred = Promise.defer(); setTimeout(function(){ deferred.resolve(x*2); }, 1000); return deferred.promise; }
Selanjutnya, kita membutuhkan fungsi konsumen:
function consumer(generator){ var cursor = generator(); var value; function loop() { var data = cursor.next(value); if (data.done) { return; } else { data.value.then(x => { value = x; loop(); }) } } loop(); }
Fungsi ini menggunakan generator apa pun sebagai argumen, dan terus memanggil next()
di dalamnya selama ada nilai yang akan yield
. Dalam hal ini, nilai yang dihasilkan adalah janji, sehingga perlu menunggu hingga janji diselesaikan, dan menggunakan rekursi dengan loop()
untuk mencapai perulangan di seluruh fungsi bersarang.
Nilai kembalian diselesaikan di pengendali then()
, dan diteruskan ke value
, yang didefinisikan dalam lingkup luar, dan yang akan diteruskan ke panggilan next(value)
. Panggilan ini menjadikan nilai sebagai hasil dari ekspresi hasil yang sesuai. Ini berarti bahwa kita sekarang dapat menulis secara asinkron tanpa panggilan balik sama sekali:
function* myGenerator(){ const data1 = yield asyncDouble(1); console.log(`Double 1 = ${data1}`); const data2 = yield asyncDouble(2); console.log(`Double 2 = ${data2}`); const data3 = yield asyncDouble(3); console.log(`Double 3 = ${data3}`); } consumer(myGenerator);
Generator myGenerator
akan dijeda pada setiap yield
, menunggu konsumen memberikan janji yang diselesaikan. Dan memang, kita akan melihat angka yang dihitung muncul di konsol dalam interval satu detik.
Double 1 = 2 Double 2 = 4 Double 3 = 6
Ini menunjukkan konsep dasar, namun, saya tidak menyarankan Anda menggunakan kode ini dalam produksi. Sebaliknya, pilih perpustakaan yang sudah teruji seperti co. Ini akan memungkinkan Anda untuk dengan mudah menulis kode asinkron dengan hasil, termasuk penanganan kesalahan:
co(function *(){ var a = yield Promise.resolve(1); console.log(a); var b = yield Promise.resolve(2); console.log(b); var c = yield Promise.resolve(3); console.log(c); }).catch(function(err){ console.error(err.stack); });
Jadi, contoh ini menunjukkan cara menulis kode asinkron tanpa panggilan balik menggunakan generator ES6. ES7 mengambil pendekatan ini satu langkah lebih jauh dengan memperkenalkan kata kunci async
dan await
, dan menghilangkan kebutuhan akan perpustakaan generator sama sekali. Dengan kemampuan ini, contoh sebelumnya akan terlihat seperti ini:
async function (){ try { var a = await Promise.resolve(1); console.log(a); var b = await Promise.resolve(2); console.log(b); var c = await Promise.resolve(3); console.log(c); } catch (err) { console.error(err.stack); } };
Menurut pendapat saya, ini menghilangkan rasa sakit dari bekerja dengan kode asinkron dalam JavaScript. Tidak hanya di React, tetapi juga di tempat lain.
Generator tidak hanya lebih ringkas dan lugas, tetapi juga memungkinkan kita menggunakan teknik yang akan sangat sulit diterapkan dengan callback. Contoh yang menonjol dari kebaikan generator adalah library middleware koa untuk Node.js. Ini bertujuan untuk menggantikan Express, dan, menuju tujuan itu, ia hadir dengan satu fitur pembunuh: Rantai middleware tidak hanya mengalir ke hilir (dengan permintaan klien), tetapi juga ke hulu , memungkinkan modifikasi lebih lanjut pada respons server. Perhatikan contoh server koa berikut:
// Response time logger middleware app.use(function *(next){ // Downstream var start = new Date; yield next; // Upstream this.body += ' World'; var ms = new Date - start; console.log('%s %s - %s', this.method, this.url, ms); }); // Response handler app.use(function *(){ this.body = 'Hello'; }); app.listen(3000);

Middleware respons yield
s hilir ke penangan respons, yang menetapkan badan respons, dan dalam aliran hulu (setelah ekspresi yield
), modifikasi lebih lanjut dari this.body
diperbolehkan, serta fungsi lain seperti pencatatan waktu, yang dimungkinkan karena hulu dan hilir memiliki konteks fungsi yang sama. Ini jauh lebih kuat daripada Express, di mana upaya untuk mencapai hal yang sama akan berakhir seperti ini:
var start; // Downstream middleware app.use(function(req, res, next) { start = new Date; next(); // Already returned, cannot continue here }); // Response app.use(function (req, res, next){ res.send('Hello World') next(); }); // Upstream middleware app.use(function(req, res, next) { // res already sent, cannot modify var ms = new Date - start; console.log('%s %s - %s', this.method, this.url, ms); next(); }); app.listen(3000);
Anda mungkin sudah bisa melihat apa yang salah di sini; menggunakan variabel start
"global" akan menghasilkan kondisi balapan, mengembalikan omong kosong dengan permintaan bersamaan. Solusinya adalah beberapa solusi yang tidak jelas, dan Anda dapat melupakan tentang memodifikasi respons di aliran hulu.
Juga, saat menggunakan koa, Anda akan mendapatkan alur kerja asinkron generator secara gratis:
app.use(function *(){ try { const part1 = yield fs.readFile(this.request.query.file1, 'utf8'); const part2 = yield fs.readFile(this.request.query.file2, 'utf8'); this.body = part1 + part2; } catch (err) { this.status = 404 this.body = err; } }); app.listen(3000);
Anda dapat membayangkan janji dan panggilan balik yang terlibat dalam pembuatan ulang contoh kecil ini di Express.
Bagaimana semua pembicaraan Node.js ini berhubungan dengan React? Nah, Node adalah pilihan pertama ketika mempertimbangkan back end yang cocok untuk React. Karena Node juga ditulis dalam JavaScript, ia mendukung berbagi kode antara back end dan front end, memungkinkan kita untuk membangun aplikasi web React isomorfik. Tapi, lebih lanjut tentang ini nanti.
Perpustakaan Fluks
React sangat hebat dalam membuat komponen tampilan yang dapat dikomposisi, tetapi kita memerlukan beberapa cara untuk mengelola data dan status di seluruh aplikasi. Hampir secara universal disepakati bahwa React paling baik dilengkapi dengan arsitektur aplikasi Flux. Jika Anda benar-benar baru mengenal Flux, saya merekomendasikan penyegaran cepat.
Apa yang belum disepakati secara universal adalah yang mana dari banyak implementasi Flux yang harus dipilih. Facebook Flux akan menjadi pilihan yang jelas, tetapi bagi kebanyakan orang itu terlalu bertele-tele. Implementasi alternatif sebagian besar berfokus pada pengurangan jumlah boilerplate yang diperlukan dengan pendekatan konvensi-over-konfigurasi, dan juga dengan beberapa fungsi kenyamanan untuk komponen tingkat tinggi, rendering sisi server, dan sebagainya. Beberapa pesaing teratas, dengan berbagai metrik popularitas, dapat dilihat di sini. Saya telah melihat ke Alt, Reflux, Flummox, Fluxxor, dan Marty.js.
Cara saya memilih perpustakaan yang tepat sama sekali tidak objektif, tetapi mungkin tetap membantu. Fluxxor adalah salah satu perpustakaan pertama yang saya periksa, tetapi sekarang terlihat agak basi. Marty.js menarik, dan memiliki banyak fitur, tetapi masih melibatkan banyak boilerplate, dan beberapa fungsi tampak berlebihan. Reflux tampak hebat dan memiliki daya tarik, tetapi terasa agak sulit bagi pemula, dan juga tidak memiliki dokumentasi yang tepat. Flummox dan Alt sangat mirip, tetapi Alt tampaknya memiliki boilerplate yang lebih sedikit, pengembangan yang sangat aktif, dokumentasi terkini, dan komunitas Slack yang membantu. Oleh karena itu, saya memilih Alt.
Alt Fluks
Dengan Alt, alur kerja Flux menjadi lebih sederhana tanpa kehilangan kekuatannya. Dokumentasi Flux Facebook menjelaskan banyak hal tentang operator, tetapi kita bebas mengabaikannya karena, di Alt, operator secara implisit terhubung ke tindakan berdasarkan konvensi, dan biasanya tidak memerlukan kode kustom apa pun. Ini membuat kita hanya memiliki toko , tindakan , dan komponen . Ketiga lapisan ini dapat digunakan sedemikian rupa sehingga mereka memetakan dengan baik ke dalam model pemikiran MVC : Stores adalah Models , actions adalah Controllers , dan komponen adalah Views . Perbedaan utama adalah aliran data searah yang menjadi pusat pola Flux, yang berarti, bahwa pengontrol (tindakan) tidak dapat secara langsung mengubah tampilan (komponen), tetapi, sebaliknya, hanya dapat memicu modifikasi model (penyimpanan), yang pandangannya terikat secara pasif. Ini sudah menjadi praktik terbaik untuk beberapa pengembang Angular yang tercerahkan.
Alur kerjanya adalah sebagai berikut:
- Komponen memulai tindakan.
- Toko mendengarkan tindakan dan memperbarui data.
- Komponen terikat ke penyimpanan, dan dirender saat data diperbarui.
tindakan
Saat menggunakan perpustakaan Alt Flux, tindakan umumnya datang dalam dua rasa: otomatis dan manual. Tindakan otomatis dibuat menggunakan fungsi generateActions
, dan langsung menuju ke operator. Metode manual didefinisikan sebagai metode kelas tindakan Anda, dan metode tersebut dapat dikirim ke operator dengan muatan tambahan. Kasus penggunaan paling umum dari tindakan otomatis adalah memberi tahu toko tentang beberapa peristiwa dalam aplikasi. Tindakan manual, antara lain, cara yang disukai untuk menangani interaksi server.
Jadi panggilan REST API milik tindakan. Alur kerja lengkapnya adalah sebagai berikut:
- Komponen memicu tindakan.
- Pembuat tindakan menjalankan permintaan server asinkron, dan hasilnya dikirim ke operator sebagai muatan.
- Toko mendengarkan tindakan, penangan tindakan yang sesuai menerima hasilnya sebagai argumen, dan toko memperbarui statusnya sesuai dengan itu.
Untuk permintaan AJAX, kita dapat menggunakan pustaka axios, yang, antara lain, menangani data dan header JSON dengan mulus. Alih-alih janji atau panggilan balik, kita dapat menggunakan pola async
/ await
ES7. Jika status respons POST
bukan 2XX, kesalahan akan muncul, dan kami mengirimkan data yang dikembalikan, atau kesalahan yang diterima.
Mari kita lihat halaman login untuk contoh sederhana dari alur kerja Alt. Tindakan logout tidak perlu melakukan sesuatu yang khusus, cukup beri tahu toko, sehingga kami dapat membuatnya secara otomatis. Tindakan login bersifat manual, dan mengharapkan data login sebagai parameter bagi pembuat tindakan. Setelah kami mendapat respons dari server, kami mengirimkan data sukses, atau, jika ada kesalahan, kami mengirimkan kesalahan yang diterima.
class LoginActions { constructor() { // Automatic action this.generateActions('logout'); } // Manual action async login(data) { try { const response = await axios.post('/auth/login', data); this.dispatch({ok: true, user: response.data}); } catch (err) { console.error(err); this.dispatch({ok: false, error: err.data}); } } } module.exports = (alt.createActions(LoginActions));
Toko
Toko Flux melayani dua tujuan: Memiliki penangan tindakan, dan membawa status. Mari kita lanjutkan contoh halaman login kita untuk melihat cara kerjanya.
Mari buat LoginStore
, dengan dua atribut status: user
, untuk pengguna yang masuk saat ini, dan error
, untuk kesalahan terkait login saat ini. Dengan semangat mengurangi boilerplate, Alt memungkinkan kita untuk mengikat semua tindakan dari satu kelas dengan satu fungsi bindActions
.
class LoginStore { constructor() { this.bindActions(LoginActions); this.user = null; this.error = null; } ...
Nama handler ditentukan oleh konvensi, diawali on
nama tindakan yang sesuai. Jadi tindakan login
ditangani oleh onLogin
, dan seterusnya. Perhatikan bahwa huruf pertama dari nama tindakan akan dikapitalisasi dalam gaya camelCase. Di LoginStore
kami, kami memiliki penangan berikut, yang dipanggil oleh tindakan yang sesuai:
... onLogin(data) { if (data.ok) { this.user = data.user; this.error = null; router.transitionTo('home'); } else { this.user = null; this.error = data.error } } onLogout() { this.user = null; this.error = null; } }
Komponen
Cara yang biasa untuk mengikat toko ke komponen adalah dengan menggunakan semacam mixin Bereaksi. Tetapi karena mixin sudah ketinggalan zaman, perlu ada cara lain. Salah satu pendekatan baru adalah dengan menggunakan komponen tingkat tinggi. Kami mengambil komponen kami dan memasukkannya ke dalam komponen pembungkus, yang akan menangani mendengarkan penyimpanan dan memanggil render ulang. Komponen kami akan menerima status toko di props
. Pendekatan ini juga membantu untuk mengatur kode kita menjadi komponen cerdas dan bodoh, yang telah menjadi trend akhir-akhir ini. Untuk Alt, pembungkus komponen diimplementasikan oleh AltContainer
:
export default class Login extends React.Component { render() { return ( <AltContainer stores={{LoginStore: LoginStore}}> <LoginPage/> </AltContainer> )} }
Komponen LoginPage
kami juga menggunakan dekorator changeHandler
yang diperkenalkan sebelumnya. Data dari LoginStore
digunakan untuk menampilkan kesalahan jika login gagal, dan rendering ulang ditangani oleh AltContainer
. Mengklik tombol masuk menjalankan tindakan login
, menyelesaikan alur kerja fluks Alt:
@changeHandler export default class LoginPage extends React.Component { constructor(props) { super(props); this.state = { loginForm: {} }; } login() { LoginActions.login(this.state.loginForm) } render() { return ( <Alert>{{this.props.LoginStore.error}}</Alert> <Input label='Username' type='text' value={this.state.login.username} onChange={this.changeHandler.bind(this, 'loginForm', 'username')} /> <Input label='Password' type='password' value={this.state.login.password} onChange={this.changeHandler.bind(this, 'loginForm', 'password')} /> <Button onClick={this.login.bind(this)}>Login</Button> )} }
Rendering Isomorfik
Aplikasi web isomorfik adalah topik hangat akhir-akhir ini karena mereka menyelesaikan beberapa tugas terbesar dari aplikasi satu halaman tradisional. Dalam aplikasi tersebut, markup dibuat secara dinamis oleh JavaScript di browser. Hasilnya adalah konten tidak tersedia untuk klien dengan JavaScript dimatikan, terutama, perayap web mesin telusur. Ini berarti halaman web Anda tidak diindeks dan tidak muncul di hasil pencarian. Ada beberapa cara untuk menyiasatinya, tetapi masih jauh dari optimal. Pendekatan isomorfik mencoba untuk memperbaiki masalah ini dengan melakukan pra-rendering URL yang diminta dari aplikasi satu halaman di server. Dengan Node.js, Anda memiliki JavaScript di server, artinya React juga dapat menjalankan sisi server. Ini seharusnya tidak terlalu sulit, bukan?
Salah satu kendalanya adalah beberapa library Flux, terutama yang menggunakan singleton, mengalami kesulitan dengan rendering sisi server. Ketika Anda memiliki toko Flux tunggal dan beberapa permintaan bersamaan ke server Anda, data akan tercampur. Beberapa perpustakaan menyelesaikan ini dengan menggunakan instance Flux, tetapi ini disertai dengan kelemahan lain, terutama kebutuhan untuk meneruskan instance tersebut dalam kode Anda. Alt juga menawarkan instans Flux, tetapi juga telah memecahkan masalah rendering sisi server dengan lajang; itu menyiram toko setelah setiap permintaan, sehingga setiap permintaan bersamaan dimulai dengan yang bersih.
Inti dari fungsionalitas rendering sisi server disediakan oleh React.renderToString
. Seluruh aplikasi front end React juga dijalankan di server. Dengan cara ini, kita tidak perlu menunggu JavaScript sisi klien untuk membuat markup; itu sudah dibuat sebelumnya di server untuk URL yang diakses, dan dikirim ke browser sebagai HTML. Saat JavaScript klien berjalan, ia melanjutkan dari bagian yang ditinggalkan server. Untuk mendukung ini, kita dapat menggunakan perpustakaan Iso, yang dimaksudkan untuk digunakan dengan Alt.
Pertama, kita menginisialisasi Flux di server menggunakan alt.bootstrap
. Dimungkinkan untuk mengisi awal penyimpanan Flux dengan data untuk rendering. Penting juga untuk memutuskan komponen mana yang akan dirender untuk URL mana, yang merupakan fungsionalitas dari Router
sisi klien . Kami menggunakan versi tunggal dari alt
, jadi setelah setiap render, kami perlu alt.flush()
toko untuk membersihkannya untuk permintaan lain. Menggunakan add-on iso
, status Flux diserialkan ke markup HTML, sehingga klien tahu di mana harus mengambil:
// We use react-router to run the URL that is provided in routes.jsx var getHandler = function(routes, url) { var deferred = Promise.defer(); Router.run(routes, url, function (Handler) { deferred.resolve(Handler); }); return deferred.promise; }; app.use(function *(next) { yield next; // We seed our stores with data alt.bootstrap(JSON.stringify(this.locals.data || {})); var iso = new Iso(); const handler = yield getHandler(reactRoutes, this.request.url); const node = React.renderToString(React.createElement(handler)); iso.add(node, alt.flush()); this.render('layout', {html: iso.render()}); });
Di sisi klien, kami mengambil status server, dan bootstrap alt
dengan data. Kemudian kita menjalankan Router
dan React.render
pada wadah target, yang akan memperbarui markup yang dihasilkan server seperlunya.
Iso.bootstrap(function (state, _, container) { // Bootstrap the state from the server alt.bootstrap(state) Router.run(routes, Router.HistoryLocation, function (Handler, req) { let node = React.createElement(Handler) React.render(node, container) }) })
Cantik!
Perpustakaan Front-End yang Berguna
Panduan ekosistem React tidak akan lengkap tanpa menyebutkan beberapa library front-end yang bermain sangat baik dengan React. Pustaka ini menangani tugas paling umum yang ditemukan di hampir setiap aplikasi web: tata letak dan wadah CSS, formulir dan tombol bergaya, validasi, pemilihan tanggal, dan sebagainya. Tidak ada gunanya menemukan kembali roda ketika masalah itu telah dipecahkan.
React-Bootstrap
Twitter's Bootstrap framework has become commonplace since it is of immense help to every web developer who does not want to spend a ton of time working in CSS. In the prototyping phase especially, Bootstrap is indispensable. To leverage the power of bootstrap in a React application, it's good to use it with React-Bootstrap, which comes with nice React syntax and re-implements Bootstrap's jQuery plugins using native React components. The resulting code is terse and easy to understand:
<Navbar brand='React-Bootstrap'> <Nav> <NavItem eventKey={1} href='#'>Link</NavItem> <NavItem eventKey={2} href='#'>Link</NavItem> <DropdownButton eventKey={3} title='Dropdown'> <MenuItem eventKey='1'>Action</MenuItem> <MenuItem eventKey='2'>Another action</MenuItem> <MenuItem eventKey='3'>Something else here</MenuItem> <MenuItem divider /> <MenuItem eventKey='4'>Separated link</MenuItem> </DropdownButton> </Nav> </Navbar>
Personally, I cannot escape the feeling that this is what HTML should always have been like.
If you want to use Bootstrap source with Webpack, consider using less-loader or bootstrap-sass-loader, depending on the preprocessor you prefer. It will allow you to easily customize which Bootstrap components to include, and also allow the usage of LESS or SASS global variables in your CSS code.
React Router
React Router has become the de-facto standard for routing in React. It allows nested routing, support for redirections, plays nicely with isomorphic rendering, and has a simple JSX-based syntax:
<Router history={new BrowserHistory}> <Route path="/" component={App}> <Route path="about" name="about" component={About}/> <Route path="users" name="users" component={Users} indexComponent={RecentUsers}> <Route path="/user/:userId" name="user" component={User}/> </Route> <Route path="*" component={NoMatch}/> </Route> </Router>
React Router also provides a Link
component that you can use for navigation in your application, specifying only the route name:
<nav> <Link to="about">About</Link> <Link to="users">Users</Link> </nav>
There is even a library for integration with React-Bootstrap, so if you are using Bootstrap's components and don't feel like setting the active
class on them manually all the time, you can use react-router-bootstrap and write code like this:
<Nav> <NavItemLink to="about">About</NavItemLink> <NavItemLink to="users">Users</NavItemLink> </Nav>
No additional setup is necessary. Active links will take care of themselves.
Formsy-React
Working with forms can be tedious, so let's get some help from the formsy-react library, which will help us manage validations and data models. The Formsy-React library, strangely enough, does not include the actual form inputs because users are encouraged to write their own (which is great). But if you are content with the common ones, just use formsy-react-components. Bootstrap classes are included:
import Formsy from 'formsy-react'; import {Input} from 'formsy-react-components'; export default class FormsyForm extends React.Component { enableButton() { this.setState({canSubmit: true}); } disableButton() { this.setState({canSubmit: true}); } submit(model) { FormActions.saveEmail(model.email); } render() { return ( <Formsy.Form onValidSubmit={this.submit} onValid={this.enableButton} onInvalid={this.disableButton}> <Input name="email" validations="isEmail" validationError="This is not a valid email" required/> <button type="submit" disabled={!this.state.canSubmit}>Submit</button> </Formsy.Form> )} }
Calendar and Typeahead
Calendar and typeahead are icing on the cake of every UI toolkit. Sadly, these two components were removed from Bootstrap 3, probably because they are too specialized for a general purpose CSS framework. Luckily, I've been able to find worthy replacements in react-pikaday and react-select. I've tested more than 10 libraries, and these two came out as the best. They are dead easy to use, as well:
import Pikaday from 'react-pikaday'; import Select from 'react-select'; export default class CalendarAndTypeahead extends React.Component { constructor(props){ super(props); this.options = [ { value: 'one', label: 'One' }, { value: 'two', label: 'Two' } ]; } dateChange(date) { this.setState({date: date}); }, selectChange(selected) { this.setState({selected: selected}); }, render() { return ( <Pikaday value={this.state.date} onChange={this.dateChange} /> <Select name="form-field-name" value={this.state.selected} options={this.options} onChange={selectChange} /> )} }
Conclusion - React.JS
In this article I've presented libraries and techniques that I consider some of the most productive in current web development. Some of them are React-specific, but due to React's open nature, many of them are usable in other environments as well. Technological progress is sometimes hindered by fear of the newest stuff, so I hope this article will help to dissipate doubts concerning React, Flux and the newest features in ECMAScript.
If you are interested, you can take a look at my example application built with these technologies. The source code is available on GitHub.
Terima kasih sudah membaca!