티스토리 뷰

1. AJAX 비동기 작업 처리

React에서 ajax로 서버에 요청하는 방법을 이야기 할 것입니다. ajax를 요청하는 방법은 axios, jquery 등 여러가지 방법이 있지만, axios 모듈을 이용하여 서버 요청을 해 보겠습니다. 서버와 통신하여 아래와 같은 간단한 예를 만들어 보겠습니다.

AJAX 비동기 작업 처리
AJAX 비동기 작업 처리

1) axios 설치하기

create-react-app을 사용하여 react 프로젝트를 생성합니다. ([React.JS] CodePen, create-react-app으로 React.JS 개발하기 참고)

create-react-app hello-axios
cd hello-axios
npm install --save axios

2) axios 사용하기

axios의 제세한 내용은 https://github.com/mzabriskie/axios 참고 바랍니다.

axios는 get, post, delete, put, head, patch 의 메소드를 붙여 사용할 수 있습니다. 예를 들어 get의 경우는,

axios.get('/users', {
    params: {
      id: 1
    }
  })
  .then(function (response) {
    console.log(response);
  })
  .catch(function (error) {
  });

혹은,

axios({
    method: 'get',
    url: '/users',
    params: {
      id: 1
    }
  })
  .then(function (response) {
    console.log(response);
  })
  .catch(function (error) {
  });

위와 같은 형태로 사용할 수 있습니다. post의 경우는 params 대신 data를 써서 data에 값을 담아 보내면 됩니다.

axios의 리턴 값은 Promise입니다. Promise는 비동기 처리를 깔끔하게 하기 위해서 then 메소드를 제공하는데, Promise 관련 내용은 [자바스크립트] 비동기프로그래밍, ES6(ECMA Script 6) - Promise로 콜백지옥 해결하기를 참고 바랍니다.

요청이 끝나고 받는 response의 형태는 아래와 같습니다.

{
  // `data` 는 서버에서 반환한 데이터입니다. 
  data: {},

  // `status` 는 서버에서 반환한 HTTP 상태입니다
  status: 200,

  // `statusText` 는 HTTP 상태 메시지입니다
  statusText: 'OK',

  // `headers` 는 서버에서 반환한 헤더값입니다
  headers: {},

  // `config` 는 axios 요청시 전달했던 설정값입니다
  config: {}
}

3) API 함수 모듈화 하기

axios를 사용하여 ajax를 요청하는 함수들을 scr/services에 모아 모듈화 하겠습니다.

src/services/posts.js

import axios from 'axios';

export function getTitle(id) {
    return axios.get('http://localhost:5000/posts/title/' + id);
}

export function getContent(id) {
    return axios.get('http://localhost:5000/posts/content/' + id);    
}
  • 1번 줄, axios를 import 합니다.
  • 3~5번 줄, title을 가져오도록 서버에 요청하는 api입니다.
  • 7~9번 줄, content를 가져오도록 서버에 요청하는 api입니다.

title과 content를 요청하는 2개의 api입니다. 굳이 2개의 api를 나눠서 만들 필요는 없지만, 여러개의 api를 호출하는 예를 만들기 위해 2개의 api를 나누어 만들었습니다.

4. component 구조

src/components/Button.js

import React, { Component } from 'react';

class Button extends Component {
    render() {
        return (
            <div>
                <button onClick={this.props.onDecrement}>PREV</button>
                <button onClick={this.props.onIncrement}>NEXT</button>
            </div>
        );
    }
}

export default Button;
  • 7~8번 줄, 클릭 될 때 호출되는 이벤트 핸들러를 등록합니다. PREV 클릭시 id를 1만큼 줄이고, NEXT 클릭시 id를 1만큼 증가시킵니다.

App.js

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';

import Button from './components/Button';
import * as service from './services/posts';

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      id: 0,
      title: '',
    };
  }

  onIncrement = (event) => {
    this.fetchUserInfo(this.state.id + 1);
  }

  onDecrement = (event) => {
    this.fetchUserInfo(this.state.id - 1);
  }

  fetchUserInfo = async (id) => {
    const info = await Promise.all([
      service.getTitle(id),
      service.getContent(id)
    ]);

    this.setState(prevState => ({
      id: id,
      title: info[0].data.title,
      content: info[1].data.content
    }));
  }

  render() {
    return (
      <div>
        <h1>{this.state.title}</h1>
        <p>{this.state.content}</p>
        <Button onIncrement={this.onIncrement} onDecrement={this.onDecrement} />
      </div>
    );
  }
}

