[React.JS] 강좌 12편 axios 모듈을 통한 웹서버와의 통신 (AJAX) 알아보기


liste

이 강좌는 outdated 되었습니다. 더 좋은 내용을 다루는 강좌를 준비하도록 하겠습니다. 이 강좌는 참고용으로만 읽어주세요.

이번 포스트에서는 Express 프레임워크를 사용한 Node 웹서버에서 간단한 REST API 를 구현하고, React.js 어플리케이션에서 axios 라이브러리를 통하여 AJAX 를 통하여 통신하는 방법에 대하여 알아보겠습니다. 추가적으로, 이 강좌에서는 Redux 에 대한 설명과 Express.js 서버와 React.js 를 함께 사용하는 방법에 대해선 생략되었으니 이전 강좌들을 참고해주세요.

# 프로젝트 미리보기

ok

이전에 Redux 를 배우면서 만들었던 예제 에서는 Redux를 통해 Flux 데이터 흐름 구조를 사용하는 카운터를 만들었었죠?
오늘은 서버와 연동한 카운터를 만들어보도록 하겠습니다. 또, 별거아닌 카운터이지만, CSS 를 사용하여 꾸며보겠습니다.

# 짚고 넘어가기

React.js 는 효율적인 UI 구현을 위한 라이브러리 입니다. HTTP Client 를 내장하고있는 Angular 와는 다르게, React.js 는 따로 내장 클래스가 존재하지 않죠. 따라서 React.js 어플리케이션에서 AJAX 를 구현하려면 JavaScript 내장객체인 XMLRequest 를 사용하거나, 다른 HTTP Client 라이브러리와 함께 사용하셔야합니다.

# 어떤 HTTP Client 라이브러리를 사용하는게 좋을까?

위 질문에 대한 해답은 없습니다. 자신이 익숙한, 혹은 편하다고 생각하는 라이브러리를 사용하시면됩니다.

jQuery 라이브러리가 익숙하신분은 jQuery를 사용하셔도 됩니다. 하지만, jQuery는 ajax 외에도 저희 React.js 어플리케이션엔 더 이상 필요하지 않을 쓸모없는 기능을 많이 가지고있죠. jQuery를 사용하고 싶은 경우엔 jQuery-builder 를 사용하여 ajax 부분만 추출하여 사용하시면 됩니다.

링크를 참조하시면, React.js 와 함께 쓰기에 좋은 HTTP Client 라이브러리들이 있습니다.

이 포스트에서는 axios 라이브러리를 사용하도록 하겠습니다. 사용법은 크게 어렵지않으니 이 포스트에 큰 설명은 안하도록 하겠습니다.

매뉴얼을 한번 읽어주시길 바랍니다


# 시작하기

프로젝트 환경 기반은 11편 강좌 에서 만든 환경에서부터 시작하겠습니다.

해당 강좌를 처음부터 따라하셔도 되고, 간단하게 GitHub에 있는 repository를 clone 하시면됩니다.

$ git clone https://github.com/velopert/react-express-hmr-example.git && cd react-express-hmr-example && npm install

# 디렉토리 구조 이해하기

./
├── build
├── package.json
├── public
│   ├── bundle.js
│   └── index.html
├── server 
│   ├── main.js
│   └── routes
│       └── counter.js
├── src
│   ├── actions
│   │   └── index.js
│   ├── components
│   │   ├── App
│   │   │   └── App.js
│   │   ├── Counter
│   │   │   ├── Counter.css
│   │   │   └── Counter.js
│   │   ├── index.js
│   │   └── Spinner
│   │       ├── Spinner.css
│   │       └── Spinner.js
│   ├── index.js
│   └── reducers
│       └── index.js
├── webpack.config.js
└── webpack.dev.config.js
  • build: 컴파일된 서버사이드 코드가 위치한 디렉토리입니다.
  • public: 서버의 컨텐츠 베이스 입니다
    • bundle.js: 컴파일된 클라이언트 사이드 코드가 합쳐져서 이 파일에 작성됩니다.
  • server: ES6 문법으로 작성된 서버사이드 코드가 위치한 디렉토리입니다.
  • src: ES6 문법으로 작성된 클라이언트사이드 코드가 위치한 디렉토리입니다.
    • components/index.js: 모든 컴포넌트를 import 한다음에 export 합니다 (편의를 위하여)
    • index.js: webpack entry point

