React Native'de Redux, RxJS ve Redux-Observable ile Reaktif Uygulamalar Oluşturma

Yayınlanan: 2022-03-11

Büyüyen zengin ve güçlü web ve mobil uygulamalar ekosisteminde, mevcut kullanıcı, yüklenen öğelerin listesi, yükleme durumu, hatalar ve çok daha fazlası gibi yönetilmesi gereken daha fazla durum var. Redux, durumu küresel bir nesnede tutarak bu soruna bir çözümdür.

Redux'un sınırlamalarından biri, kutunun dışında asenkron davranışı desteklememesidir. Bunun için bir çözüm, JavaScript'te reaktif programlama için güçlü bir kitaplık olan RxJS'ye dayanan redux-observable . RxJS, Microsoft'tan gelen reaktif programlama için bir API olan ReactiveX'in bir uygulamasıdır. ReactiveX, reaktif paradigmanın en güçlü özelliklerinden bazılarını, işlevsel programlamayı, gözlemci kalıbını ve yineleyici kalıbı birleştirir.

Bu eğitimde, Redux ve React ile kullanımını öğreneceğiz. Ayrıca RxJS kullanarak reaktif programlamayı ve bunun sıkıcı ve karmaşık asenkron çalışmayı nasıl çok basit hale getirebileceğini keşfedeceğiz.

Son olarak, asenkron çalışma yapmak için RxJS'den yararlanan bir kitaplık olan redux-observable'ı öğreneceğiz ve ardından Redux ve redux-observable kullanarak React Native'de bir uygulama oluşturacağız.

redux

GitHub'da kendisini tanımladığı gibi Redux, “JavaScript uygulamaları için öngörülebilir bir durum kapsayıcıdır”. JavaScript uygulamalarınıza genel bir durum sağlayarak durumu ve eylemleri React bileşenlerinden uzak tutar.

Redux içermeyen tipik bir React uygulamasında, kök düğümden çocuklara özellikler veya props yoluyla veri iletmemiz gerekir. Bu veri akışı, küçük uygulamalar için yönetilebilir ancak uygulamanız büyüdükçe gerçekten karmaşık hale gelebilir. Redux, birbirinden bağımsız bileşenlere sahip olmamızı sağlar, böylece onu tek bir hakikat kaynağı olarak kullanabiliriz.

Redux, React bileşenlerinin Redux'tan veri okuması ve Redux durumunu güncellemek için eylemleri göndermesi için bağlamalar sağlayan react-redux kullanılarak React'te kullanılabilir.

redux

Redux üç basit ilke olarak tanımlanabilir:

1. Tek Gerçeğin Kaynağı

Tüm uygulamanızın durumu tek bir nesnede saklanır. Redux'daki bu nesne bir mağaza tarafından tutuluyor. Herhangi bir Redux uygulamasında tek bir mağaza olmalıdır.

 » console.log(store.getState()) « { user: {...}, todos: {...} }

