본문 바로가기
프로젝트 & 강의

react(2)

by Chars4785 2020. 11. 20.

middleWare

Redux ( 공식문서 )

ko.redux.js.org/advanced/middleware

 

미들웨어 | Redux

심화 강좌 > 미들웨어: How middleware enable adding additional capabilities to the Redux store

ko.redux.js.org

reducer가 순수함수 이어야 하는 이유는 reducer 자체가 동기 함수이기 때문에 순수 함수여야 한다.

순수 함수: 외부와 아무런 dependency 없이, 그리고 side effect 없이 동작하는 함수, 인풋이 똑같으면 아웃풋이 동일하다.

순수 하지 않는 함수: 실행 될때 마다 결과가 일정하지 않은 작업 ( 비동기 )

const api= (url, db) =>{
  setTimeout(()=>{
    db({ type: '응답', data: [] })
  },2000)
}
const reduecer = (state = { count: 0 }, action) => {
  switch (action.type) {
    case "inc":
      return {
        ...state,
        count: state.count + 1
      };
    case 'fetch-user':
      api('user/v1/1',( data )=>{
        return { ...state, ...data }
      })
    break;
    default:
      return { ...state };
  }
};

const store = createStore(reduecer);

store.subScribe(() => {
  console.log(store.getStore());
});

store.dispatch()

이렇게 되면 state 는 클로저 영역에 잡히게 되면서 2초동안 변경 될수도 있다. 

그래서 redux는 이문제를 미들웨어로 통해서 해결한다.

 

미들웨어란?

- 그림

미들웨어로 다가가기!!

 Loging

함수 감싸기

let action = addTodo('Use Redux')

console.log('dispatching', action)
store.dispatch(action)
console.log('next state', store.getState())

console.log('dispatching', action)
store.dispatch(action)
console.log('next state', store.getState())

- 이쁘지 않아서 함수 감싸기 많이 한다. ( 많이 이용한다. )

function dispatchAndLog(store, action) {
  console.log('dispatching', action)
  store.dispatch(action)
  console.log('next state', store.getState())
}
dispatchAndLog(store, addTodo('Use Redux'))

그러나..

- 아름답지 않다

- 로그를 지우고 싶다 그러면 console.log()를 없애야 한다.

- 그럼 코드 지워야 한다.

- 빌드 다시 

- 배포 다시 

- qa 다시

...

버그 생기면 x 됨

> 결론: 코드를 바꾸는 행위는 비싼 행위이다.

"가능하면 코드를 수정하지 않고 코드의 행위를 바꿀수 있는 테크닉들을 연구 하는 것이다."

그래서 위의 코드는 안좋다.

 

몽키패칭

우리가 저장소 인스턴스에 있는 dispatch 함수를 대체한다면 어떨까요? Redux의 저장소는 몇개의 메서드를 가진 평범한 오브젝트일 뿐이고, 우리는 자바스크립트로 작성하고 있으니 dispatch구현을 몽키패칭할 수 있습니다.

> 원래꺼를 다른걸로 바꾸겠다.

let next = store.dispatch
store.dispatch = function dispatchAndLog(action) {
  console.log('dispatching', action)
  
  let result = next(action) //원래 dispatch() 꺼를 사용하고 있다.
  
  console.log('next state', store.getState())
  return result
}

2개 이상

우리가 dispatch에 이런 변환을 두 개 이상 적용하고 싶다면 어떨까요?

 

로깅 + error로깅

function patchStoreToAddLogging(store) {
  let next = store.dispatch
  store.dispatch = function dispatchAndLog(action) {
    // ...some
  }
}

function patchStoreToAddCrashReporting(store) {
  let next = store.dispatch
  store.dispatch = function dispatchAndReportErrors(action) {
    //..some
  }
}

patchStoreToAddLogging(store)
patchStoreToAddCrashReporting(store)

내가 할일만 하고 다시 바꿔놔야 한다. 그래서 여기서 나온 것이 무엇일까요?

 

function add(a,b){
	return a + b;
}

function add2(a){
	return function(b){
    	return a + b;
    }
}

