-
✔ 우아한 테크러닝 3기: React & TypeScript 5회차우아한 테크러닝 3기 2020. 9. 19. 19:03
✌ 9월15일 (화) 우아한 테크러닝 3기 5회차 강의 정리
🚀 5회차 강의 목표
- javascript로 만든 Redux 리뷰
- Redux의 비동기
- Redux 미들웨어 알아보기
💻 Redux 리뷰
- 2회차때 만든 리덕스를 사용하여
store
와reducer
생성 해준다. - 자세한 내용은 위 링크 참조 🙏
index.js
import { createStore } from "./redux"; function reducer(state = { counter: 0 }, action) { switch (action.type) { case "inc": return { ...state, counter: state.counter + 1 }; default: return { ...state }; } } const store = createStore(reducer); store.subscribe(() => { //Object {counter: 1} //Object {counter: 2} console.log(store.getState()); }); store.dispatch({ type: "inc" }); store.dispatch({ type: "inc" });
redux.js
export function createStore(reducer) { let state; const listeners = []; const getState = () => ({ ...state }); const dispatch = (action) => { state = reducer(state, action); listeners.forEach((fn) => fn()); }; const subscribe = (fn) => { listeners.push(fn); }; return { getState, dispatch, subscribe }; }
index.js
에서 이뤄지는 모든 동작 즉, reducer따로, store 만드는것 따로, subscribe 따로, dispatch가 각각 따로 일어나는데 실제로 모든 동작은 동기적으로 작동한다.
그렇기 때문에 순서가 완전히 보장이된다.그래서
reducer
는 순수함수 여야만한다. (원칙)
순수함수란 외부와 아무런dependency
없이 내부적으로 아무런side effect
없이 작동하는 함수이다.멱등성: 인풋이 똑같으면 아웃풋도 무조건 같아야되는 성질
🙄 순수하지 않은 작업은 뭘까??
- 실행될때마다 결과가 일정치 않은 작업들을 순수하지 않는 작업이라한다.
대표적으로 비동기 작업이고 그중애서도 대표적인 것은 API호출이다.
그렇기 때문에 결과 예측이 안된다.
🙄 그래서 순수하지 않는 상황은 어떻게 다룰까??
- 현재 상태에서는 API를 다루는 것은 불가능하다.
index.js
... // api 받는 함수 function api(url, cb) { setTimeout(() => { cb({ type: "응답이야", data: [] }); }, 2000); } ... function reducer(state = { counter: 0 }, action) { switch (action.type) { case "inc": return { ...state, counter: state.counter + 1 }; case "fetch-user": api("/api/v1/users/1", (users) => { return { ...state, ...users }; }); break; default: return { ...state }; } } ... store.dispatch({ type: "fetch-user" })
Redux
는 모든 로직을 동기적으로 처리하기 때문에 값을 기다리지 않는다.(setTimeout
) 그렇기 때문에 reducer가 호출되고api
가 호출되고 반환을 하지만 현재 상태에서는 반환한 상태가 없게 된다.
그렇기 때문에 store의 상태를 바꾸는 것은 불가능하게 된다.
따라서 redux는 비동기 작업을 할 때 미들웨어을 사용한다.
🚀 Redux의 미들웨어
- 아래와 같은 형태들로 만들 수 있다.
const myMiddleware = (store) => (dispatch) => (action) => { dispatch(action); }; // 일반 함수 function yourMiddleware(store) { return function (dispatch) { return function (action) { dispatch(action); }; }; } function ourMiddleware(store, dispatch, action) { dispatch(action); }
myMiddleware
와yourMiddleware
는 문법만 다르고 같은 형태이고ourMiddleware
은 함수가 하나이다.이 세 함수의 공통점은 실행하는 코드가 같다.
다른점은
myMiddleware
,yourMiddleware
와ourMiddleware
의 다른점은 중첩되어 있는 지점이 다르다.인자가 n개인 함수를 인자를 각각 하나씩 쪼갠 함수를 분리하는 기법을 함수형 프로그래밍에서는 커링이라고 한다.
// 커링을 이용한 함수를 이렇게 실행 할 수 있다. myMiddleware(store)(store.dispatch)({ type: "inc" }); // Object {counter: 1} yourMiddleware(store)(store.dispatch)({ type: "inc" }); // Object {counter: 2}
ourMiddleware
호출 할 땐 인자를 한꺼번에 전달한다.
ourMiddleware(store, store.dispatch, { type: "inc" }); // Object {counter: 3}
🤔 커링을 사용해서 미들웨어을 사용하는 이유는 뭘까??
- 아래와 같이 어떤 타입이 들어왔나 로그를 찍고싶을 때가 있다.
console.log('action -> { type: "INC" }'); myMiddleware(store)(store.dispatch)({ type: "inc" });
- 그치만 필요할때 마다 작성해줘야 되기 때문에 굉장히 번거롭다.
- Redux의 공식문서를 참고해보자.
🌊 디스패치 감싸기
function dispatchAndLog(store, action) { console.log('dispatching', action); store.dispatch(action); console.log('next state', store.getState()); } // 로깅함수 호출 dispatchAndLog(store, { type: "inc" }); dispatchAndLog(store, { type: "inc" });
이런식으로 로깅을 함수로 뽑아낼 수 있다.
하지만 위 상항도 하드코딩이 되어있기 때문에 코드를 수정하는 것은 매우 좋지 못한 방법이다.
🌊 디스패치 몽키패칭하기
store.dispatch
를next
라는 변수를 만들어서 함수 자체를 넣어준다. (원본을 넣는다.)- 그리고
store.dispatch
를dispatchAndLog
함수로 바꿔준다. - 실행시간에 필요한 것으로 바꿔주고 필요하지 않아졌을 때 원래대로 복구시켜준다.
let next = store.dispatch; // 바깥쪽 store.dispatch 에서는 로깅 찍히는 작업 바꿔준다. store.dispatch = function dispatchAndLog(action) { console.log('dispatching', action); // 원래의 것으로 dispatch 해준다. 클로저로 가지고 있다. let result = next(action); console.log('next state', store.getState()); return result; };
- 이제 위와 같이 하면
dispatchAndLog
함수를 사용하지 않고dispatch
를 할 수 있게 되었다. - 하지만! 만약
dispatch
에 이런 함수 변환(위 처럼 함수를 바꿔치기 하는 것. 즉, 로깅하는 함수와 또 다른 함수)을 두 개 이상 적용할 때는 어떻게 될까??
🌊 디스패치 두 개 이상 몽키패칭하기
- 로깅 이 외의 오류를 잡을 때를 콘솔에 값을 출력하는 로직을 추가시켜 보자.
- 여기서 중요한 것은 안에 있는 코드가 어떤 코드인지는 중요하지 않다.
중요한 점은 함수가 함수를 리턴하고 있고, 바깥 함수와 안쪽 함수가 분리되어있다는 점이다.patchStoreToAddLogging
와dispatchAndLog
patchStoreToAddCrashReporting
와dispatchAndReportErrors
function patchStoreToAddLogging(store) { let next = store.dispatch; store.dispatch = function dispatchAndLog(action) { console.log('dispatching', action); let result = next(action); console.log('next state', store.getState()); return result; }; } function patchStoreToAddCrashReporting(store) { let next = store.dispatch; store.dispatch = function dispatchAndReportErrors(action) { try { return next(action); } catch (err) { console.error("Caught an exception!", err); Raven.captureException(err, { extra: { action, state: store.getState() } }); throw err; } }; }
📌 여기서 잠깐 커링의 예시
add1
함수는 사용자가 최종 계산에 개입할 여지가 전혀 없다. (10 + 20을 변화시킬 수 없다.)
const add1 = function (a, b) { return a + b; }; const add2 = function (a) { return function (b) { return a + b; }; }; add1(10, 20); add2(10)(20);
add2
함수는 사용자가 개입할 여지가 충분히 존재한다.- 그렇기 때문에 지연효과를 줄 수 있다.
// 사용자 쪽에서 작업을 할 수있다. const addTen = add2(20); // do some.. addTen(20); addTen(120);
- 결국 커링은 사용자한테 인자와 인자 사이에 개입할 수 있는 여지를 열어 주는 것이다.
🌊 몽키패칭 숨기기
- 여기서 본격적인 커링의 모양이 등장한다.
dispatchAndLog
를logger
함수로 감싸고dispatchAndLog
를 리턴한다.
function logger(store) { let next = store.dispatch; // 앞에서: // store.dispatch = function dispatchAndLog(action) { return function dispatchAndLog(action) { console.log('dispatching', action); let result = next(action); console.log('next state', store.getState()); return result; }; }
- 여기서 사용자인 리덕스가 미들웨어로 사용자가 만든 함수를 조작하고 싶은데
add1(10, 20);
이런식으로 만들면 조작할 수 없으니 리덕스가 개입해서 원하는 형태로 재구성할 수 있는 여지를 주는 커링 테크닉을 사용하는 것이다.add2(10)(20);
- 각각의 미들웨어를 적용시키는
applyMiddlewareByMonkeypatching
함수를 아래와 같이 작성할 수 있다. - Redux 안에 실제 몽키패칭을 적용할 수 있게 돕는 헬퍼를 제공할 수 있다.
function applyMiddlewareByMonkeypatching(store, middlewares) { middlewares = middlewares.slice(); middlewares.reverse(); // 각각의 미들웨어로 디스패치 함수를 변환 middlewares.forEach(middleware => store.dispatch = middleware(store) ); } // 여러 미들웨어를 적용 applyMiddlewareByMonkeypatching(store, [logger, crashReporter]);
- 하지만 이건 몽키패칭을 숨기기만하고 아직 사용중이다.
- 몽키패칭을 제거하기 위해서 아래와 같이 사용할 수 있다.
// 바깥 function logger(store) { // 두번째 함수 return function wrapDispatchToAddLogging(next) { // 마지막 함수 return function dispatchAndLog(action) { // 결국 할려는 것 console.log('dispatching', action); let result = next(action); console.log('next state', store.getState()); return result; }; } }
- 미들웨어가
next()
디스패치 함수를store
인스턴스에서 읽어오는 대신 매개변수로 받을 수 있다. - 화살표 함수를 이용해 커링을 간단하게 나타낼 수 있다.
const logger = store => next => action => { console.log('dispatching', action); let result = next(action); console.log('next state', store.getState()); return result; };
- 결국 최종적으로 마지막 안쪽 함수에만 쓰면된다.
등록된 미들웨어들을 순서대로 연결시켜 놓은 구조를 만들 수 있게 리덕스가 할 수 있게 열어놓는 구조를 만들기 위해서 커링 테크닉을 사용한 것이다.
📚 Redux에 미들웨어 추가하기
- 미들웨어를 추가해준다.
- 위 경우에서는
setTimeout
를 사용한 비동기적 함수는 실행되지 않았다.
// index.js const logger = (store) => (next) => (action) => { console.log("logger:", action.type); next(action); }; const monitor = (store) => (next) => (action) => { setTimeout(() => { console.log("monitor:", action.type); next(action); }, 2000); };
- 미들웨어를 받는 부분을 작성해준다.
middlewares
는 미들웨어를 배열로 받는다.
// redux.js export function createStore(reducer, middlewares = []) { ... middlewares = Array.from(middlewares).reverse(); let lastDispatch = store.dispatch; middlewares.forEach((middleware) => { lastDispatch = middleware(store)(lastDispatch); }); return { ...store, dispatch: lastDispatch }; ... }
middlewares.reverse();
를 해주는 이유는middleware(store)(lastDispatch)
형태로 호출되며 반환값은 계속해서 앞에 미들웨어에 전달된다.
그래서reverse
로 뒤집어서 가장 뒤인 안쪽에서 부터 바깥쪽으로dispatch
가 전달된다. 그렇기 때문에 최종 실행되는dispatch
함수는 기존 맨 처음의store
의dispatch
함수가 되게 된다.- 결과적으로 아래와 같이 콘솔이 찍히고, 미들웨어를 순서를 변경해도 그대로 동기적으로 실행된 결과가 나온다.
🚀 5회차 후기
이번 미들웨어 수업은 이해하기가 어려웠다. 그래도 블로깅을 하면서 이해해볼려고 노력했다..😥
공부가 많이 필요한 부분인 거 같다.
바로바로 블로깅을 했어야 했지만 생각보다 시간적 여유가 부족한거 같다.. 그래도 꼭 8회차까지 다써야겠다.'우아한 테크러닝 3기' 카테고리의 다른 글
✔ 우아한 테크러닝 3기: React & TypeScript 7회차 (0) 2020.09.23 ✔ 우아한 테크러닝 3기: React & TypeScript 6회차 (0) 2020.09.20 ✔ 우아한 테크러닝 3기: React & TypeScript 4회차 (0) 2020.09.11 ✔ 우아한 테크러닝 3기: React & TypeScript 3회차 (0) 2020.09.09 ✔ 우아한 테크러닝 3기: React & TypeScript 2회차 (0) 2020.09.07