Functional Programming - Thunk

이번 주제는 이어가는 내용 보다는 실제로 실무에서 어떻게 쓰여지는지 얘기하고 싶어서다. 사실 Redux 밖에 FP 패러다임을 지켰다는 것 밖에 모른다. 다른건 아직 관심 없다. 여기서 아주 간단한 소스를 보여주려 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}

return next(action);
};
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;

Thunk 미들웨어의 소스며, 전부다. Redux 는 미들웨어를 통해 action 을 거치기 전 추가된 미들웨어를 거쳐 가공할 수 있게 만들어져있다. 즉, 일종의 플러그인이다. 사실 Redux 를 모르는 분은 동작 방법을 이해 못할 수도 있다. 나는 그냥 FP 에 대해 설명하기 위해 추가했으므로, 이해 못해도 아무런 문제가 없으니 두려워하지 않았으면 좋겠다.

Arrow 표현식을 풀면 이렇게 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
function createThunkMiddleware(extraArgument) {
return function ({ dispatch, getState }) {
return function (next) {
return function (action) {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}

return next(action);
}
}
}
}

마치, 콜백 지옥을 보는 듯한 모양이지 않나? Arrow 표현식이 들어오면서 정말 손쉬워졌다. Redux 의 성정 초반에 applyMiddleware 를 쓴다. 그리고,

1
const middleware = applyMiddleware(thunk)

같이 쓸 것이다. 즉, createThunkMiddleware 함수를 이미 실행한 나머지를 미들웨어로 넘기는 것이다. 너무 우아한 함수다. 첫번째 리턴의 dispatch 와 getState 는 store 오브젝트를 받은 것 뿐이다. 예를 들어,

1
const { dispatch, getState } = store

라고 생각하면 된다. 아무튼 원래 Redux 는 비동기 호출에 관해서는 구현되지 않았다. 그래서 우리가 주입식으로 익혔다면,

1
const asyncFn = (arg) => ({ dispatch, getState }) => Promise.resolve()

같은 방법으으로 비동기를 썻을 것이다. 여기서 (dispatch, getState) 은 thunk 에서 if (typeof action === ‘function’) 에서 걸린다고 보면 된다. 미들웨어를 등록했으니, thunk 를 흘러 가공 되는데, 만약 (dispatch, getState) 같이 함수를 다시 리턴하는 경우에는 if 문에 걸리는 것이다. 여기에 ‘extraArgument 는 뭐지?’ 생각하는 분도 있다. 이게 참 우아한 코드 중 하나다.

우리는 axios 를 쓸 때 instance 를 만들어 쓸 수 있다. 즉, 모든 Redux action 에 instance 를 적용하고 싶은데, 굳이 import 를 하지 않아도 될 수 있는 옵션을 넣은 것이다. 예를 들어,

1
2
3
4
5
6
7
8
9
import axios from 'axios'
import { applyMiddleware } from 'redux'
import thunk from 'redux-thunk'

const instance = axios.create({ ... })
const middleware = applyMiddleware(thunk.withExtraArgument(instance))

// action
const asyncAction = (arg) => (dispatch, getState, instance) => instance({ ... })

이런 방법이 가능하다. 이런식으로 활용 가능하다. 여기서 초점은 변수를 받아서 결과를 만들어내는 굳이, 용도에 따라 여러 함수를 만들 필요가 없는 것이다.