저희는 Express 서버에 2개의 API – 값 불러오기 & 값 1씩 추가하기 – 를 만들것이며,
3가지의 컴포넌트 – App, Counter, Spinner 를 만들것입니다.
디렉토리 구조에 보이다시피, 저희는 컴포넌트에서 CSS 파일을 사용 할 것인데요, 이는 css-loader 를 필요로합니다.
css-loader 를 적용하는 방법은 [React.JS] Tip: Webpack css-loader 를 통하여 .css 파일을 import 하여 사용하기 를 읽어주세요.


# 강좌 진행순서

  1. Express.js REST API 구현
    1. 라우터 작성하기
      1. GET: 현재 값 가져오기
      2. POST: 값에 1 더하기
    2. main.js 수정하기
  2. 빈(empty) 컴포넌트 생성
  3. Redux 설정하기
    1. action 작성
    2. reducer 작성
    3. store 생성 및 컴포넌트 렌더링
  4. Counter 컴포넌트 작성하기
    1. Redux 연동
    2. 기본 CSS 및 뼈대 작성
    3. 컴포넌트 로드 후 AJAX 요청
    4. 클릭하였을때 AJAX 요청
    5. 애니메이션 효과넣기
  5. Spinner 컴포넌트 작성하기
    1. CSS 및 JS 작성
  6. Counter 컴포넌트 초기 로딩 떄 Spinner 보여주기

# 1. Express.js REST API 구현

# 라우터 작성하기

server/routes/counter.js

import express from 'express';
import colors from 'colors';


export default function counter(data) {

    function getIP(req) {
        return req.connection.remoteAddress.split(":").pop();
    }

    const router = express.Router();

    router.post('/', (req, res) => {
        console.log(colors.green('[INC]'), ++data.number, getIP(req));
        return res.json({number: data.number});
    });

    router.get('/', (req, res) => {
        console.log(colors.yellow('[REQ]'), data.number, getIP(req));
        return res.json({number: data.number});
    });

    return router;

}

* 따로 데이터베이스를 사용하지 않고, main.js 파일에서 지정한 객체변수의 값을 데이터로 사용합니다.

colors 모듈은 node.js 콘솔의 텍스트에 색상을 쉽게 입힐 수 있게 해주는 모듈입니다. 컬러가 필요없다면 생략하셔도됩니다. 이 모듈을 사용하려면, npm 을 통해 설치하셔야합니다.

$ npm install --save-dev colors

# main.js 수정하기

server/main.js

import express from 'express';
import WebpackDevServer from 'webpack-dev-server';
import webpack from 'webpack';

const app = express();
const port = 3000;
const devPort = 3001;


if(process.env.NODE_ENV == 'development') {
    console.log('Server is running on development mode');

    const config = require('../webpack.dev.config');
    let compiler = webpack(config);
    let devServer = new WebpackDevServer(compiler, config.devServer);
    devServer.listen(devPort, () => {
        console.log('webpack-dev-server is listening on port', devPort);
    });
}


app.use('/', express.static(__dirname + '/../public'));

import counter from './routes/counter';
let data = { number: 0 };
app.use('/counter', counter(data));

const server = app.listen(port, () => {
    console.log('Express listening on port', port);
});
  • 웹서버가 켜질 때 마다 카운터의 값이 0으로 초기설정됩니다.

# 테스팅

$ npm run build && npm start

15

코드를 제대로 입력하셨더라면 오류가 날 일은 없겠지만, 완벽함을 위하여 좋아하는 REST API 테스팅 툴로 테스팅하세요.

이 포스트에서는 Chrome 확장프로그램인 Insomnia 가 사용되었습니다.


2. 비어있는 컴포넌트 만들기

왜 비어있는 컴포넌트를 만드냐구요? 먼저 파일들을 생성해둬야 나중에 작업하기도 편하고,
개발서버(webpack-dev-server) 를 열때 차질이 생기지 않기 때문입니다.

다음 명령어를 실행하면 디렉토리와 파일들을 한꺼번에 자동으로 생성합니다.

$ mkdir components components/App components/Counter components/Spinner && touch index.js components/App/App.js components/Counter/Counter.js components/Counter/Counter.css components/Spinner/Spinner.js components/Spinner/Spinner.css

src/components/index.js

import App from './App/App.js';
import Counter from './Counter/Counter.js';
import Spinner from './Spinner/Spinner.js';

export { App, Counter, Spinner };

src/components/App/App.js

import React from 'react';

import { Counter } from '../';


class App extends React.Component {
    render() {
        return (
            <Counter/>
        )
    }
}

export default App;

src/components/Counter/Counter.js

import React from 'react';

class Counter extends React.Component {
    render() {
        return (
            <div>Counter</div>
        )
    }
}

