SlideShare a Scribd company logo
리덕스 도입을 주저하게
만드는 장벽들
에어비앤비 박호준
Feb. 2020
Hi there, we are Propel.
We’re Airbnb’s emerging tech
R&D team.
Team overview
iOS DS FE PM BE EM
오늘의 주제:
에너미 오브
스테이트
형상 관리가 힘든 가장 큰 이유는
바로 데이터의
복잡성
state management
형상 관리 Forms Routing Inputs
Tabs Filters
Menus Navigations Notifications
Error states
Loading states
데이터
과
거
현
재
미
래
에어비앤비
상태 관리의
역사
Alt Redux Apollo
Graphql
(~2017) (2017~2020) (2019~)
MobX 대신 Redux를 선택한
이유
에어비앤비가
MobX
● 반응형 프로그래밍 패러다임 (Reactive)
● 상태 관찰(Observable) 형태의
형상관리
액션 상태 반응
변경 파생
참조: slideshare.net/hyunseoblee7/mobx-mst-state-management-109460634
● @observable
● @computed
● @action
● runInAction
● @observer
MobX참조: slideshare.net/hyunseoblee7/mobx-mst-state-management-109460634
MobX: Serialize/Deserialize
단점
● 서버에서 받아온 자료를
호환 가능한 자료로
변환해야함
● 계층 구조가 깊어질수록
Deserialize 지옥
(Observable Array = hell)
● 에러 컨트롤
MobX
장점
● 단순하고 이해하기 쉬움
● 간편한 비동기 처리
● 클래스와 연결 용이
참조: slideshare.net/hyunseoblee7/mobx-mst-state-management-109460634
Two-way binding
Make it simple!
Flux해버렸지!
Data Down Action Up
Redux란?
● 단방향(Flux) 형상 관리 도구
● 컨텍스트와 유사
액션 ● 무엇을 할지만 정의하고 어떻게 처리하는지는 포함하지
않음
리듀서
● 이전 리덕스 스토어 상태와 액션 값으로 새 상태를 생성하는 순수
함수
function reducer(prevState, action) {
return prevState + action.payload;
}
prevState
newState
비동기 제어
미들웨어 선택 장애
redux-saga
redux-actions
redux-observable
...
pure
middleware vs
redux-saga 최고임!
Reactive 대세임!
react-thunk: 액션
fuction apiAction() {
redux.dispatch({type: LOADING});
Api.get(‘search’).then(response =>
redux.dispatch({type: LOAD, payload: response});
).catch(error=>
redux.dispatch({type: ERROR, payload:error});
);
}
react-thunk: 액션 (중복 호출 방지)
let xhr;
fuction apiAction(params) {
if (xhr) xhr.abort();
redux.dispatch({type: 'START'});
xhr = Api.get(‘search’, params).then(response =>
redux.dispatch({type: 'SUCCESS', payload: response});
).catch(error=>
redux.dispatch({type: 'ERROR', payload:error});
).finally(() => redux.dispatch({type: 'FINISH' });
}
redux-saga: 중복 호출 방지
function* sagaAction(params) {
yield put({type: 'START'});
try {
const response = yield call(Api.get, 'search', params));
yield put({ type: 'SUCCESS', payload: response })
} catch (err) {
yield put({type: 'ERROR', payload:error });
} finally {
yield put({type: 'FINISHED' });
}
}
function* takeLatestSagaAction() {
yield takeLatest('REQUEST_ACTION', sagaAction);
}
redux-observable: 중복 호출 방지
(action$) => action$.ofType('REQUEST_ACTION')
.map(action => action.payload)
.switchMap(payload =>
Observable.merge(
Observable.of({type: 'START'}),
Api.get(‘search’)
.map(response => { type: 'SUCCESS', payload: response })
.catch((error) => Observable.of({type: 'ERROR', payload: error }))
),
);
redux-saga: 연쇄 효과
function* notificationAction() {
yield put({ type: 'SHOW_ERROR'});
}
function* hideAction() {
yield put({ type: 'HIDE_ERROR'});
}
function* hideAfter4s() {
yield debounce(4000, 'HIDE_ERROR', hideAction);
}
function* takeLatestSagaAction() {
yield takeEvery('ERROR', notificationAction);
}
function* hideNotificationAction() {
yield takeLatest('SHOW_ERROR', hideAfter4s);
}
로그인 창 하나 붙이는 데...
redux-form
redux-saga
reducer
action
redux
학습 장벽
미들웨어, 어디까지
살펴보았나요?
미들웨어
function middleware(store) {
return function (nextRunner) {
return function(action) {
// 리듀서 전
const result = nextRunner(action);
// 리듀서 후
return result;
}
}
}
redux-thunk는 무엇인가요?
const thunk = (store) => (nextRunner) => (action) => {
if (typeof action === 'function') {
return action(store.dispatch, store.getState);
}
return nextRunner(action);
};
비동기 제어
react-pack은 무엇인가요?
redux-pack: 액션
function fetchResource() {
return (dispatch) => {
dispatch({ type: 'START'}),
Api.get('/resource').then(
data => dispatch({ type: 'SUCCESS', payload: data }),
error => dispatch({ type: 'ERROR', payload: error }),
).finally(
() => dispatch({ type: 'FINISH' });
);
}
}
function fetchResource() {
return {
type: 'REDUX_PACK',
promise: Api.get('/resource')
};
}
redux-pack: 리듀서
import { handle } from 'redux-pack';
export function packReducer(state, action) {
const { type, payload } = action;
if (action === 'REDUX_PACK') {
return handle(state, action, {
start: prevState => ({
...prevState, loading: true,
}),
success: prevState => ({ ...prevState, payload }),
failure: prevState => ({
...prevState, error: true,
}),
finish: prevState => ({
...prevState, loading: false,
}),
});
}
return state;
}
export function reducer(state, action) {
const { type, payload } = action;
switch(type) {
case: 'START': {
return { ...state, loading: true };
}
case: 'SUCCESS': {
return { ...state, payload };
}
case: 'FAILURE': {
return { ...state, error: true };
}
case: 'FINISH': {
return { ...state, loading: false };
}
}
return state;
}
비동기 제어
미들웨어: 연쇄 작용(effects)
const notificationEffect = store => nextRunner => action => {
const { type, payload, meta = {} } = action;
const { errorMessage, successMessage } = meta;
const result = nextRunner(action);
if (type === 'ERROR' && errorMessage) {
store.dispatch(showMessage(errorMessage, false));
} else if (type === 'SUCCESS' && successMessage) {
store.dispatch(showMessage(successMessage, true));
}
return result;
}
function fetchResource() {
return {
type: 'REDUX_PACK',
promise: Api.get('/resource'),
meta: {
successMessage: '성공적으로 요청을 완료했습니다.',
errorMessage: '서버에 문제가 발생했습니다.',
}
};
}
function showMessage(msg, isSuccess) {
return {
type: 'SHOW_NOTIFICATION',
Payload: msg,
};
}
미들웨어: 연쇄 작용(effects)
const notificationEffect = store => nextRunner => action => {
const { type, payload, meta = {} } = action;
const { errorMessage, successMessage } = meta;
const result = nextRunner(action);
...
if (type === 'SHOW_NOTIFICATION') {
const hide = () => store.dispatch(hideMessage());
setTimeout(hide, 4000);
debounceRunner(hide);
}
return result;
}
다중 비동기 제어
다중 비동기 제어
컨테이너 컴포넌트
예제
import openSocket from 'socket.io-client';
const customAsyncMiddleware = store => {
const io = openSocket();
return nextRunner => action => {
if(action.type === 'CONNECT_POST') {
io.on('message', function(message) {
store.dispatch({ type: 'ADD_CHAT_MESSAGE', payload: message});
});
return nextRunner(action);
} else if (action.type === 'AXIOS_GET') {
axios.get(action.payload.url).then((response) => {
store.dispatch({
type: 'ADD_AXIOS_RESPONSE',
payload: response.data
});
});
return action;
}
return nextRunner(action);
}
}
const chat = (state, action) => {
if (action.type === 'ADD_CHAT_MESSAGE')
return { message: action.payload };
return state;
};
const axios = (state, action) => {
if (action.type === 'ADD_AXIOS_RESPONSE')
return { data: action.payload };
return state;
};
const ChatMessageContainer = connect(
state => state.chat.message,
)(ChatMessage);
const DBDataContainer = connect(
state => state.axios.data,
)(DBData);
리듀서
파이어베이스 연결 예제
const customFirebaseMiddleware = store => {
firebase.initializeApp(config);
const database = firebase.database();
return nextRunner => action => {
if(action.type === 'CONNECT_FIREBASE') {
const postRef = firebase.database().ref('posts');
postRef.on('value', function(snapshot) {
store.dispatch({ type: 'SET_POST', payload: snapshot.val()});
});
return nextRunner(action);
} else if (action.type === 'DISCONNECT_FIREBASE' && database) {
database.off();
return nextRunner(action);
}
return nextRunner(action);
}
}
GraphQL 과 apollo
2020 Plan
GraphQL and Schema
Rest API → GraphQL
3 Calls 1 Call
자동 완성: 데이터 시뮬레이션
swagger GraphiQL
자동 완성: 편집기
Typescript + schema
codegen
형상 관리
const TOGGLE_TODO = gql`
mutation ToggleTodo($id: Int!) {
toggleTodo(id: $id) @client
}
`;
const Todo = ({ id, completed, text }) => (
<Mutation mutation={TOGGLE_TODO} variables={{ id }}>
{toggleTodo => (
<button onClick={toggleTodo}> {text} </button>
)}
</Mutation>
);
연쇄 효과: middleware → apollo-link
const link = new ApolloLink((operation, forward) => {
// request 전
return forward(operation).map((data) => {
// request 후
return data;
});
});
function middleware(store) {
return function (nextRunner) {
return function(action) {
// 리듀서 전
const result = nextRunner(action);
// 리듀서 후
return result;
}
}
}
스토리북과 API Mocking
const apolloManifest = require('../../../apollo-manifest.json');
const id = 112358;
axios({
method: 'post',
url: 'http://localhost:3000/graphql',
headers: { 'Content-Type': 'application/json' },
data: JSON.stringify({
variables,
query: getQueryFromManifest(apolloManifest)
}),
})
.then(({ data }) => {
fs.writeFile(
`/__mocks__/listing.json`,
JSON.stringify(data),
);
});
출력 컴포넌트
출력 컴포넌트
GraphQL이
완벽한 것만은
아닙니다.
● GraphQL 서버 구축 비용
● 학습 장벽
● 새 패러다임의 적응 필요
검색 액션 API요청 connect() 출력
검색 Filter변경 <Query/> 출력API요청
Context
Redux
Apollo
상황에 맞게
잘 쓰자!
도입 편리성
생산성
감사합니다!
Q&A
Thank you!
Redux: Demo!
연쇄 작용
import { SET_FILTER } from '../actions/searchFilterActions';
import { requestTransactionList, resetTransactionList } from '../actions/transactionPackActions';
export default store => nextRunner => action => {
const { type, payload } = action;
const result = nextRunner(action);
if (type === SET_FILTER) {
const { params } = payload || {};
store.dispatch(resetTransactionList());
store.dispatch(requestTransactionList(params));
}
return result;
}
let prevParams;
fuction apiAction(params) {
if (shallowEqual(prevParams,params))
return;
prevParams = params;
redux.dispatch({type: 'START'});
Api.get(‘search’, params).then(response =>
redux.dispatch({type: 'SUCCESS', payload: response});
).catch(error=>
redux.dispatch({type: 'ERROR', payload:error});
).finally(() => redux.dispatch({type: 'FINISH' });
}

More Related Content

What's hot (20)

PDF
(국비지원/실업자교육/재직자교육/스프링교육/마이바티스교육추천)#13.스프링프레임워크 & 마이바티스 (Spring Framework, MyB...
탑크리에듀(구로디지털단지역3번출구 2분거리)
 
PPTX
(Spring Data JPA)게시판 리스트보기_오라클, 스프링부트,페이지나누기
탑크리에듀(구로디지털단지역3번출구 2분거리)
 
PDF
01.실행환경 실습교재(공통기반)
Hankyo
 
PPTX
ECMAScript 6의 새로운 것들!
WooYoung Cho
 
PDF
#27.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_스프링프레임워크 강좌, 재직자환급교육,실업자국비지원...
탑크리에듀(구로디지털단지역3번출구 2분거리)
 
PDF
#33.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_스프링프레임워크 강좌, 재직자환급교육,실업자국비지원...
탑크리에듀(구로디지털단지역3번출구 2분거리)
 
PDF
(스프링 초보자를 위한)스프링 DI관련 어노테이션,자동스캐닝 컴포넌트(Spring Framework Auto-Scanning Component)
탑크리에듀(구로디지털단지역3번출구 2분거리)
 
PPTX
자바스크립트 패턴 3장
Software in Life
 
PDF
[D2 오픈세미나]5.robolectric 안드로이드 테스팅
NAVER D2
 
PPT
헷갈리는 자바스크립트 정리
은숙 이
 
PDF
#32.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_스프링프레임워크 강좌, 재직자환급교육,실업자국비지원...
탑크리에듀(구로디지털단지역3번출구 2분거리)
 
PDF
EcmaScript6(2015) Overview
yongwoo Jeon
 
PDF
02.실행환경 실습교재(데이터처리)
Hankyo
 
PDF
안드로이드 개발자를 위한 스위프트
병한 유
 
PPTX
파이썬 언어 기초
beom kyun choi
 
PDF
ReactJS | 서버와 클라이어트에서 동시에 사용하는
Taegon Kim
 
PDF
06.실행환경 실습교재(easy company,해답)
Hankyo
 
PDF
(#8.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis))스프링/자바교육/IT교육/스프링프레임워크교육/국비지...
탑크리에듀(구로디지털단지역3번출구 2분거리)
 
PPTX
Xe hack
sejin7940
 
PDF
Ji 개발 리뷰 (신림프로그래머)
beom kyun choi
 
(국비지원/실업자교육/재직자교육/스프링교육/마이바티스교육추천)#13.스프링프레임워크 & 마이바티스 (Spring Framework, MyB...
탑크리에듀(구로디지털단지역3번출구 2분거리)
 
(Spring Data JPA)게시판 리스트보기_오라클, 스프링부트,페이지나누기
탑크리에듀(구로디지털단지역3번출구 2분거리)
 
01.실행환경 실습교재(공통기반)
Hankyo
 
ECMAScript 6의 새로운 것들!
WooYoung Cho
 
#27.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_스프링프레임워크 강좌, 재직자환급교육,실업자국비지원...
탑크리에듀(구로디지털단지역3번출구 2분거리)
 
#33.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_스프링프레임워크 강좌, 재직자환급교육,실업자국비지원...
탑크리에듀(구로디지털단지역3번출구 2분거리)
 
(스프링 초보자를 위한)스프링 DI관련 어노테이션,자동스캐닝 컴포넌트(Spring Framework Auto-Scanning Component)
탑크리에듀(구로디지털단지역3번출구 2분거리)
 
자바스크립트 패턴 3장
Software in Life
 
[D2 오픈세미나]5.robolectric 안드로이드 테스팅
NAVER D2
 
헷갈리는 자바스크립트 정리
은숙 이
 
#32.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_스프링프레임워크 강좌, 재직자환급교육,실업자국비지원...
탑크리에듀(구로디지털단지역3번출구 2분거리)
 
EcmaScript6(2015) Overview
yongwoo Jeon
 
02.실행환경 실습교재(데이터처리)
Hankyo
 
안드로이드 개발자를 위한 스위프트
병한 유
 
파이썬 언어 기초
beom kyun choi
 
ReactJS | 서버와 클라이어트에서 동시에 사용하는
Taegon Kim
 
06.실행환경 실습교재(easy company,해답)
Hankyo
 
(#8.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis))스프링/자바교육/IT교육/스프링프레임워크교육/국비지...
탑크리에듀(구로디지털단지역3번출구 2분거리)
 
Xe hack
sejin7940
 
Ji 개발 리뷰 (신림프로그래머)
beom kyun choi
 

Similar to Redux (20)

PDF
React 애플리케이션 아키텍처 - 아무도 알려주지 않아서 혼자서 삽질했다.
병대 손
 
PDF
React, Redux 실전 적용기
은미 김
 
PPTX
React 실무활용 이야기
철민 배
 
PPTX
Kit-Works Team Study_zustand에 대해 알아보자 염겨레_20241206.pptx
Wonjun Hwang
 
PDF
SWR
Wonjun Hwang
 
PDF
불변객체 적용으로 리액트 성능 최적화
Hun Yong Song
 
PPTX
Node.js and react
HyungKuIm
 
PDF
[부스트캠프 Tech Talk]손정현_PUB/SUB를 적용한 Todo앱 만들기(순한맛)
CONNECT FOUNDATION
 
PDF
React Hooks 마법. 그리고 깔끔한 사용기
NAVER SHOPPING
 
PDF
GraphQL overview #2
기동 이
 
PDF
track2 04. MS는 Rx를 왜 만들었을까? feat. RxJS/ 네이버, 김훈민
양 한빛
 
PDF
웹 프론트엔드 개발자의 얕고 넓은 Rx 이야기
Kim Hunmin
 
PPTX
Apollo jay
ssuserc80245
 
PPTX
[112]rest에서 graph ql과 relay로 갈아타기 이정우
NAVER D2
 
PDF
React native 개발 및 javascript 기본
Tj .
 
PPTX
Flux 예제 분석 2
Peter YoungSik Yun
 
PDF
200819 NAVER TECH CONCERT 07_신입 iOS 개발자 개발업무 적응기
NAVER Engineering
 
PDF
이정환_구름에듀_특강.pdf
이정환
 
PPTX
React-Query가필요한가요.pptx
ssuser89c688
 
PDF
Facebook은 React를 왜 만들었을까?
Kim Hunmin
 
React 애플리케이션 아키텍처 - 아무도 알려주지 않아서 혼자서 삽질했다.
병대 손
 
React, Redux 실전 적용기
은미 김
 
React 실무활용 이야기
철민 배
 
Kit-Works Team Study_zustand에 대해 알아보자 염겨레_20241206.pptx
Wonjun Hwang
 
불변객체 적용으로 리액트 성능 최적화
Hun Yong Song
 
Node.js and react
HyungKuIm
 
[부스트캠프 Tech Talk]손정현_PUB/SUB를 적용한 Todo앱 만들기(순한맛)
CONNECT FOUNDATION
 
React Hooks 마법. 그리고 깔끔한 사용기
NAVER SHOPPING
 
GraphQL overview #2
기동 이
 
track2 04. MS는 Rx를 왜 만들었을까? feat. RxJS/ 네이버, 김훈민
양 한빛
 
웹 프론트엔드 개발자의 얕고 넓은 Rx 이야기
Kim Hunmin
 
Apollo jay
ssuserc80245
 
[112]rest에서 graph ql과 relay로 갈아타기 이정우
NAVER D2
 
React native 개발 및 javascript 기본
Tj .
 
Flux 예제 분석 2
Peter YoungSik Yun
 
200819 NAVER TECH CONCERT 07_신입 iOS 개발자 개발업무 적응기
NAVER Engineering
 
이정환_구름에듀_특강.pdf
이정환
 
React-Query가필요한가요.pptx
ssuser89c688
 
Facebook은 React를 왜 만들었을까?
Kim Hunmin
 
Ad

More from NAVER Engineering (20)

PDF
React vac pattern
NAVER Engineering
 
PDF
디자인 시스템에 직방 ZUIX
NAVER Engineering
 
PDF
진화하는 디자인 시스템(걸음마 편)
NAVER Engineering
 
PDF
서비스 운영을 위한 디자인시스템 프로젝트
NAVER Engineering
 
PDF
BPL(Banksalad Product Language) 무야호
NAVER Engineering
 
PDF
이번 생에 디자인 시스템은 처음이라
NAVER Engineering
 
PDF
날고 있는 여러 비행기 넘나 들며 정비하기
NAVER Engineering
 
PDF
쏘카프레임 구축 배경과 과정
NAVER Engineering
 
PDF
플랫폼 디자이너 없이 디자인 시스템을 구축하는 프로덕트 디자이너의 우당탕탕 고통 연대기
NAVER Engineering
 
PDF
200820 NAVER TECH CONCERT 15_Code Review is Horse(코드리뷰는 말이야)(feat.Latte)
NAVER Engineering
 
PDF
200819 NAVER TECH CONCERT 03_화려한 코루틴이 내 앱을 감싸네! 코루틴으로 작성해보는 깔끔한 비동기 코드
NAVER Engineering
 
PDF
200819 NAVER TECH CONCERT 10_맥북에서도 아이맥프로에서 빌드하는 것처럼 빌드 속도 빠르게 하기
NAVER Engineering
 
PDF
200819 NAVER TECH CONCERT 08_성능을 고민하는 슬기로운 개발자 생활
NAVER Engineering
 
PDF
200819 NAVER TECH CONCERT 05_모르면 손해보는 Android 디버깅/분석 꿀팁 대방출
NAVER Engineering
 
PDF
200819 NAVER TECH CONCERT 09_Case.xcodeproj - 좋은 동료로 거듭나기 위한 노하우
NAVER Engineering
 
PDF
200820 NAVER TECH CONCERT 14_야 너두 할 수 있어. 비전공자, COBOL 개발자를 거쳐 네이버에서 FE 개발하게 된...
NAVER Engineering
 
PDF
200820 NAVER TECH CONCERT 13_네이버에서 오픈 소스 개발을 통해 성장하는 방법
NAVER Engineering
 
PDF
200820 NAVER TECH CONCERT 12_상반기 네이버 인턴을 돌아보며
NAVER Engineering
 
PDF
200820 NAVER TECH CONCERT 11_빠르게 성장하는 슈퍼루키로 거듭나기
NAVER Engineering
 
PDF
200819 NAVER TECH CONCERT 06_놓치기 쉬운 안드로이드 UI 디테일 살펴보기
NAVER Engineering
 
React vac pattern
NAVER Engineering
 
디자인 시스템에 직방 ZUIX
NAVER Engineering
 
진화하는 디자인 시스템(걸음마 편)
NAVER Engineering
 
서비스 운영을 위한 디자인시스템 프로젝트
NAVER Engineering
 
BPL(Banksalad Product Language) 무야호
NAVER Engineering
 
이번 생에 디자인 시스템은 처음이라
NAVER Engineering
 
날고 있는 여러 비행기 넘나 들며 정비하기
NAVER Engineering
 
쏘카프레임 구축 배경과 과정
NAVER Engineering
 
플랫폼 디자이너 없이 디자인 시스템을 구축하는 프로덕트 디자이너의 우당탕탕 고통 연대기
NAVER Engineering
 
200820 NAVER TECH CONCERT 15_Code Review is Horse(코드리뷰는 말이야)(feat.Latte)
NAVER Engineering
 
200819 NAVER TECH CONCERT 03_화려한 코루틴이 내 앱을 감싸네! 코루틴으로 작성해보는 깔끔한 비동기 코드
NAVER Engineering
 
200819 NAVER TECH CONCERT 10_맥북에서도 아이맥프로에서 빌드하는 것처럼 빌드 속도 빠르게 하기
NAVER Engineering
 
200819 NAVER TECH CONCERT 08_성능을 고민하는 슬기로운 개발자 생활
NAVER Engineering
 
200819 NAVER TECH CONCERT 05_모르면 손해보는 Android 디버깅/분석 꿀팁 대방출
NAVER Engineering
 
200819 NAVER TECH CONCERT 09_Case.xcodeproj - 좋은 동료로 거듭나기 위한 노하우
NAVER Engineering
 
200820 NAVER TECH CONCERT 14_야 너두 할 수 있어. 비전공자, COBOL 개발자를 거쳐 네이버에서 FE 개발하게 된...
NAVER Engineering
 
200820 NAVER TECH CONCERT 13_네이버에서 오픈 소스 개발을 통해 성장하는 방법
NAVER Engineering
 
200820 NAVER TECH CONCERT 12_상반기 네이버 인턴을 돌아보며
NAVER Engineering
 
200820 NAVER TECH CONCERT 11_빠르게 성장하는 슈퍼루키로 거듭나기
NAVER Engineering
 
200819 NAVER TECH CONCERT 06_놓치기 쉬운 안드로이드 UI 디테일 살펴보기
NAVER Engineering
 
Ad

Redux

  • 1. 리덕스 도입을 주저하게 만드는 장벽들 에어비앤비 박호준 Feb. 2020
  • 2. Hi there, we are Propel. We’re Airbnb’s emerging tech R&D team. Team overview
  • 3. iOS DS FE PM BE EM
  • 5. 형상 관리가 힘든 가장 큰 이유는 바로 데이터의 복잡성 state management
  • 6. 형상 관리 Forms Routing Inputs Tabs Filters Menus Navigations Notifications Error states Loading states 데이터
  • 8. MobX 대신 Redux를 선택한 이유 에어비앤비가
  • 9. MobX ● 반응형 프로그래밍 패러다임 (Reactive) ● 상태 관찰(Observable) 형태의 형상관리 액션 상태 반응 변경 파생 참조: slideshare.net/hyunseoblee7/mobx-mst-state-management-109460634
  • 10. ● @observable ● @computed ● @action ● runInAction ● @observer MobX참조: slideshare.net/hyunseoblee7/mobx-mst-state-management-109460634
  • 12. 단점 ● 서버에서 받아온 자료를 호환 가능한 자료로 변환해야함 ● 계층 구조가 깊어질수록 Deserialize 지옥 (Observable Array = hell) ● 에러 컨트롤 MobX 장점 ● 단순하고 이해하기 쉬움 ● 간편한 비동기 처리 ● 클래스와 연결 용이 참조: slideshare.net/hyunseoblee7/mobx-mst-state-management-109460634
  • 16. Redux란? ● 단방향(Flux) 형상 관리 도구 ● 컨텍스트와 유사
  • 17. 액션 ● 무엇을 할지만 정의하고 어떻게 처리하는지는 포함하지 않음
  • 18. 리듀서 ● 이전 리덕스 스토어 상태와 액션 값으로 새 상태를 생성하는 순수 함수 function reducer(prevState, action) { return prevState + action.payload; } prevState newState
  • 22. react-thunk: 액션 fuction apiAction() { redux.dispatch({type: LOADING}); Api.get(‘search’).then(response => redux.dispatch({type: LOAD, payload: response}); ).catch(error=> redux.dispatch({type: ERROR, payload:error}); ); }
  • 23. react-thunk: 액션 (중복 호출 방지) let xhr; fuction apiAction(params) { if (xhr) xhr.abort(); redux.dispatch({type: 'START'}); xhr = Api.get(‘search’, params).then(response => redux.dispatch({type: 'SUCCESS', payload: response}); ).catch(error=> redux.dispatch({type: 'ERROR', payload:error}); ).finally(() => redux.dispatch({type: 'FINISH' }); }
  • 24. redux-saga: 중복 호출 방지 function* sagaAction(params) { yield put({type: 'START'}); try { const response = yield call(Api.get, 'search', params)); yield put({ type: 'SUCCESS', payload: response }) } catch (err) { yield put({type: 'ERROR', payload:error }); } finally { yield put({type: 'FINISHED' }); } } function* takeLatestSagaAction() { yield takeLatest('REQUEST_ACTION', sagaAction); }
  • 25. redux-observable: 중복 호출 방지 (action$) => action$.ofType('REQUEST_ACTION') .map(action => action.payload) .switchMap(payload => Observable.merge( Observable.of({type: 'START'}), Api.get(‘search’) .map(response => { type: 'SUCCESS', payload: response }) .catch((error) => Observable.of({type: 'ERROR', payload: error })) ), );
  • 26. redux-saga: 연쇄 효과 function* notificationAction() { yield put({ type: 'SHOW_ERROR'}); } function* hideAction() { yield put({ type: 'HIDE_ERROR'}); } function* hideAfter4s() { yield debounce(4000, 'HIDE_ERROR', hideAction); } function* takeLatestSagaAction() { yield takeEvery('ERROR', notificationAction); } function* hideNotificationAction() { yield takeLatest('SHOW_ERROR', hideAfter4s); }
  • 27. 로그인 창 하나 붙이는 데... redux-form redux-saga reducer action redux
  • 30. 미들웨어 function middleware(store) { return function (nextRunner) { return function(action) { // 리듀서 전 const result = nextRunner(action); // 리듀서 후 return result; } } }
  • 31. redux-thunk는 무엇인가요? const thunk = (store) => (nextRunner) => (action) => { if (typeof action === 'function') { return action(store.dispatch, store.getState); } return nextRunner(action); };
  • 34. redux-pack: 액션 function fetchResource() { return (dispatch) => { dispatch({ type: 'START'}), Api.get('/resource').then( data => dispatch({ type: 'SUCCESS', payload: data }), error => dispatch({ type: 'ERROR', payload: error }), ).finally( () => dispatch({ type: 'FINISH' }); ); } } function fetchResource() { return { type: 'REDUX_PACK', promise: Api.get('/resource') }; }
  • 35. redux-pack: 리듀서 import { handle } from 'redux-pack'; export function packReducer(state, action) { const { type, payload } = action; if (action === 'REDUX_PACK') { return handle(state, action, { start: prevState => ({ ...prevState, loading: true, }), success: prevState => ({ ...prevState, payload }), failure: prevState => ({ ...prevState, error: true, }), finish: prevState => ({ ...prevState, loading: false, }), }); } return state; } export function reducer(state, action) { const { type, payload } = action; switch(type) { case: 'START': { return { ...state, loading: true }; } case: 'SUCCESS': { return { ...state, payload }; } case: 'FAILURE': { return { ...state, error: true }; } case: 'FINISH': { return { ...state, loading: false }; } } return state; }
  • 37. 미들웨어: 연쇄 작용(effects) const notificationEffect = store => nextRunner => action => { const { type, payload, meta = {} } = action; const { errorMessage, successMessage } = meta; const result = nextRunner(action); if (type === 'ERROR' && errorMessage) { store.dispatch(showMessage(errorMessage, false)); } else if (type === 'SUCCESS' && successMessage) { store.dispatch(showMessage(successMessage, true)); } return result; } function fetchResource() { return { type: 'REDUX_PACK', promise: Api.get('/resource'), meta: { successMessage: '성공적으로 요청을 완료했습니다.', errorMessage: '서버에 문제가 발생했습니다.', } }; } function showMessage(msg, isSuccess) { return { type: 'SHOW_NOTIFICATION', Payload: msg, }; }
  • 38. 미들웨어: 연쇄 작용(effects) const notificationEffect = store => nextRunner => action => { const { type, payload, meta = {} } = action; const { errorMessage, successMessage } = meta; const result = nextRunner(action); ... if (type === 'SHOW_NOTIFICATION') { const hide = () => store.dispatch(hideMessage()); setTimeout(hide, 4000); debounceRunner(hide); } return result; }
  • 41. 컨테이너 컴포넌트 예제 import openSocket from 'socket.io-client'; const customAsyncMiddleware = store => { const io = openSocket(); return nextRunner => action => { if(action.type === 'CONNECT_POST') { io.on('message', function(message) { store.dispatch({ type: 'ADD_CHAT_MESSAGE', payload: message}); }); return nextRunner(action); } else if (action.type === 'AXIOS_GET') { axios.get(action.payload.url).then((response) => { store.dispatch({ type: 'ADD_AXIOS_RESPONSE', payload: response.data }); }); return action; } return nextRunner(action); } } const chat = (state, action) => { if (action.type === 'ADD_CHAT_MESSAGE') return { message: action.payload }; return state; }; const axios = (state, action) => { if (action.type === 'ADD_AXIOS_RESPONSE') return { data: action.payload }; return state; }; const ChatMessageContainer = connect( state => state.chat.message, )(ChatMessage); const DBDataContainer = connect( state => state.axios.data, )(DBData); 리듀서
  • 42. 파이어베이스 연결 예제 const customFirebaseMiddleware = store => { firebase.initializeApp(config); const database = firebase.database(); return nextRunner => action => { if(action.type === 'CONNECT_FIREBASE') { const postRef = firebase.database().ref('posts'); postRef.on('value', function(snapshot) { store.dispatch({ type: 'SET_POST', payload: snapshot.val()}); }); return nextRunner(action); } else if (action.type === 'DISCONNECT_FIREBASE' && database) { database.off(); return nextRunner(action); } return nextRunner(action); } }
  • 45. Rest API → GraphQL 3 Calls 1 Call
  • 46. 자동 완성: 데이터 시뮬레이션 swagger GraphiQL
  • 48. 형상 관리 const TOGGLE_TODO = gql` mutation ToggleTodo($id: Int!) { toggleTodo(id: $id) @client } `; const Todo = ({ id, completed, text }) => ( <Mutation mutation={TOGGLE_TODO} variables={{ id }}> {toggleTodo => ( <button onClick={toggleTodo}> {text} </button> )} </Mutation> );
  • 49. 연쇄 효과: middleware → apollo-link const link = new ApolloLink((operation, forward) => { // request 전 return forward(operation).map((data) => { // request 후 return data; }); }); function middleware(store) { return function (nextRunner) { return function(action) { // 리듀서 전 const result = nextRunner(action); // 리듀서 후 return result; } } }
  • 50. 스토리북과 API Mocking const apolloManifest = require('../../../apollo-manifest.json'); const id = 112358; axios({ method: 'post', url: 'http://localhost:3000/graphql', headers: { 'Content-Type': 'application/json' }, data: JSON.stringify({ variables, query: getQueryFromManifest(apolloManifest) }), }) .then(({ data }) => { fs.writeFile( `/__mocks__/listing.json`, JSON.stringify(data), ); });
  • 51. 출력 컴포넌트 출력 컴포넌트 GraphQL이 완벽한 것만은 아닙니다. ● GraphQL 서버 구축 비용 ● 학습 장벽 ● 새 패러다임의 적응 필요 검색 액션 API요청 connect() 출력 검색 Filter변경 <Query/> 출력API요청
  • 54. Q&A
  • 57. 연쇄 작용 import { SET_FILTER } from '../actions/searchFilterActions'; import { requestTransactionList, resetTransactionList } from '../actions/transactionPackActions'; export default store => nextRunner => action => { const { type, payload } = action; const result = nextRunner(action); if (type === SET_FILTER) { const { params } = payload || {}; store.dispatch(resetTransactionList()); store.dispatch(requestTransactionList(params)); } return result; }
  • 58. let prevParams; fuction apiAction(params) { if (shallowEqual(prevParams,params)) return; prevParams = params; redux.dispatch({type: 'START'}); Api.get(‘search’, params).then(response => redux.dispatch({type: 'SUCCESS', payload: response}); ).catch(error=> redux.dispatch({type: 'ERROR', payload:error}); ).finally(() => redux.dispatch({type: 'FINISH' }); }