add(10,20)
// 사용자가 최종 계산에 개입할 여지가 없다.
add(10)(20)
// 10과 20이 더해지는 사이에 사용자가 개입 할 수있다.

const next = add(10) // 함수 합성, 함수 조합..등 이름이다.
// do something
// 지연 평가
next(20);
// 즉 몽키패칭 할 수 있다. 사용자가 뭔가를 할 수 있다.
// 즉 커링이란 사용자에게 인자와 인자 사이에 개입할수 있는 여지를 열어 줄수 있는 테크닉

몽키패칭 숨기기

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

이 코드를 사용하는 주체는 누구일까?

 

____ 가 let next 이 사이에 본인들이 사용한다는 뜻이다.

add(10)(20)
const next = add(10) 

// 원래 작업 실행

next(20);

최종적으로

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 는 미들웨어끼리 함수를 부른것이 아니다 첫번째 미들웨어 실행후 다음 미들웨어가 실행 되어야 하는데 그걸 부르기 위해서 next가 있는것이다. 이건 redux에 몽키패칭을 돕는 헬퍼가 있다. 거기서 알아서 넘겨준다.

function applyMiddlewareByMonkeypatching(store, middlewares) {
  middlewares = middlewares.slice()
  middlewares.reverse()

  // 각각의 미들웨어로 디스패치 함수를 변환합니다.
  middlewares.forEach(middleware => (store.dispatch = middleware(store)))
}

es6 표현식

const logger = store => next => action => {
  console.log('dispatching', action)
  let result = next(action)
  console.log('next state', store.getState())
  return result
}

등록된 미들웨어를 순서대로 연결시놓은 구조를 redux에서 할수 있도록 열어둔 커링 테크닉

gist.github.com/ibare/0eb8597551070bf1ebf8e797439913a3

 

middlewares 가 들어간다.

export function createStore(reducer, middlewares = []) {
  let state;
  const listeners = [];
  const publish = () => {
    listeners.forEach(({ subscriber, context }) => {
      subscriber.call(context);
    });
  };

  const dispatch = (action) => {
    state = reducer(state, action);
    publish();
  };

  const subscribe = (subscriber, context = null) => {
    listeners.push({
      subscriber,
      context
    });
  };

  const getState = () => ({ ...state });
  const store = {
    dispatch,
    getState,
    subscribe
  };

  middlewares = Array.from(middlewares).reverse();
  let lastDispatch = store.dispatch;

//예시
const mymid = store => next => action => { }

  middlewares.forEach((middleware) => {
    lastDispatch = middleware(store)(lastDispatch);
  });

  return { ...store, dispatch: lastDispatch };
}

export const actionCreator = (type, payload = {}) => ({
  type,
  payload: { ...payload }
});


왜 reverse() 를 했을까?

> 마지막 lastDispatch() 를 넘겨주니까

[ 1, 2, 3 ] 미들웨어면 => [ 3, 2, 1] 실행하고 1의 action을 return 하기 때문에

 

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

const store = createStore(reducer, [logger, monitor]);

만약 api 작업을 하고 싶다면

 

next(action) 을 하기전에 비동기 작업후 값이 오면 next(action) 을 하면 됩니다.

const logger = (store) => (next) => (action) => {
  console.log("logger: ", action.type);
  if( action.type === 'get user info'){
  	api.call('/user').then(res =>{
    	next({
        	type:'res user info',
            data: res
        });
    });
  }else{
  	next(action);
  }
};

const monitor = (store) => (next) => (action) => {
  setTimeout(() => {
    console.log("monitor: ", action.type);
    next(action);
  }, 2000);
};

const store = createStore(reducer, [logger, monitor]);

 

'프로젝트 & 강의' 카테고리의 다른 글

Next js (1) 기초지식  (0) 2021.12.10
react 웹앱 서비스 개발  (0) 2021.12.08
react (1)  (0) 2020.11.16
[follow] 2020.03.13  (0) 2020.03.13
[Follow] 프로젝트 일기  (0) 2020.03.11

댓글