export default Counter;

src/components/Spinner/Spinner.js

import React from 'react';

class Spinner extends React.Component {
    render() {
        return (
            <div>Spinner</div>
        )
    }
}

export default Spinner;

# 개발서버 실행하기

$ npm run build && npm run development

서버를 실행하고 페이지에 접속해보세요. Counter 라고 떴나요? 이제 클라이언트 사이드 코드를 뚝딱뚝딱 작성해봅시다.


# 3. Redux 설정하기

# 의존 모듈 설치하기

$ npm install --save redux react-redux

# action 작성하기

src/actions/index.js

export const RECV_VALUE = "RECV_VALUE";

export function receiveValue(value) {
    return {
        type: RECV_VALUE,
        value: value
    };
};

이 프로젝트에 필요한 action 은 단 한가지입니다. 나중에 Ajax 요청을 했을 때, 그 결과값을 처리하는 action 인데요,
저희가 준비한 두 API 둘 다 같은 종류의 결과값을 반환하므로 하나의 action으로도 충분합니다.

# reducer 작성하기

src/reducers/index.js

import { RECV_VALUE } from '../actions';

const initialState = {
    value: -1
};

const counterReducer = (state = initialState, action) => {
    switch(action.type) {
        case RECV_VALUE:
            return Object.assign({}, state, {
                value: action.value
            });
        default:
            return state;
    }
};

export default counterReducer;

카운터의 초기값은 -1 입니다.

-1으로 설정함으로서, 컴포넌트가 렌더링 된 후, 첫 Ajax 요청이 처리가 완료되었는지 안되었는지 구분합니다.

# store 생성 및 렌더링

src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { App } from './components';

import { createStore } from 'redux';
import { Provider } from 'react-redux';
import counterReducer from './reducers';

const store = createStore(counterReducer);

const rootElement = document.getElementById('root');
ReactDOM.render(
    <Provider store={store}>
        <App/>
    </Provider>, rootElement
);

# 4. Counter 컴포넌트 작성하기

# Redux 연동

src/components/Counter/Counter.js

import React from 'react';

import { connect } from 'react-redux';
import { receiveValue } from '../../actions';

class Counter extends React.Component {

    render() {
        return (
            <div>Counter</div>
        )
    }
}


const mapStateToProps = (state) => {
    return {
        value: state.value
    };
};

const mapDispatchToProps = (dispatch) => {
    return {
        onReceive: (value) => {
            dispatch(receiveValue(value));
        }
    }
}

export default connect(mapStateToProps, mapDispatchToProps)(Counter);

# 기본 CSS 및 뼈대 작성

컴포넌트에서 CSS 파일을 import 하여 사용하려면 webpack css-loader 를 사용해야합니다.

의존 모듈 설치

$ npm install --save-dev style-loader css-loader

webpack.config.js / webpack.dev.config.js 파일 수정

loaders: [
        {
            test: /\.js$/,
            loader: 'babel',
            exclude: /node_modules/,
            query: {
                cacheDirectory: true,
                presets: ['es2015', 'react']
            }
        },
        {
          test: /\.css$/,
          loader: 'style!css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]'
        }
    ]

여러분의 마음에 드는대로 CSS를 작성하세요.

src/components/Counter/Counter.css

body {
    margin: 0px;
}

.container {
    background-color: #1ABC9C;
    height: 100%;
    width: 100%;
    position: fixed;
    cursor: pointer;
}

.center {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
}

.number {
    font-weight: bold;
    text-shadow: 0 0 5px #1B6254;
    font-size: 7em;
    color: white;

    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
}

src/components/Counter/Counter.js

import React from 'react';

import { connect } from 'react-redux';
import { receiveValue } from '../../actions';
import style from './Counter.css';

class Counter extends React.Component {

    render() {
        return (
            <div className={style.container}>
                <div className={style.center}>
                    <div className={style.number}>
                        {this.props.value}
                    </div>
                </div>
            </div>
        )
    }
}

/* 생략 */

f

이러한 디자인이 형성되었습니다.

# axios 설치

npm install --save axios

axios 사용 예제

불러오기

import axios from 'axios';

저희는 npm 을 통해 설치하였기에 프로젝트에서 직접 불러와서 사용합니다.

상황에 따라 CDN 에서 불러와도 됩니다. (<script src="https://npmcdn.com/axios/dist/axios.min.js"></script>)

GET 요청

axios.get('/user?id=velopert')
    .then( response => { console.log(response); } ) // SUCCESS
    .catch( response => { console.log(response); } ); // ERROR

