Membangun Aplikasi Reaktif dengan Redux, RxJS, dan Redux-Observable di React Native
Diterbitkan: 2022-03-11Dalam ekosistem yang berkembang dari aplikasi web dan seluler yang kaya dan kuat, semakin banyak status yang harus dikelola, seperti pengguna saat ini, daftar item yang dimuat, status pemuatan, kesalahan, dan banyak lagi. Redux adalah salah satu solusi untuk masalah ini dengan menjaga negara dalam objek global.
Salah satu keterbatasan Redux adalah tidak mendukung perilaku asinkron di luar kotak. Salah satu solusi untuk ini adalah redux-observable
, yang didasarkan pada RxJS, pustaka yang kuat untuk pemrograman reaktif dalam JavaScript. RxJS adalah implementasi dari ReactiveX, sebuah API untuk pemrograman reaktif yang berasal dari Microsoft. ReactiveX menggabungkan beberapa fitur yang paling kuat dari paradigma reaktif, pemrograman fungsional, pola pengamat dan pola iterator.
Dalam tutorial ini, kita akan belajar tentang Redux dan penggunaannya dengan React. Kami juga akan mengeksplorasi pemrograman reaktif menggunakan RxJS dan bagaimana hal itu dapat membuat pekerjaan asinkron yang membosankan dan kompleks menjadi sangat sederhana.
Terakhir, kita akan mempelajari redux-observable, sebuah library yang memanfaatkan RxJS untuk melakukan pekerjaan asinkron, dan kemudian akan membangun aplikasi di React Native menggunakan Redux dan redux-observable.
redux
Seperti yang dijelaskan di GitHub, Redux adalah “wadah negara yang dapat diprediksi untuk aplikasi JavaScript.” Ini memberi aplikasi JavaScript Anda status global, menjauhkan status dan tindakan dari komponen React.
Dalam aplikasi React biasa tanpa Redux, kita harus meneruskan data dari node root ke anak-anak melalui properti, atau props
. Aliran data ini dapat dikelola untuk aplikasi kecil tetapi bisa menjadi sangat kompleks seiring dengan pertumbuhan aplikasi Anda. Redux memungkinkan kita untuk memiliki komponen yang independen satu sama lain, sehingga kita dapat menggunakannya sebagai satu sumber kebenaran.
Redux dapat digunakan di React menggunakan react-redux
, yang menyediakan binding untuk komponen React untuk membaca data dari Redux dan mengirim tindakan untuk memperbarui status Redux.
Redux dapat digambarkan sebagai tiga prinsip sederhana:
1. Sumber Kebenaran Tunggal
Status seluruh aplikasi Anda disimpan dalam satu objek. Objek di Redux ini dipegang oleh toko. Seharusnya ada satu toko di aplikasi Redux apa pun.
» console.log(store.getState()) « { user: {...}, todos: {...} }
Untuk membaca data dari Redux di komponen React Anda, kami menggunakan fungsi connect
dari react-redux
. connect
membutuhkan empat argumen, yang semuanya opsional. Untuk saat ini, kita akan fokus pada yang pertama, yang disebut mapStateToProps
.
/* UserTile.js */ import { connect } from 'react-redux'; class UserTile extends React.Component { render() { return <p>{ this.props.user.name }</p> } } function mapStateToProps(state) { return { user: state.user } } export default connect(mapStateToProps)(UserTile)
Dalam contoh di atas, mapStateToProps
menerima status Redux global sebagai argumen pertamanya dan mengembalikan objek yang akan digabungkan dengan properti yang diteruskan ke <UserTile />
oleh komponen induknya.
2. Status Hanya-Baca
Status redux bersifat read-only untuk komponen React, dan satu-satunya cara untuk mengubah status adalah dengan mengeluarkan action . Tindakan adalah objek polos yang mewakili niat untuk mengubah keadaan. Setiap objek tindakan harus memiliki bidang type
, dan nilainya harus berupa string. Selain itu, konten tindakan sepenuhnya terserah Anda, tetapi sebagian besar aplikasi mengikuti format tindakan standar fluks, yang membatasi struktur tindakan hanya empat kunci:
-
type
Setiap pengidentifikasi string untuk suatu tindakan. Setiap tindakan harus memiliki tindakan yang unik. -
payload
Data opsional untuk tindakan apa pun. Itu bisa kapan saja dan berisi informasi tentang tindakan. -
error
Setiap properti boolean opsional disetel ke true jika tindakan menunjukkan kesalahan. Ini analog denganPromise. string
pengidentifikasiPromise. string
untuk suatu tindakan. Setiap tindakan harus memiliki tindakan yang unik. Menurut konvensi, ketikaerror
adalahtrue
,payload
harus berupa objek error. -
meta
Meta dapat berupa jenis nilai apa pun. Ini ditujukan untuk informasi tambahan apa pun yang bukan merupakan bagian dari muatan.
Berikut adalah dua contoh tindakan:
store.dispatch({ type: 'GET_USER', payload: '21', }); store.dispatch({ type: 'GET_USER_SUCCESS', payload: { user: { id: '21', name: 'Foo' } } });
3. Status Diubah dengan Fungsi Murni
Status Redux global diubah menggunakan fungsi murni yang disebut reduksi. Peredam mengambil status dan tindakan sebelumnya dan mengembalikan status berikutnya. Peredam membuat objek status baru alih-alih mengubah yang sudah ada. Bergantung pada ukuran aplikasi, toko Redux dapat memiliki satu peredam atau beberapa peredam.
/* store.js */ import { combineReducers, createStore } from 'redux' function user(state = {}, action) { switch (action.type) { case 'GET_USER_SUCCESS': return action.payload.user default: return state } } function todos(state = [], action) { switch (action.type) { case 'ADD_TODO_SUCCESS': return [ ...state, { id: uuid(), // a random uuid generator function text: action.text, completed: false } ] case 'COMPLETE_TODO_SUCCESS': return state.map(todo => { if (todo.id === action.id) { return { ...todo, completed: true } } return todo }) default: return state } } const rootReducer = combineReducers({ user, todos }) const store = createStore(rootReducer)
Mirip dengan membaca dari keadaan, kita dapat menggunakan fungsi connect
untuk mengirim tindakan.
/* UserProfile.js */ class Profile extends React.Component { handleSave(user) { this.props.updateUser(user); } } function mapDispatchToProps(dispatch) { return ({ updateUser: (user) => dispatch({ type: 'GET_USER_SUCCESS', user, }), }) } export default connect(mapStateToProps, mapDispatchToProps)(Profile);
RxJS
Pemrograman Reaktif
Pemrograman reaktif adalah paradigma pemrograman deklaratif yang berhubungan dengan aliran data dalam " aliran " dan dengan penyebaran dan perubahannya. RxJS, perpustakaan untuk pemrograman reaktif dalam JavaScript, memiliki konsep yang dapat diamati , yang merupakan aliran data yang dapat diikuti oleh pengamat, dan pengamat ini mengirimkan data dari waktu ke waktu.
Pengamat dari sebuah observable adalah sebuah objek dengan tiga fungsi: next
, error
, dan complete
. Semua fungsi ini opsional.
observable.subscribe({ next: value => console.log(`Value is ${value}`), error: err => console.log(err), complete: () => console.log(`Completed`), })
Fungsi .subscribe
juga dapat memiliki tiga fungsi sebagai ganti objek.
observable.subscribe( value => console.log(`Value is ${value}`), err => console.log(err), () => console.log(`Completed`) )
Kita dapat membuat observable baru dengan membuat objek dari observable
, meneruskan fungsi yang menerima subscriber alias observer. Pelanggan memiliki tiga metode: next
, error
, dan complete
. Pelanggan dapat menelepon berikutnya dengan nilai sebanyak yang diperlukan, dan complete
atau error
pada akhirnya. Setelah memanggil complete
atau error
, observable tidak akan mendorong nilai apa pun ke bawah aliran.
import { Observable } from 'rxjs' const observable$ = new Observable(function subscribe(subscriber) { const intervalId = setInterval(() => { subscriber.next('hi'); subscriber.complete() clearInterval(intervalId); }, 1000); }); observable$.subscribe( value => console.log(`Value is ${value}`), err => console.log(err) )
Contoh di atas akan mencetak Value is hi
setelah 1000 milidetik.
Membuat yang dapat diamati secara manual setiap saat bisa menjadi bertele-tele dan membosankan. Oleh karena itu, RxJS memiliki banyak fungsi untuk membuat sebuah observable. Beberapa yang paling umum digunakan adalah of
, from
, dan ajax
.
dari
of
mengambil urutan nilai dan mengubahnya menjadi aliran:

import { of } from 'rxjs' of(1, 2, 3, 'Hello', 'World').subscribe(value => console.log(value)) // 1 2 3 Hello World
dari
from
mengubah hampir semua hal menjadi aliran nilai:
import { from } from 'rxjs' from([1, 2, 3]).subscribe(console.log) // 1 2 3 from(new Promise.resolve('Hello World')).subscribe(console.log) // 'Hello World' from(fibonacciGenerator).subscribe(console.log) // 1 1 2 3 5 8 13 21 ...
ajax
ajax
mengambil URL string atau membuat yang dapat diamati yang membuat permintaan HTTP. ajax
memiliki fungsi ajax.getJSON
, yang hanya mengembalikan objek respons bersarang dari panggilan AJAX tanpa properti lain yang dikembalikan oleh ajax()
:
import { ajax } from 'rxjs/ajax' ajax('https://jsonplaceholder.typicode.com/todos/1').subscribe(console.log) // {request, response: {userId, id, title, completed}, responseType, status} ajax.getJSON('https://jsonplaceholder.typicode.com/todos/1').subscribe(console.log) // {userId, id, title, completed} ajax({ url, method, headers, body }).subscribe(console.log) // {...}
Ada banyak cara lain untuk membuat observable (daftar lengkapnya bisa dilihat di sini).
Operator
Operator adalah pembangkit tenaga listrik nyata dari RxJS, yang memiliki operator untuk hampir semua hal yang Anda perlukan. Sejak RxJS 6, operator bukanlah metode pada objek yang dapat diamati tetapi fungsi murni yang diterapkan pada objek yang dapat diamati menggunakan metode .pipe
.
peta
map
mengambil fungsi argumen tunggal dan menerapkan proyeksi pada setiap elemen dalam aliran:
import { of } from 'rxjs' import { map } from 'rxjs/operators' of(1, 2, 3, 4, 5).pipe( map(i=> i * 2) ).subscribe(console.log) // 2, 4, 6, 8, 10
Saring
filter
mengambil satu argumen dan menghapus nilai dari aliran yang mengembalikan false untuk fungsi yang diberikan:
import { of } from 'rxjs' import { map, filter } from 'rxjs/operators' of(1, 2, 3, 4, 5).pipe( map(i => i * i), filter(i => i % 2 === 0) ).subscribe(console.log) // 4, 16
peta datar
operator flatMap
mengambil fungsi yang memetakan setiap item di steam ke aliran lain dan meratakan semua nilai aliran ini:
import { of } from 'rxjs' import { ajax } from 'rxjs/ajax' import { flatMap } from 'rxjs/operators' of(1, 2, 3).pipe( flatMap(page => ajax.toJSON(`https://example.com/blog?size=2&page=${page}`)), ).subscribe(console.log) // [ { blog 1 }, { blog 2 }, { blog 3 }, { blog 4 }, { blog 5 }, { blog 6 } ]
menggabungkan
merge
menggabungkan item dari dua aliran dalam urutan kedatangannya:
import { interval, merge } from 'rxjs' import { pipe, take, mapTo } from 'rxjs/operators' merge( interval(150).pipe(take(5), mapTo('A')), interval(250).pipe(take(5), mapTo('B')) ).subscribe(console.log) // ABAABAABBB
Daftar lengkap operator tersedia di sini.
Redux-Diamati
Secara desain, semua tindakan di Redux sinkron. Redux-observable adalah middleware untuk Redux yang menggunakan aliran yang dapat diamati untuk melakukan pekerjaan asinkron dan kemudian mengirimkan tindakan lain di Redux dengan hasil pekerjaan asinkron tersebut.
Redux-observable didasarkan pada ide Epics . Epik adalah fungsi yang mengambil aliran tindakan, dan secara opsional aliran status dan mengembalikan aliran tindakan.
fungsi (aksi$: Dapat diamati
Dengan konvensi, setiap variabel yang merupakan aliran (_aka _observable
) diakhiri dengan $
. Sebelum kita dapat menggunakan redux-observable, kita harus menambahkannya sebagai middleware di toko kita. Karena epik adalah aliran yang dapat diamati dan setiap tindakan yang keluar dari uap ini disalurkan kembali ke aliran, mengembalikan tindakan yang sama akan menghasilkan loop tak terbatas.
const epic = action$ => action$.pipe( filter(action => action.type === 'FOO'), mapTo({ type: 'BAR' }) // not changing the type of action returned // will also result in an infinite loop ) // or import { ofType } from 'redux-observable' const epic = action$ => action$.pipe( ofType('FOO'), mapTo({ type: BAZ' }) )
Pikirkan arsitektur reaktif ini sebagai sistem pipa di mana setiap keluaran pipa diumpankan kembali ke setiap pipa, termasuk dirinya sendiri, dan juga ke reduksi Redux. Filter di atas pipa inilah yang memutuskan apa yang masuk dan apa yang diblokir.
Mari kita lihat bagaimana epik Ping-Pong akan bekerja. Dibutuhkan ping, mengirimkannya ke server—dan setelah permintaan selesai—mengirim pong kembali ke aplikasi.
const pingEpic = action$ => action$.pipe( ofType('PING'), flatMap(action => ajax('https://example.com/pinger')), mapTo({ type: 'PONG' }) ) Now, we are going to update our original todo store by adding epics and retrieving users. import { combineReducers, createStore } from 'redux' import { ofType, combineEpics, createEpicMiddleware } from 'redux-observable'; import { map, flatMap } from 'rxjs/operators' import { ajax } from 'rxjs/ajax' // ... /* user and todos reducers defined as above */ const rootReducer = combineReducers({ user, todos }) const epicMiddleware = createEpicMiddleware(); const userEpic = action$ => action$.pipe( ofType('GET_USER'), flatMap(() => ajax.getJSON('https://foo.bar.com/get-user')), map(user => ({ type: 'GET_USER_SUCCESS', payload: user })) ) const addTodoEpic = action$ => action$.pipe( ofType('ADD_TODO'), flatMap(action => ajax({ url: 'https://foo.bar.com/add-todo', method: 'POST', body: { text: action.payload } })), map(data => data.response), map(todo => ({ type: 'ADD_TODO_SUCCESS', payload: todo })) ) const completeTodoEpic = action$ => action$.pipe( ofType('COMPLETE_TODO'), flatMap(action => ajax({ url: 'https://foo.bar.com/complete-todo', method: 'POST', body: { id: action.payload } })), map(data => data.response), map(todo => ({ type: 'COMPLEE_TODO_SUCCESS', payload: todo })) ) const rootEpic = combineEpics(userEpic, addTodoEpic, completeTodoEpic) const store = createStore(rootReducer, applyMiddleware(epicMiddleware)) epicMiddleware.run(rootEpic);
_Penting: Epik sama seperti aliran lain yang dapat diamati di RxJS. Mereka dapat berakhir dalam keadaan lengkap atau kesalahan. Setelah status ini, epik—dan aplikasi Anda—akan berhenti berfungsi. Jadi, Anda harus menangkap setiap potensi kesalahan dalam uap. Anda dapat menggunakan operator __catchError__
untuk ini. Informasi lebih lanjut: Penanganan Kesalahan di redux-observable .
Aplikasi Todo yang Reaktif
Dengan beberapa UI ditambahkan, aplikasi demo (minimal) terlihat seperti ini:
Ringkasan Tutorial React, Redux, dan RxJS
Kami mempelajari apa itu aplikasi reaktif. Kami juga belajar tentang Redux, RxJS, dan redux-observable, dan bahkan membuat aplikasi Todo reaktif di Expo dengan React Native. Untuk pengembang React dan React Native, tren saat ini menawarkan beberapa opsi manajemen status yang sangat kuat.
Sekali lagi, kode sumber untuk aplikasi ini ada di GitHub. Jangan ragu untuk membagikan pemikiran Anda tentang manajemen negara untuk aplikasi reaktif di komentar di bawah.