React bileşeninizde Redux'tan veri okumak için, react-redux connect işlevini kullanırız. connect , tümü isteğe bağlı olan dört bağımsız değişken alır. Şimdilik, mapStateToProps adlı ilkine odaklanacağız.

 /* 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)

Yukarıdaki örnekte, mapStateToProps ilk argümanı olarak global Redux durumunu alır ve üst bileşeni tarafından <UserTile /> iletilen aksesuarlarla birleştirilecek bir nesne döndürür.

2. Durum Salt Okunur

Redux durumu, React bileşenleri için salt okunurdur ve durumu değiştirmenin tek yolu, bir eylem göndermektir. Eylem, durumu değiştirme niyetini temsil eden düz bir nesnedir. Her eylem nesnesinin bir type alanı olmalı ve değer bir dize olmalıdır. Bunun dışında, eylemin içeriği tamamen size bağlıdır, ancak çoğu uygulama, bir eylemin yapısını yalnızca dört anahtarla sınırlayan, akış standardı eylem biçimini izler:

  1. Bir eylem için herhangi bir dize tanımlayıcısı type . Her eylemin benzersiz bir eylemi olmalıdır.
  2. payload Herhangi bir eylem için isteğe bağlı veriler. Herhangi bir zamanda olabilir ve eylem hakkında bilgi içerir.
  3. error Eylem bir hatayı temsil ediyorsa, isteğe bağlı herhangi bir boole özelliği true olarak ayarlanır. Bu, reddedilen bir Promise. string bir eylem için Promise. string tanımlayıcısı. Her eylemin benzersiz bir eylemi olmalıdır. Kural olarak, error true olduğunda, payload bir hata nesnesi olmalıdır.
  4. meta Meta herhangi bir değer türü olabilir. Yükün bir parçası olmayan herhangi bir ekstra bilgi için tasarlanmıştır.

İşte iki eylem örneği:

 store.dispatch({ type: 'GET_USER', payload: '21', }); store.dispatch({ type: 'GET_USER_SUCCESS', payload: { user: { id: '21', name: 'Foo' } } });

3. Durum Saf Fonksiyonlarla Değiştirilir

Global Redux durumu, redüktör adı verilen saf işlevler kullanılarak değiştirilir. Bir indirgeyici önceki durumu ve eylemi alır ve bir sonraki durumu döndürür. Redüktör, mevcut olanı mutasyona uğratmak yerine yeni bir durum nesnesi oluşturur. Uygulama boyutuna bağlı olarak, bir Redux mağazasında tek bir redüktör veya birden fazla redüktör olabilir.

 /* 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)

Durumdan okumaya benzer şekilde, eylemleri göndermek için bir connect işlevi kullanabiliriz.

 /* 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

Reaktif Programlama

Reaktif programlama, " akışlar " içindeki veri akışı ve yayılması ve değişiklikleri ile ilgilenen bildirimsel bir programlama paradigmasıdır. JavaScript'te reaktif programlama için bir kitaplık olan RxJS, bir gözlemcinin abone olabileceği veri akışları olan gözlemlenebilirler kavramına sahiptir ve bu gözlemciye zaman içinde veri teslim edilir.

Bir gözlemlenebilirin gözlemcisi, üç işlevi olan bir nesnedir: next , error ve complete . Bu işlevlerin tümü isteğe bağlıdır.

 observable.subscribe({ next: value => console.log(`Value is ${value}`), error: err => console.log(err), complete: () => console.log(`Completed`), })

.subscribe işlevi ayrıca bir nesne yerine üç işleve sahip olabilir.

 observable.subscribe( value => console.log(`Value is ${value}`), err => console.log(err), () => console.log(`Completed`) )

Bir aboneyi, yani gözlemciyi alan bir fonksiyondan geçerek, observable bir nesne oluşturarak yeni bir gözlemlenebilir oluşturabiliriz. Abonenin üç yöntemi vardır: next , error ve complete . Abone, next'i gerektiği kadar bir değerle arayabilir ve sonunda complete veya error . complete veya error çağrıldıktan sonra, gözlemlenebilir, akışta herhangi bir değeri itmeyecektir.

 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) )

Yukarıdaki örnek 1000 milisaniye sonra Value is hi yazdıracaktır.

Her seferinde manuel olarak bir gözlemlenebilir oluşturmak, ayrıntılı ve sıkıcı hale gelebilir. Bu nedenle, RxJS'nin bir gözlemlenebilir oluşturmak için birçok işlevi vardır. En sık kullanılanlardan bazıları of , from ve ajax .

ile ilgili

of bir dizi değer alır ve onu bir akışa dönüştürür:

 import { of } from 'rxjs' of(1, 2, 3, 'Hello', 'World').subscribe(value => console.log(value)) // 1 2 3 Hello World

itibaren

from hemen hemen her şeyi bir değer akışına dönüştürür:

 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 bir dize URL'si alır veya bir HTTP isteği yapan bir gözlemlenebilir oluşturur. ajax , ajax() tarafından döndürülen başka hiçbir özellik olmadan yalnızca AJAX çağrısından iç içe yanıt nesnesini döndüren ajax.getJSON işlevine sahiptir:

 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) // {...}

Bir gözlemlenebilir yapmanın daha birçok yolu vardır (tam listeyi burada görebilirsiniz).

operatörler

Operatörler, ihtiyacınız olan hemen hemen her şey için bir operatöre sahip olan RxJS'nin gerçek bir güç merkezidir. RxJS 6'dan beri, operatörler gözlemlenebilir nesne üzerindeki yöntemler değil, bir .pipe yöntemi kullanılarak gözlemlenebilir nesneye uygulanan saf işlevlerdir.

harita

map tek bir bağımsız değişken işlevi alır ve akıştaki her öğeye bir izdüşüm uygular:

 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 

Harita

filtre

filter tek bir argüman alır ve verilen işlev için false döndüren değerleri akıştan kaldırır:

 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 

filtre

düz harita

flatMap operatörü, buhardaki her öğeyi başka bir akışla eşleyen ve bu akışların tüm değerlerini düzleştiren bir işlev alır:

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

Düz harita

birleştirmek

merge , iki akıştaki öğeleri geldikleri sırayla birleştirir:

 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 

Birleştirmek

Operatörlerin tam listesi burada mevcuttur.

Redux-Gözlenebilir

Redux-Gözlenebilir

Tasarım gereği, Redux'daki tüm eylemler eşzamanlıdır. Redux-observable, asenkron çalışmayı gerçekleştirmek için gözlemlenebilir akışları kullanan ve ardından bu asenkron çalışmanın sonucuyla Redux'ta başka bir eylem gönderen Redux için bir ara yazılımdır.

Redux-gözlenebilir, Epics fikrine dayanır. Bir epik, bir eylem akışı ve isteğe bağlı olarak bir durum akışı alan ve bir eylem akışı döndüren bir işlevdir.

işlev (action$: Gözlenebilir , durum$: DurumGözlenebilir ): Gözlenebilir ;

Kural olarak, bir akış (_aka _observable ) olan her değişken bir $ ile biter. Redux-observable'ı kullanmadan önce, onu mağazamıza bir ara katman yazılımı olarak eklemeliyiz. Destanlar gözlemlenebilir akışlar olduğundan ve bu buhardan çıkan her hareket akışa geri gönderildiğinden, aynı işlemin geri döndürülmesi sonsuz bir döngü ile sonuçlanacaktır.

 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' }) )

Bu reaktif mimariyi, her borunun çıktısının kendisi de dahil olmak üzere her boruya ve ayrıca Redux'un redüktörlerine geri beslediği bir boru sistemi olarak düşünün. Neyin içeri girip neyin engelleneceğine karar veren bu boruların üzerindeki filtrelerdir.

Bakalım bir Ping-Pong destanı nasıl işleyecek. Bir ping alır, sunucuya gönderir ve istek tamamlandıktan sonra uygulamaya bir pong gönderir.

 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);

_Önemli: Destanlar, RxJS'deki diğer gözlemlenebilir akışlar gibidir. Tam veya hata durumunda olabilirler. Bu durumdan sonra, epik ve uygulamanız çalışmayı durduracaktır. Bu nedenle, buhardaki her olası hatayı yakalamalısınız. Bunun için __catchError__ operatörünü kullanabilirsiniz. Daha fazla bilgi: Redux-observable'da Hata İşleme .

Reaktif Yapılacaklar Uygulaması

Bazı kullanıcı arayüzü eklendiğinde, (minimum) bir demo uygulaması şuna benzer:

Reaktif Yapılacaklar Uygulaması
Bu uygulamanın kaynak kodu Github'da mevcuttur. Projeyi Expo'da deneyin veya yukarıdaki QR kodunu Expo uygulamasında tarayın.

Bir React, Redux ve RxJS Eğitimi Özeti

Reaktif uygulamaların ne olduğunu öğrendik. Ayrıca Redux, RxJS ve redux-observable hakkında bilgi edindik ve hatta Expo'da React Native ile reaktif bir Todo uygulaması oluşturduk. React ve React Native geliştiricileri için mevcut trendler bazı çok güçlü durum yönetimi seçenekleri sunuyor.

Bir kez daha, bu uygulamanın kaynak kodu GitHub'da. Aşağıdaki yorumlarda reaktif uygulamalar için durum yönetimi hakkındaki düşüncelerinizi paylaşmaktan çekinmeyin.