axios.get('/user', {
        params: { id: 'velopert' }
    })
    .then( response => { console.log(response) } );
    .catch( response => { console.log(response) } );

// catch 는 생략 될 수 있습니다.

POST 요청

axios.post('/msg', {
        user: 'velopert',
        message: 'hi'
    })
    .then( response => { console.log(response) } )
    .catch( response => { console.log(response) } );

더 많은 사용 예제는 메뉴얼을 참고해주세요

참고: axios 는 IE에선 기본적으로는 호환이 안됩니다. 호환시키려면 https://babeljs.io/docs/usage/polyfill/ 를 참고해주세요.

# 컴포넌트 로드 후 AJAX 요청

컴포넌트의 초기 AJAX 요청은 언제나 componentDidMount LifeCycle API 안에서 하세요.

componentWillMount 안에 작성하여도 작동하긴 하나, 여기선 DOM Manipulation 이 불가합니다.

서버사이드 렌더링시엔 componentDidMount는 실행되지 않고 componentWillMount 는 실행됩니다.

src/components/Counter/Counter.js

import React from 'react';
import axios from 'axios';
import { connect } from 'react-redux';
import { receiveValue } from '../../actions';
import style from './Counter.css';

class Counter extends React.Component {

    componentDidMount() {
        let getNumber = () => {
            axios.get('/counter').then(response => {
               this.props.onReceive(response.data.number);
               setTimeout(getNumber, 1000 * 5); // REPEAT THIS EVERy 5 SECONDS
            });
        }

        getNumber();

    }

/* 생략 */

컴포넌트 초기 AJAX 요청을 하고, 매 5초마다 값을 서버와 동기화 하게끔 코드를 작성하였습니다.

브라우저로 테스팅을 해보면 처음엔 값이 -1이였다가 약 0.5초 정도 후 0으로 값이 업데이트됩니다.
보기에 좀 안좋죠? 물론, -1이 아니라 아예 공백으로 하는것도 방법이긴 합니다.
저희는, 잠시 후 Spinner 컴포넌트를 만들어서, 초기 로딩을 할때 로딩창이 보이게 할 것입니다.

# 클릭하였을 때 AJAX 요청

src/components/Counter/Counter.js

/* 생략 */

class Counter extends React.Component {

    constructor(props) {
        super(props);
        this.onClick = this.onClick.bind(this);
    }

    componentDidMount() {
        /* 생략 */
    }

    render() {
        /* 생략 */
    }

    onClick() {
        axios.post('/counter').then(response => {
            this.props.onReceive(response.data.number);
        });
    }
}


/* 생략 */

꽤 간단하죠? 한번 브라우저로 테스팅해보세요. 잘 되나요?

수고하셨습니다! 이제 기능상의 부분은 모두 완성되었습니다. 서버와의 연동은 이런식으로 간단하게 하면 됩니다.

하단부는 눈을 즐겁게 하기 위한 효과를 넣는 과정입니다.
관심이 없으신분들은 생략하셔도 됩니다.

# 애니메이션 효과 넣기

src/components/Counter/Counter.css

/* 생략 */ 

/*
 * Animation
 */

@-webkit-keyframes bounce {
        0%, 20%, 50%, 80%, 100% {-webkit-transform: translateY(0);}
        40% {-webkit-transform: translateY(-10px) }
        60% {-webkit-transform: translateY(-5px);}

}
@keyframes bounce {
        0%, 20%, 50%, 80%, 100% {transform: translateY(0);}
        40% {transform: translateY(-10px);}
        60% {transform: translateY(-5px);}

}


.bounce {
        -webkit-animation-duration: 0.5s;
         animation-duration: 0.5s;
         -webkit-animation-name: bounce;
         animation-name: bounce;
}

src/components/Counter/Counter.js

/* 생략 */
    render() {
        return (
            <div className={style.container} onClick={this.onClick}>
                <div className={style.center}>
                    <div className={style.number} ref={ ref => { this.element = ref } }>
                        {this.props.value}
                    </div>
                </div>
            </div>
        )
    }

    componentDidUpdate() {
        this.element.classList.remove(style.bounce);
        this.element.offsetWidth; // Triggers reflow; enables restart animation
        this.element.classList.add(style.bounce);
    }
/* 생략 */

이 과정에서 React.js 에서 DOM 을 manipulate 할 때 쉽게 할 수 있게 해주는 ref가 사용되었습니다.

컴포넌트가 업데이트 될 때마다 animation 클래스를 제거하고 다시 추가하는 방식으로 애니메이션을 적용하였습니다.


# 5. Spinner 만들기

spinner

http://tobiasahlin.com/spinkit/

저희는 위 페이지에서 Spinner 코드를 가져와서 사용 할 것입니다.

예쁜 로딩 Spinner 가 많으니 한번 방문해보세요

src/components/Spinner/Spinner.css

.spinner {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);