export default App;
  • 5번 줄, Button 컴포넌트를 import 합니다.
  • 6번 줄, 서버에 요청하는 모듈을 import 합니다.
  • 17~19번 줄, NEXT를 클릭 할 경우 실행되는 이벤트 핸들러 입니다. id를 1증가 시킨 값으로 서버에 요청합니다.
  • 21~23번 줄, PREV를 클릭 할 경우 실행되는 이벤트 핸들러 입니다. id를 1감소 시킨 값으로 서버에 요청합니다.
  • 26~29번 줄, 서버에 api를 호출 합니다.
    Promise.all 메소드를 하용하여([자바스크립트] 비동기프로그래밍, ES6(ECMA Script 6) - Promise로 콜백지옥 해결하기 참고) getTitle과 getContent가 모두 실행 됩니다.
    async/await를 사용하여 비동기 작업을 동기 작업인 것처럼 코딩을 가능하도록 합니다. async는 비동기 작업을 하도록 하고, await는 비동기 작업인 Promise를 기다립니다.
  • 31~35번 줄, id와 서버에서 전달 받은 title, content를 state에 저장합니다.
  • 41번 줄, 서버에서 가져온 title을 출력합니다.
  • 42번 줄, 서버에서 가져온 content를 출력합니다.

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import './index.css';

import Promise from 'promise-polyfill'; 

if (!window.Promise) {
  window.Promise = Promise;
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
);
  • 6~10번 줄, 이전 버전의 브라우저 호환을 맞추기 위해 promise-plolyfill 모듈을 사용하였습니다.

Promise는 ES6에서 추가되었습니다. 그렇기 때문에 ES6를 지원하지 않는 옛 버전의 브라우저에서는 Promise가 동작하지 않게 됩니다. 하위 버전의 브라우저에서 Promise를 사용하기 위해 (하위 호환을 맞추기 위해), promise-polyfill 모듈을 사용하였습니다.

npm install --save promise-polyfill

promise-polyfill을 npm을 통해 받아서 사용해야 합니다.

5) React 코드 확인

위의 코드는 https://github.com/beomy/hello-axios에서 확인 할 수 있습니다.

npm start로 실행하면 3000번 포트로 실행되어야 합니다.

1. Express 서버 연동

React와 관련 없는 내용이지만, React 프로젝트와 Express로 구현한 서버 연동에 대해 이야기 하겠습니다. 이후의 내용은 Express 관련된 내용입니다.

1) express-generator 설치

create-react-app과 같이 express도 express-generator를 사용하면 편리하게 express 프로젝트를 만들 수 있습니다.

npm install -g express-generator

2) express 프로젝트 만들기

express axios-api
cd axios-api
npm install

위의 3줄이면 express 프로젝트 생성이 가능합니다.

3) 포트 바꾸기

...

/**
 * Get port from environment and store in Express.
 */

var port = normalizePort(process.env.PORT || '5000');
app.set('port', port);

...

React 프로젝트의 포트가 3000번 이기 때문에, express 프로젝트 포트를 5000번으로 바꿔 포트 충돌이 발생하지 않도록 해야 합니다.

4) 서버 api 만들기

routes/posts.js

var express = require('express');
var router = express.Router();

router.get('/title/:id', function(req, res, next) {
  res.json({title: "title-" + req.params.id});
});

router.get('/content/:id', function(req, res, next) {
  res.json({content: "content-" + req.params.id});
});

module.exports = router;
  • 4~6번 줄, /posts/title/:id로 들어올 경우 호출되는 부분입니다. JSON 형태로 title을 보냅니다.
  • 8~10번 줄, /posts/content/:id로 들어올 경우 호출되는 부분입니다. JSON 형태로 content를 보냅닙니다.

api를 반환할 부분을 만들었고, 라우팅 선언 부분입니다.

App.js

...

var posts = require('./routes/posts');

...

app.use('/posts', posts);

...

라우팅이 설정된 부분을 위와 같이 수정합니다.

5) CORS 이슈 해결하기

이제 마지막 단계입니다. 이제까지 설명한 대로 React와 Expree를 동작시킨다면 아래와 같은 에러를 확인 할 수 있습니다.

CORS 이슈
CORS 이슈

서버 api를 호출 할 때, Acess-Control-Allow-Origin 에러가 발생하고 동작하지 않는 것을 확인 할 수 있습니다.

크로스 브라우저가 가능하도록 라우팅 설정을 해야 합니다.

App.js

...

var posts = require('./routes/posts');

...

app.use(function(req, res, next) {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
  next();
});

app.use('/posts', posts);

...

최종 App.js는 위의 코드와 같습니다.

6) Express 코드 확인

위의 코드는 https://github.com/beomy/axios-api에서 확인 할 수 있습니다.

npm start로 실행하면 5000번 포트로 실행 되어야 합니다.

React 프로젝트와 Express 프로젝트가 동시에 실행되어야 합니다. 두개의 터미널을 띄우고 각각 터미널에서 React와 Express 프로젝트를 실행시키면 됩니다.

참고

댓글
공지사항
최근에 올라온 글