2-1. 포스트 작성하기

드디어, 서버에 API 를 요청 할 차례가 되었습니다. 현재 백엔드 서버는 4000 포트로 열려있고, 개발 서버는 3000포트로 열려있습니다. 따라서 API 요청을 하려면 http://localhost:4000/api/posts 이런 주소로 요청을 직접 넣어줘야 하는데요, 이 방식은 잘못된 방법입니다. 그 이유는, 나중에 이 프로젝트를 배포하게 되면 호스트도 변경 될 것이기 때문입니다. API 를 요청 할 때, 리액트 앱이 제공된 서버와 동일한 서버에 요청을 하려면, /api/posts 주소로 요청을 하면 되는데요, 현재는 개발서버에서 리액트 애플리케이션이 제공되고 있으니 제대로 작동하지 않게 됩니다.

Proxy 설정

이러한 경우에는, webpack 의 proxy 기능을 사용하면, 개발서버로 들어온 요청을 백엔드 서버에 전달해주고, 응답을 그대로 반환 할 수 있습니다.

proxy 설정은, webpack 설정에서 해줄 수도 있고, create-react-app 으로 만든 프로젝트에서는 package.json 에 명시해주면 됩니다.

package.json 파일을 열어서 마지막 부분에 다음과 같이 코드를 입력해주세요.

package.json

{
  (...)
  "proxy": "http://localhost:4000"
}

이제, 웹팩 개발서버로 REST API 요청을 하게 되면, 프록시를 통하여 백엔드 서버로 요청하고, 응답도 받게 됩니다. 프록시를 설정하고 난 다음엔, 개발 서버를 다시 시작해야 설정이 적용됩니다.

axios 설치

REST API 웹 요청을 프로미스 기반으로 간편하게 할 수있게 해주는 라이브러리 axios 를 설치하세요.

$ yarn add axios

글 작성 API 함수 작성

클라이언트에서 API 를 호출하는 함수를 작성하세요. 앞으로 사용 할 REST API 함수들은 모두 src/lib/api.js 에 함수를 생성하게 됩니다. 우선, writePost 함수를 만들고 내보내세요.

src/lib/api.js

import axios from 'axios';

export const writePost = ({title, body, tags}) => axios.post('/api/posts', { title, body, tags });

editor 모듈에 WRITE_POST 액션 생성

위 함수를 액션화 해봅시다. WRITE_POST 라는 액션 이름을 만들고, 이를 위한 액션 생성 함수를 만드세요. API 요청이 성공하였을 때, 서버에서 응답하는 _id 값의 editor 모듈의 postId 값에 넣겠습니다.

src/store/modules/editor.js

(...)
import * as api from 'lib/api';

// action types
(...)
const WRITE_POST = 'editor/WRITE_POST';

// action creators
(...)
export const writePost = createAction(WRITE_POST, api.writePost);

// initial state
const initialState = Map({
  (...)
  postId: null
});

// reducer
export default handleActions({
  (...),
  ...pender({
    type: WRITE_POST,
    onSuccess: (state, action) => {
      const { _id } = action.payload.data;
      return state.set('postId', _id);
    }
  })
}, initialState)

EditorHeaderContainer 컴포넌트 만들기

EditorHeader 컴포넌트에, 리덕스의 상태와 액션 생성 함수를 붙여줍시다. 왼쪽의 뒤로가기 버튼과, 우측의 글쓰기 버튼에 기능을 붙여주겠습니다. 왼쪽 버튼이 클릭 됐을 땐, 리액트 라우터에서 뒤로가기를 하게 해주는 history 객체의 goBack 함수를 호출하겠습니다. 이 과정에서, 해당 컴포넌트에서 리액트 라우터가 전달해주는 props 값을 받아오기 위하여 withRouter 를 불러와서 컴포넌트를 내보낼 때 감싸주겠습니다. 현재 컴포넌트가 리덕스와의 상태 연결을 위하여 connect 함수로 감싸져있는데, connect 와 withRouter 가 중첩되어도 무방합니다.

우측의 버튼에선, 글쓰기 액션을 발생시킨 뒤, postId 값을 받아와서 포스트 주소로 이동시켜줍니다.,

그리고, componentDidMount 가 발생할 때 INITIALIZE 액션을 실행시켜서 에디터의 상태를 초기화 하세요. 초기화를 하지 않으면 이전에 작성하던 상태가 남아있게 됩니다.

src/containers/editor/EditorHeaderContainer.js

import React, { Component } from 'react';
import EditorHeader from 'components/editor/EditorHeader';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { withRouter } from 'react-router-dom';

import * as editorActions from 'store/modules/editor';

class EditorHeaderContainer extends Component {
  componentDidMount() {
    const { EditorActions } = this.props;
    EditorActions.initialize(); // 에디터를 초기화 합니다.
  }

  handleGoBack = () => {
    const { history } = this.props;
    history.goBack();
  }

  handleSubmit = async () => {
    const { title, markdown, tags, EditorActions, history } = this.props;
    const post = {
      title,
      body: markdown,
      // 태그 텍스트를 , 로 분리시키고 앞뒤 공백을 지운 후 중복 되는 값을 제거해줍니다.
      tags: tags === "" ? [] : [...new Set(tags.split(',').map(tag => tag.trim()))]
    };
    try {
      await EditorActions.writePost(post);
      // 페이지를 이동시킵니다. 주의: postId 를 상단에서 레퍼런스를 만들지 않고
      // 이 자리에서 this.props.postId 를 조회해주어야합니다. (현재의 값을 불러오기 위함)
      history.push(`/post/${this.props.postId}`);
    } catch (e) {
      console.log(e);
    }
  }


  render() {
    const { handleGoBack, handleSubmit } = this;

    return (
      <EditorHeader
        onGoBack={handleGoBack}
        onSubmit={handleSubmit}
      />
    );
  }
}

export default connect(
  (state) => ({
    title: state.editor.get('title'),
    markdown: state.editor.get('markdown'),
    tags: state.editor.get('tags'),
    postId: state.editor.get('postId')
  }),
  (dispatch) => ({
    EditorActions: bindActionCreators(editorActions, dispatch)
  })
)(withRouter(EditorHeaderContainer));

컨테이너 컴포넌트를 다 만들었다면, EditorPage 를 열어서 기존의 EditorHeader 를 EditorHeaderContainer 로 바꾸세요.

src/pages/EditorPage.js

import React from 'react';
import EditorTemplate from 'components/editor/EditorTemplate';
import EditorHeaderContainer from 'containers/editor/EditorHeaderContainer';
import EditorPaneContainer from 'containers/editor/EditorPaneContainer';
import PreviewPaneContainer from 'containers/editor/PreviewPaneContainer';

const EditorPage = () => {
  return (
    <EditorTemplate
      header={<EditorHeaderContainer/>}
      editor={<EditorPaneContainer/>}
      preview={<PreviewPaneContainer/>}
    />
  );
};

export default EditorPage;

이제 포스트 작성 기능이 완료되었습니다. 에디터 페이지를 열어서 뒤로가기 버튼을 눌러보세요. 페이지 전환이 일어났나요? 잘 됐다면, 다시 에디터를 열어서 제목, 내용, 태그를 입력하고 작성하기 버튼을 눌러보세요. 포스트 페이지로 페이지가 전환 될 것입니다. 하지만, 아직 포스트를 읽는 API 가 구현되지 않았기 때문에 화면에는 기본값만 나타나게 됩니다. 이제 포스트를 보여주는 기능을 구현해봅시다.

results matching ""

    No results matching ""