 /* margin: 100px auto;*/
  width: 50px;
  height: 40px;
}

.spinner > div {
  background-color: white;
  height: 100%;
  width: 6px;
  display: inline-block;
  margin: 1px;
  -webkit-animation: sk-stretchdelay 1.2s infinite ease-in-out;
  animation: sk-stretchdelay 1.2s infinite ease-in-out;
}

.spinner .rect2 {
  -webkit-animation-delay: -1.1s;
  animation-delay: -1.1s;
}

.spinner .rect3 {
  -webkit-animation-delay: -1.0s;
  animation-delay: -1.0s;
}

.spinner .rect4 {
  -webkit-animation-delay: -0.9s;
  animation-delay: -0.9s;
}

.spinner .rect5 {
  -webkit-animation-delay: -0.8s;
  animation-delay: -0.8s;
}

@-webkit-keyframes sk-stretchdelay {
  0%, 40%, 100% { -webkit-transform: scaleY(0.4) }
  20% { -webkit-transform: scaleY(1.0) }
}

@keyframes sk-stretchdelay {
  0%, 40%, 100% {
    transform: scaleY(0.4);
    -webkit-transform: scaleY(0.4);
  }  20% {
    transform: scaleY(1.0);
    -webkit-transform: scaleY(1.0);
  }
}

src/components/Spinner/Spinner.js

import React from 'react';
import style from './Spinner.css';

export default class Spinner extends React.Component {
    render() {
        return (
                <div className={style.spinner}>
                <div className={style.rect1}></div>
                <div className={style.rect2}></div>
                <div className={style.rect3}></div>
                <div className={style.rect4}></div>
                <div className={style.rect5}></div>
                </div>
            );

    }
}

# 6. Counter 수정하기

src/components/Counter/Counter.js

/* 생략 */
import { Spinner } from '../';

    /* 생략 */

    render() {

        const number = (
            <div className={style.number} ref={ ref => { this.element = ref } }>
                {this.props.value}
            </div>
        );

        const spinner = (
            <Spinner/>
        );

        return (
            <div className={style.container} onClick={this.onClick}>
                <div className={style.center}>
                    { (this.props.value == -1) ? spinner : number }
                </div>
            </div>
        )
    }

/* 생략 */

위와같이, Spinner 컴포넌트를 import 하고, render 메소드를 수정하면 됩니다.

이렇게 하면, 로딩화면이 제대로 뜨긴 할텐데요, 로딩시간이 그렇게 긴건 아니라서 저희가 실컷 준비한 예쁜 로딩화면을 제대로 보여주질 못하죠..

AJAX 요청을 컴포넌트가 로딩 된 후, 1초 후 실행하게 합시다.

    componentDidMount() {
        let getNumber = () => {
            axios.get('/counter').then(response => {
               this.props.onReceive(response.data.number);
                setTimeout(getNumber, 1000 * 5); // REPEAT THIS EVERY 5 SECONDS
            });

        }

        setTimeout(getNumber, 1000);

    }

giphy

수고하셨습니다!


# 마치면서..

LIVE PREVIEW: https://remotecounter.hoah.xyz/ (링크가 짤렸습니다)

GITHUB: https://github.com/velopert/react-remote-counter

이번 예제 꽤 재미있지 않았나요? 너무 심플해서 조금 식상하기도 했습니다.

다음 강좌에선 조금더 Complex 한 프로젝트를 만들어보겠습니다 – 객체배열 형식의 데이터를 처리하는 웹 어플리케이션 

그리고, 더~ 다음번엔 기회가 되면 이번에 만든 프로젝트에 Socket.io 를 적용하여 5초마다 값을 업데이트하는게 아닌,

정말 실시간 으로 업데이트 하는 방법을 알아보겠습니다.

Reference

  1. “Where to make an Initial AJAX request from in ReactJS”. Stackoverflow.
  2. “Restart CSS Animation”. CSS-Tricks.

