ABOUT ME

jacey.park.dev@gmail.com \n 5년차 프론트엔드 개발자

Today
-
Yesterday
-
Total
-
  • [JS] Redux Thunk 알아보기
    Dev/JS 2018. 4. 28. 20:01

    Redux 미들웨어인 Redux Thunk에 대해서 알아봅시다.

    redux-thunk 도큐먼트를 번역하였습니다.





    Redux Thunk?


      Redux Thunk는 리덕스의 미들웨어로서, 액션 생성자를 작성하면 액션 대신에 함수가 반환되게끔 할 수 있습니다.  또한, 액션을 디스패치 할때 delay를 줄 수 도 있고, 아니면 특정한 조건이 만족되면 디스패치 할 수 있는 기능도 있습니다. 그리하여 내부 함수에서 dispatch, getState와 같은 메소드를 매개변수로 받을 수 있습니다.



    const INCREMENT_COUNTER = 'INCREMENT_COUNTER';

    function increment() {
    return {
    type: INCREMENT_COUNTER
    };
    }

    function incrementAsync() {
    return dispatch => {
    setTimeout(() => {
    dispatch(increment()); //dispatch() 를 통해 동기 혹은 비동기 액션을 부를 수 있다.
    }, 1000)
    }
    }




    function incrementIfOdd() {
    return (dispatch, getState) => {
    const { counter } = getState();

    if(counter % 2 === 0) { //조건문을 통해 함수를 반환할 수 있다.
    return;
    }
    dispatch(increment());
    }
    }






    Thunk?


    thunk는 표현식을 감싸고, 표현식 안에 있는 내용의 수행을 지연시킬 수 있는 함수입니다.


    let x = 1 + 2; // 1 + 2가 바로 수행된다.
    // x는 3이다.

    let foo = () => 1 + 2; // 1 + 2의 계산이 지연된다.
    // foo 함수가 호출 된 후에, 1 + 2의 계산이 수행된다.
    // foo 가 thunk 함수이다.




    설치방법


    npm install --save redux-thunk



    그리고 applyMiddleware()를 사용하여 createStore() 안에서 리덕스와 연결합니다.



    import { createStore, applyMiddleware } from 'redux';
    import thunk from 'redux-thunk';
    import rootReducer from './reducers/index';

    const store = createStore(
    rootReducer,
    applyMiddleware(thunk)
    )




    비교하기 


    다음은 우리가 흔히 알고있던 미들웨어가 없는 리덕스 코드입니다.


    import { createStore, applyMiddleware } from 'redux';
    import thunk from 'redux-thunk';
    import rootReducer from './reducers/index';

    const store = createStore(
    rootReducer,
    applyMiddleware(thunk)
    )


    function fetchSecretSauce() {
    return fetch('http://www.google.com/search?q=secret+sauce');
    }


    // 지금까지 봐왔던 평범한 액션 생성자 입니다.
    // 미들웨어 없이 액션을 반환합니다.

    // 하지만 이는 '사실'만을 반환합니다. 또한 비동기처리를 하지 못합니다.


    function makeASandwich(forPerson, secretSauce) {
    return {
    type: 'MAKE_SANDWICH',
    forPerson,
    secretSauce
    }
    }

    function aplogize(fromPerson, toPerson, error) {
    return {
    type: 'APOLOGIZE',
    fromPerson,
    toPerson,
    error
    };
    }

    function withdrawMoney(amout) {
    return {
    type: 'WITHDRAW',
    amout
    };
    }

    //미들웨어 없이 이렇게 dispatch할 수 있습니다.

    store.dispatch(withdrawMoney(100));



     위와 같이 Redux는 오직 '사실' 만 인것을 반환합니다. 또한 비동기 처리도 하지 못하죠.


    하지만 비동기 액션 처리를 하거나, API를 호출하거나 혹은 라우터 트랜지션을 처리할때는 어떻게 해야 할까요?


    Thunk 미들웨어를 사용하면 가능합니다! 다음은 Thunk를 붙인 코드입니다. (위 코드와 연결)



    함수를 리턴한 후 디스패치


    function makeASandwichWithSecretSauce(forPerson) {
    //dispatch를 사용하여 함수를 반환합니다. 그래서 우리는 디스패치를 나중에 할 수 있습니다.

    return function (dispatch) {
    return fetchSecretSauce().then(
    sauce => dispatch(makeASandwich(forPerson, sauce)),
    error => dispatch(aplogize('The Sandwich Shop', forPerson, error))
    );
    };
    }


    ㄴ> Thunk는 dispatch()를 사용하여 함수를 리턴한 후, 디스패치를 할 수 있습니다.



    Thunk 비동기 액션 디스패치


    // 미들웨어를 사용하여 thunk 비동기 액션을 디스패치할 수 있습니다.
    // 마치 보통의 액션을 반환하는 것 처럼
    store.dispatch(
    makeASandwichWithSecretSauce('Me')
    );


    ㄴ> 그리고 보통의 리덕스에서 액션을 반환하는 것 처럼, thunk 비동기 액션을 디스패치할 수 있습니다.



    Promise Chain 가능


    // dispatch로 부터 thunk의 반환값을 반환하는 것은 주의를 기울여야함.
    // 그래서 리턴을 보내는 만큼 프로미스 채이닝이 가능하게 되어있음.
    store.dispatch(
    makeASandwichWithSecretSauce('My wife')
    ).then(() => {
    console.log('Done!')
    })


    ㄴ> dispatch 로부터 thunk의 반환값을 반환하는 것은 주의를 기울여야합니다. 그래서 그것들을 리턴할 수 있는 만큼 Promise 채이닝이 가능합니다.



    다른 액션 객체 같이 사용 가능


    // 사실 다른 액션 생성자의 디스패치할 액션이나, 비동기 액션을 액션 생성자로 작성할 수 있다.
    // 그리고 제어 흐름을 프로미스로 관리할 수 있다.
    function makeSandwichesForEverybody() {
    return function (dispatch, getState) {
    if (!getState().sandwiches.isShopOpen) {
    // 굳이 프로미스를 반환안해도 됨. 하지만 편리하게 쓰기위한 컨벤션 이다..
    // 그래서 호출자는 비동기 결과에대해 then()을 언제든지 부를 수 있음
    return Promise.resolve();
    }

    // 일반적인 액션객체와 다른 thunk들을 같이 디스패치 할 수 있음
    // 단일 플로우로 비동기 액션을 처리할 수 있음
    return dispatch(
    makeASandwichWithSecretSauce('My Grandma')
    ).then(() =>
    Promise.all([
    dispatch(makeASandwichWithSecretSauce('Me')),
    dispatch(makeASandwichWithSecretSauce('My wife'))
    ])
    ).then(() =>
    dispatch(makeASandwichWithSecretSauce('Our kids'))
    ).then(() =>
    dispatch(getState().myMoney > 42 ?
    withdrawMoney(42) :
    apologize('Me', 'The Sandwich Shop')
    )
    );
    };
    }


    ㄴ> 다른 액션 생성자의 디스패치할 액션이나, 비동기 액션에 대하여 액션 생성자를 작성할 수 있습니다. 또한 위에서 언급했듯이 Promise로 흐름을 관리할 수 있습니다. 


    하지만 굳이 Promise를 반환하지 않아도 됩니다. 그렇지만 편하게 사용하려면 쓰는게 좋습니다. 그래서 언제든지 .then()을 호출할 수 있습니다.


    또한, 일반적인 액션객체와 다른 thunk들을 같이 디스패치할 수 있습니다. 



    서버사이드 렌더링


    // 서버사이드 렌더링에 매우 유용하게 쓰인다
    // 데이터가 존재한 후에 그때 앱을 렌더링할 수 있기 때문이다.

    store.dispatch(
    makeSandwichesForEverybody()
    ).then(() =>
    response.send(ReactDOMServer.renderToString(<MyApp store={store} />))
    );



    ㄴ> 위 코드와 같이 데이터를 불러 온 후, 앱을 렌더링 할 수 있습니다.



    컴포넌트에서 디스패치


    //thunk 비동기 액션을 컴포넌트에서 디스패치 할 수 있다.
    // props 가 변경될 때마다 누락된 데이터를 불러온다.

    import { connect } from 'react-redux';
    import { Component } from 'react';
    class SandwichShop extends Component {
    componentDidMount() {
    this.props.dispatch(
    makeASandwichWithSecretSauce(this.props.forPerson)
    );
    }
    componentWillReceiveProps(nextProps) {
    if (nextProps.forPerson !== this.props.forPerson) {
    this.props.dispatch(
    makeASandwichWithSecretSauce(nextProps.forPerson)
    );
    }
    }
    render() {
    return <p>{this.props.sandwiches.join('mustard')}</p>
    }
    }
    export default connect(
    state => ({
    sandwiches: state.sandwiches
    })
    )(SandwichShop);

    ㄴ> 다음과 같이 컴포넌트에서도 액션을 디스패치 할 수도 있습니다. props가 변경되는 과정 중에, 누락된 데이터를 불러올 수 있습니다.




    참고


    https://github.com/gaearon/redux-thunk

    댓글

Designed by Tistory.