Membangun Aplikasi Reaktif dengan Redux, RxJS, dan Redux-Observable di React Native

Diterbitkan: 2022-03-11

Dalam 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

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:

  1. type Setiap pengidentifikasi string untuk suatu tindakan. Setiap tindakan harus memiliki tindakan yang unik.
  2. payload Data opsional untuk tindakan apa pun. Itu bisa kapan saja dan berisi informasi tentang tindakan.
  3. error Setiap properti boolean opsional disetel ke true jika tindakan menunjukkan kesalahan. Ini analog dengan Promise. string pengidentifikasi Promise. string untuk suatu tindakan. Setiap tindakan harus memiliki tindakan yang unik. Menurut konvensi, ketika error adalah true , payload harus berupa objek error.
  4. 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

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 

Peta

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 

Saring

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 } ] 

Peta Datar

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 

Menggabungkan

Daftar lengkap operator tersedia di sini.

Redux-Diamati

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 , state$: StateObservable ): 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:

Aplikasi Todo yang Reaktif
Kode sumber untuk aplikasi ini tersedia di Github. Cobalah proyek di Expo atau pindai kode QR di atas di aplikasi Expo.

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.