liste

  • Steve

    reactjs를 가지고 쇼핑몰을 만들 수 있을까요?
    react와 nodejs 그리고 db는 mysql을 생각하는데 가능할까요?

    • 네 충분히 만들 수 있죠.
      한국의 온라인쇼핑몰 중에선 React.js 를 사용히는곳이 아직은 하나도 없겠지만요,

      보통 대부분의 쇼핑몰들이 그냥 외주업체에 맡기거나, 이미 만들어진 솔류션을 기반으로 하기 때문에…

      그러나, React.js 가 쇼핑몰에 적합할지는, 모르겠네요.
      보통 React는 실시간으로 변화하는 데이터를 다룰 때에 정말 적합한데, 쇼핑몰은 거의 static 하지 않나요? Interaction 이 많을 경우에는 React를 사용하면 효율적으로 UI를 짤 수있겠지만 그런게 아닌 일반적인 쇼핑몰엔 불필요할수도 있을것같아요. 어떤 쇼핑몰을 만드냐에 따라 다른것같습니다.

    • Kyeseung Kim

      남은 재고가 실시간으로 보여진다거나하면 재밌을수도 있을것 같아요

  • Sang Ik Bae

    리액트 공부하는데 한줄기 빛과 같네요 항상 감사합니다

  • 재밌게 읽고 갑니다~^^

  • NunuNana

    정말 친절한 강의입니다 벨로버트님은 실제로도 착한 분이실것 같네요

  • jun

    웹페이지를 열었을때 처음 데이터들을 ajax에서 불러와야 되는 상황에서 궁금한 점이 있습니다.
    보통 리듀서에서 처음 데이터 형식을 정하는거 같은데
    const initialState = {
    value: -1
    };

    위 처럼요

    ajax에서 불러와서 그 정보를 기반으로 화면에 정보를 렌더링 해야 하는 상황에서는 받아올 json구조를 리듀서에 잡아놓고

    const initialState = {
    type: “”,
    data: [
    {
    id:1,
    name”
    }
    ]
    };

    해당 리듀서로 스토어를 생성해야 하는게 맞는건가요?

    스토어를 생성하기 전에
    ajax통신후 json파일을 받아와서 해당 정보를 가지고 store를 생성하는게 맞는거 같기도 하고

    예제 처럼 초기 값이 클라에서 담고 시작하는게 아니라
    많은 데이터 들이 이미 서버에 있고 ajax로 그정보를 불러와 Redux환경에서 프로젝트를 시작하는 과정이 궁금합니다.

    설명하기 복잡하면 괜찮은 예제 사이트가 없을까요? 찾아보고 있는데 찾기가 힘드네요..

    • 제가 지금 강의 준비용으로 시간남을때 작성하고있는 프로젝트가 있는데 한번 참고해보시겠어요?
      이 프로젝트또한 렌더링할데이터가 서버의 데이터베이스에 있고 ajax 로 불러오는데요.

      이런 경우엔 componentDidMount 부분에 ajax 요청을 넣어서 구현한답니다.
      https://facebook.github.io/react/tips/initial-ajax.html

      제 프로젝트 코드는 아직 완성되진 않았는데요 링크: https://github.com/velopert/react-codelab-memopad
      이런 프로젝트의 구조 같은걸 이해하실때 도움이 될 수 있을 것 같습니다.

      https://github.com/velopert/react-codelab-memopad/blob/master/src/containers/Home.js

      여기서 componentDidMount 에 memoListRequest(true) 으로 초기 로딩을 했죠.
      이 프로젝트에선 redux-thunk 라는 라이브러리가 사용됐는데요, 보시면

      memoListRequest(true) 이게 https://github.com/velopert/react-codelab-memopad/blob/master/src/actions/memo.js
      이 파일에 있는데 action 객체 생성자가 아니라, dispatch 를 파라미터로 가지고있는 함수를 리턴하는 함수에요.

      /* MEMO LIST */

      export function memoListRequest(isInitial, listType, id){
      return (dispatch) => {

      dispatch(memoList());

      let url = ‘/api/memo/list’;

      url = isInitial ? url : url + ‘/’ + listType + ‘/’ + id;

      return axios.get(url)
      .then((response) => {
      return dispatch(memoListSuccess(response.data, isInitial, listType));
      }).catch((error) => {
      return dispatch(memoListFailure(error));
      });
      };
      }

      이런식으로 함수에서 ajax 요청을하고 이 함수에서 요청의 성공/실패 했을때 실행 할 액션을 dispatch 합니다.

      redux-thunk 는 이렇게 함수형태의 action 을 dispatch 할 수 있게 해줘요.

      redux-thunk 에 대하여 쉽게 설명한 자료가 국내엔 아진 없어서.. 조만간 작성할 예정이긴한데
      이에대한 설명은 https://github.com/gaearon/redux-thunk 매뉴얼에 의지하시는게 좋을것같아요.

      제 카운터 앱에서 사용된 ajax 흐름은 실무에선 잘 사용되지 않는편이에요
      간단한 프로젝트면 상관없지만, 프로젝트가 커질경우 저런식으로 하기엔 조금 힘들어지거든요.

      코드한번 살펴보시고, 궁금한거있으면 덧글 달아주시면 답변드리겠습니다 😀

    • 제가 지금 강의 준비용으로 시간남을때 작성하고있는 프로젝트가 있는데 한번 참고해보시겠어요? (하단 스샷 첨부)
      이 프로젝트또한 렌더링할데이터가 서버의 데이터베이스에 있고 ajax 로 불러오는데요.

      이런 경우엔 componentDidMount 부분에 ajax 요청을 넣어서 구현한답니다.
      https://facebook.github.io/rea

      제 프로젝트 코드는 아직 완성되진 않았는데요 링크: https://github.com/velopert/re
      이런 프로젝트의 구조 같은걸 이해하실때 도움이 될 수 있을 것 같습니다.

      https://github.com/velopert/re

      여기서 componentDidMount 에 memoListRequest(true) 으로 초기 로딩을 했죠.
      이 프로젝트에선 redux-thunk 라는 라이브러리가 사용됐는데요, 보시면

      memoListRequest(true) 이게 https://github.com/velopert/re
      이 파일에 있는데 action 객체 생성자가 아니라, dispatch 를 파라미터로 가지고있는 함수를 리턴하는 함수에요.

      /* MEMO LIST */

      export function memoListRequest(isInitial, listType, id){
      return (dispatch) => {

      dispatch(memoList());

      let url = ‘/api/memo/list’;

      url = isInitial ? url : url + ‘/’ + listType + ‘/’ + id;

      return axios.get(url)
      .then((response) => {
      return dispatch(memoListSuccess(response.data, isInitial, listType));
      }).catch((error) => {
      return dispatch(memoListFailure(error));
      });
      };
      }

      이런식으로 함수에서 ajax 요청을하고 이 함수에서 요청의 성공/실패 했을때 실행 할 액션을 dispatch 합니다.

      redux-thunk 는 이렇게 함수형태의 action 을 dispatch 할 수 있게 해줘요.

      redux-thunk 에 대하여 쉽게 설명한 자료가 국내엔 아진 없어서.. 조만간 작성할 예정이긴한데
      이에대한 설명은 https://github.com/gaearon/red… 매뉴얼에 의지하시는게 좋을것같아요.

      제 카운터 앱에서 사용된 ajax 흐름은 실무에선 잘 사용되지 않는편이에요
      간단한 프로젝트면 상관없지만, 프로젝트가 커질경우 저런식으로 하기엔 조금 힘들어지거든요.

      코드한번 살펴보시고, 궁금한거있으면 덧글 달아주시면 답변드리겠습니다 😀

      • jun

        친절한 답변 너무 감사드립니다. ^^
        많은 도움이 될꺼 같습니다.

        답변중에 링크 뒤가 공통적으로 …으로 생략되는거 같은데 full url을 알 수 있을까요?

        • disqus에서 자동으로 줄이는것같아요 ㅎㅎ
          링크가 되어있으니 클릭해보세요~

          • jun

            코드중에 mapDispatchToProps 없이 바로 props를 참조하여 dispatch를 사용하는데 이건 thunk를 사용해서 가능한 건가요?

          • http://redux.js.org/docs/FAQ.html#react-props-dispatch, this.props.dispatch is available by default if you do not supply your own mapDispatchToProps function. If you do supply a mapDispatchToProps function, you are responsible for returning a prop named dispatch yourself.

            mapDispatchProps 가 없을때만 그렇게 사용 가능 하답니다.

          • 방금 확인했는데 위 댓글 링크들이 다 정말 …. 로 짤려있네요
            disqus 오류였나.. ㅋㅋ 다시 수정했어요 !

      • 궁금증

        안녕하세요 벨로퍼트님,

        axios로 ajax 통신을 하는 dispatch 들은 then으로 이벤트가 끝나는 시점에서 이벤트를 발생시키는걸 봐는데요
        일반 action들을 dispatch하고 나서 그 결과에 따라 다음 action을 dispatch하려고 하는데 이때 then 이벤트가 발생하지 않는데요

        this.props.dispatch(addCustomer()).then(() => { this.props.dispatch(다른 엑션) });

        따로 promise패턴을 구현해야 하는지 dispatch할때 다음 action을 콜백 함수로 넘겨 처리할수 있는지 궁금합니다.

  • 조재민

    어떤 부분이 outdated 된건지 알 수 있을까요? 요즘은 이런 방식이 안쓰이는건가요?

    • redux를 사용하는 프로젝트에서 Ajax요청을 할땐 componentDidMount에서 바로 요청하는것보다 redux-thunk, redux-promise-middleware, redux-saga등의 미들웨어를 통해서 작업하는것이 선호되고있습니다.

      시간나면 작성하려고 하는데.. 요새 좀 바쁘네요ㅎㅎㅎ

      redux thunk를 통해서 하는거 한번 검색해보시길 바랍니다.

      • Gyeong Hun Bae

        최근에 redux 홈페이지에서 async-action 부분을 봤는데 Fetch API 로 설명을 해놓았더라구요…! 아직 개발 초보라 대충은 이해한거 같은데 좀 더 쉬운 설명으로 볼 수 있으면 정말 좋을 것 같습니다 ㅎㅎㅎㅎ… 그리고 리액트 강좌 너무 잘 보고 있습니다. 감사합니다!

        • 요즘은 바빠서 강의 업데이트를 잘 못하고있습니다 ;( 시간이 나면 하나하나 업데이트 할 예정입니다 ㅎㅎ

  • Richa

    안녕하세요. axios 관련해서 질문 드려도 될까요?

    제가 지금 특정 사이트의 url을 등록하면 그 사이트의 타이틀과 이미지 정보를 얻어와 저장하려고 하는데요.
    axios.get()을 이용해 http request를 요청하니 cors 에러가 반환되더라고요.

    cors관련 헤더 설정을 해보았지만, 결과에 변화가 없네요.

    크롬 확장 프로그램인 postman을 이용해서 테스트해보면 모든 사이트의 html 정보를 가져와지는데..
    무슨 좋은 방법이 없을까요?

  • RGBplace

    axios가 사용하기 가장 편한거 같아요! ajax 관련해서 하루종일 고민했는데 좋은 정보 알려주셔서 감사합니다.

    요즘 velopert님 블로그 계속 보고있는데 개발 실력도 그렇고 정말 대단하시네요~ 존경스럽습니다.
    국내에 velopert 님 정도의 react 개발자가 있을까 싶습니다

  • SSunn

    안녕하세요 velopert님 좋은 강의에 항상 감사하며 도움을 받고 있습니다!
    다름이 아니라 제가 아직 초보지만 무한스크롤링을 구현하고 싶어 진행 중에 있습니다만(react와 django REST API를 사용 중 입니다!)어려움에 봉착하여 혹시 조언을 얻을 수 있을까 하며 질문을 남겨봅니다

    예를 들어 먼저 ‘물건 보기’ 버튼을 눌러서 axios로 DB에 있는 ‘모든 물건의 DATA’를 받아옵니다. 그런데 이 중에 ‘인테리어소품 / 1kg 미만 / 검정색’ 물건만을 보고 싶으면

    이미 받아 두었던 ‘모든 물건의 DATA’를 가지고 프론트 부분에서 반복문을 써서 원하는 물건의 정보만을 뽑아내는것이 더 효율적인지(시간/속도면에서) 아니면 프론트 부분에 무리가지 않게 이 filter된 정보가 담긴 api를 axios로 받아와서 프론트는 그냥 보여주기만 하는 것이 좋은 방법인지 속도/시간 면에서 어떤게 효율적인지 조언해주실 수 있으신지 여쭤봅니다.

    (제 개인적인 생각으로는 굳이 api를 거치는 것보다 이미 모든 물건의 DATA를 받아 놓았기 때문에 프론트 부분에서 filter하면 더 빠르지 않을까 하다가도 또 만약 모든 물건의 DATA가 굉장히 큰 양이라면 아무래도 프론트 부분에서 처리하는게 부담이 되지 않을까.. 그러면 미리 편집해놓은 api에서 그냥 받아만 와서 보여주는 것이 오히려 빠르고 부담이 적지않을까 하는 생각입니다..)

    두서없는 글 읽어주셔서 정말 감사드립니다. 그리고 조그마한 조언이라도 해주신다면 정말 도움이 될 것 같습니다 🙂

    • 데이터가 몇개있냐에 따라 다를 것 같습니다. 전체 40개 미만인경우엔 모두 다 불러오시고 더 많을것같으면 서버쪽에서 필터링하세요

      근데 일반적으로는 필터링은 서버쪽에서 해요 ㅎㅎ

      • SSunn

        아하 답변 감사드립니다ㅠㅠ!