React Native에서 Redux, RxJS 및 Redux-Observable을 사용하여 반응형 앱 빌드
게시 됨: 2022-03-11풍부하고 강력한 웹 및 모바일 앱의 성장하는 생태계에서 현재 사용자, 로드된 항목 목록, 로드 상태, 오류 등과 같이 관리해야 할 상태가 점점 더 많아지고 있습니다. Redux는 상태를 전역 객체에 유지함으로써 이 문제에 대한 한 가지 솔루션입니다.
Redux의 한계 중 하나는 기본적으로 비동기 동작을 지원하지 않는다는 것입니다. 이에 대한 한 가지 솔루션은 JavaScript에서 반응형 프로그래밍을 위한 강력한 라이브러리인 RxJS를 기반으로 하는 redux-observable
입니다. RxJS는 Microsoft에서 시작된 리액티브 프로그래밍용 API인 ReactiveX의 구현입니다. ReactiveX는 반응 패러다임, 함수형 프로그래밍, 관찰자 패턴 및 반복자 패턴의 가장 강력한 기능 중 일부를 결합합니다.
이 튜토리얼에서는 Redux와 React에서의 사용법에 대해 배울 것입니다. 또한 RxJS를 사용한 반응형 프로그래밍과 이것이 어떻게 지루하고 복잡한 비동기 작업을 매우 간단하게 만들 수 있는지 살펴보겠습니다.
마지막으로 RxJS를 활용하여 비동기 작업을 수행하는 라이브러리인 redux-observable을 학습한 다음 Redux 및 redux-observable을 사용하여 React Native에서 애플리케이션을 빌드합니다.
리덕스
GitHub에 설명되어 있듯이 Redux는 "JavaScript 앱을 위한 예측 가능한 상태 컨테이너"입니다. JavaScript 앱에 전역 상태를 제공하여 상태 및 작업을 React 구성 요소에서 멀리 유지합니다.
Redux가 없는 일반적인 React 애플리케이션에서는 속성 또는 props
를 통해 루트 노드에서 자식으로 데이터를 전달해야 합니다. 이 데이터 흐름은 소규모 애플리케이션에서 관리할 수 있지만 애플리케이션이 성장함에 따라 매우 복잡해질 수 있습니다. Redux를 사용하면 구성 요소를 서로 독립적으로 사용할 수 있으므로 단일 정보 소스로 사용할 수 있습니다.
Redux는 React 구성 요소가 Redux에서 데이터를 읽고 Redux 상태를 업데이트하기 위한 작업을 디스패치하는 바인딩을 제공하는 react-redux
를 사용하여 React에서 사용할 수 있습니다.
Redux는 세 가지 간단한 원칙으로 설명할 수 있습니다.
1. 진실의 단일 소스
전체 애플리케이션의 상태는 단일 개체에 저장됩니다. Redux의 이 객체는 상점에서 보유합니다. 모든 Redux 앱에는 단일 저장소가 있어야 합니다.
» console.log(store.getState()) « { user: {...}, todos: {...} }
React 구성 요소의 Redux에서 데이터를 읽으려면 react-redux
의 connect
기능을 사용합니다. connect
는 4개의 인수를 취하며 모두 선택 사항입니다. 지금은 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)
위의 예에서 mapStateToProps
는 전역 Redux 상태를 첫 번째 인수로 수신하고 상위 구성 요소에 의해 <UserTile />
에 전달된 props와 병합될 개체를 반환합니다.
2. 상태가 읽기 전용입니다.
Redux 상태는 React 구성 요소에 대해 읽기 전용이며 상태를 변경하는 유일한 방법은 action 을 내보내는 것입니다. 액션은 상태를 변경하려는 의도를 나타내는 일반 객체입니다. 모든 작업 개체에는 type
필드가 있어야 하고 값은 문자열이어야 합니다. 그 외에 작업의 내용은 전적으로 사용자에게 달려 있지만 대부분의 앱은 플럭스 표준 작업 형식을 따르므로 작업 구조를 4개의 키로 제한합니다.
-
type
작업에 대한 임의의 문자열 식별자입니다. 모든 작업에는 고유한 작업이 있어야 합니다. -
payload
모든 작업에 대한 선택적 데이터입니다. 언제든지 가능하며 작업에 대한 정보를 포함합니다. -
error
조치가 오류를 나타내는 경우 true로 설정된 선택적 부울 속성입니다. 이것은 거부된Promise. string
작업의Promise. string
식별자입니다. 모든 작업에는 고유한 작업이 있어야 합니다. 관례에 따라error
가true
payload
는 오류 객체여야 합니다. -
meta
메타는 모든 유형의 값이 될 수 있습니다. 페이로드의 일부가 아닌 추가 정보를 위한 것입니다.
다음은 작업의 두 가지 예입니다.
store.dispatch({ type: 'GET_USER', payload: '21', }); store.dispatch({ type: 'GET_USER_SUCCESS', payload: { user: { id: '21', name: 'Foo' } } });
3. 순수 함수로 상태 변경
전역 Redux 상태는 리듀서라고 하는 순수 함수를 사용하여 변경됩니다. 리듀서는 이전 상태와 동작을 취하고 다음 상태를 반환합니다. 감속기는 기존 상태를 변경하는 대신 새 상태 개체를 만듭니다. 앱 크기에 따라 Redux 스토어에는 단일 리듀서 또는 여러 리듀서가 있을 수 있습니다.
/* 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)
상태에서 읽는 것과 유사하게 connect
기능을 사용하여 작업을 전달할 수 있습니다.
/* 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는 관찰자가 구독 할 수 있는 데이터 스트림인 관찰 가능한 개념을 가지고 있으며, 이 관찰자는 시간이 지남에 따라 데이터를 전달받습니다.
옵저버블의 옵저버는 next
, error
, complete
의 세 가지 기능을 가진 객체입니다. 이러한 기능은 모두 선택 사항입니다.
observable.subscribe({ next: value => console.log(`Value is ${value}`), error: err => console.log(err), complete: () => console.log(`Completed`), })
.subscribe
함수는 객체 대신 세 가지 함수를 가질 수도 있습니다.
observable.subscribe( value => console.log(`Value is ${value}`), err => console.log(err), () => console.log(`Completed`) )
Observable의 객체를 생성하여 새로운 observable
을 생성할 수 있으며, Observer라고도 불리는 구독자를 수신하는 함수를 전달합니다. 구독자에는 세 가지 메서드가 있습니다. next
, error
및 complete
. 구독자는 필요한 만큼 값을 사용하여 next를 호출할 수 있으며 결국 complete
되거나 error
합니다. complete
또는 error
를 호출한 후 옵저버블은 스트림 아래로 값을 푸시하지 않습니다.
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) )
위의 예는 1000밀리초 후에 Value is hi
를 인쇄합니다.
매번 수동으로 관찰 가능 항목을 만드는 것은 장황하고 지루할 수 있습니다. 따라서 RxJS에는 Observable을 생성하는 많은 기능이 있습니다. 가장 일반적으로 사용되는 것들 중 일부는 of
, from
및 ajax
입니다.
의
of
는 일련의 값을 가져와 스트림으로 변환합니다.
import { of } from 'rxjs' of(1, 2, 3, 'Hello', 'World').subscribe(value => console.log(value)) // 1 2 3 Hello World
~에서
from
거의 모든 것을 값의 스트림으로 변환합니다.
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
는 문자열 URL을 사용하거나 HTTP 요청을 만드는 옵저버블을 생성합니다. ajax
에는 ajax.getJSON
함수가 있어 ajax()
가 반환하는 다른 속성 없이 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) // {...}
Observable을 만드는 더 많은 방법이 있습니다(여기에서 전체 목록을 볼 수 있습니다).
연산자
연산자는 필요한 거의 모든 것에 대한 연산자가 있는 RxJS의 진정한 강자입니다. RxJS 6부터 연산자는 옵저버블 객체의 메소드가 아니라 .pipe
메소드를 사용하여 옵저버블에 적용된 순수 함수입니다.
지도
map
은 단일 인수 함수를 사용하고 스트림의 각 요소에 투영을 적용합니다.
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
필터
filter
는 단일 인수를 취하고 주어진 함수에 대해 false를 반환하는 값을 스트림에서 제거합니다.
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
플랫맵
flatMap
연산자는 Steam의 모든 항목을 다른 스트림으로 매핑하고 이러한 스트림의 모든 값을 평면화하는 함수를 사용합니다.
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 } ]
병합
merge
는 두 스트림의 항목을 도착한 순서대로 병합합니다.
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
운영자의 전체 목록은 여기에서 확인할 수 있습니다.
Redux 관찰 가능
기본적으로 Redux의 모든 작업은 동기식입니다. Redux-observable은 관찰 가능한 스트림을 사용하여 비동기 작업을 수행한 다음 해당 비동기 작업의 결과로 Redux에서 다른 작업을 전달하는 Redux용 미들웨어입니다.
Redux-observable은 Epic 개념을 기반으로 합니다. 에픽은 일련의 행동을 취하고 선택적으로 상태의 흐름을 취하고 행동의 흐름을 반환하는 함수입니다.
함수(action$: 관찰 가능
관례에 따라 스트림(_aka _observable
)인 모든 변수는 $
로 끝납니다. redux-observable을 사용하려면 먼저 스토어에 미들웨어로 추가해야 합니다. 에픽은 관찰 가능한 스트림이고 이 스팀에서 나오는 모든 작업은 스트림으로 다시 파이프되기 때문에 동일한 작업을 반환하면 무한 루프가 발생합니다.
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' }) )
이 리액티브 아키텍처를 모든 파이프의 출력이 자체를 포함한 모든 파이프와 Redux의 리듀서로 피드백되는 파이프 시스템으로 생각하십시오. 무엇이 들어가고 무엇이 막히는지를 결정하는 것은 이 파이프 위에 있는 필터입니다.
탁구 서사시가 어떻게 작동하는지 봅시다. 핑을 받아 서버로 보내고 요청이 완료된 후 퐁을 앱으로 다시 보냅니다.
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);
_중요: Epic은 RxJS의 다른 관찰 가능한 스트림과 같습니다. 완료 또는 오류 상태가 될 수 있습니다. 이 상태가 지나면 에픽과 앱이 작동을 멈춥니다. 따라서 스팀의 모든 잠재적 오류를 잡아야 합니다. 이를 위해 __catchError__
연산자를 사용할 수 있습니다. 추가 정보: redux-observable의 오류 처리 .
반응형 Todo 앱
일부 UI가 추가되면 (최소) 데모 앱은 다음과 같이 보입니다.
React, Redux 및 RxJS 튜토리얼 요약
반응형 앱이 무엇인지 배웠습니다. 또한 Redux, RxJS 및 redux-observable에 대해 배웠고 Expo에서 React Native를 사용하여 반응형 Todo 앱도 만들었습니다. React 및 React Native 개발자의 경우 현재 추세는 몇 가지 매우 강력한 상태 관리 옵션을 제공합니다.
다시 한 번, 이 앱의 소스 코드는 GitHub에 있습니다. 아래 댓글에서 반응형 앱의 상태 관리에 대한 생각을 자유롭게 공유하세요.