2-2. 포스트 보여주기

포스트를 읽는 기능을 구현하는 것은, 매우 간단합니다. 우리가 이전에 만든 /api/posts/:id API 를 호출하여 데이터를 받아온뒤 post 모듈이 지닌 상태에 넣어주면 됩니다.

포스트 읽기 API 함수 만들기

우선 포스트를 읽는 API 를 요청하는 함수를 만들어보겠습니다. lib/api.js 파일을 열어서 다음 코드를 추가하세요.

src/lib/api.js

(...)
export const getPost = (id) => axios.get(`/api/posts/${id}`);

이렇게 한 줄이면 됩니다. id 값을 파라미터로 받아와서 API 의 주소의 뒷부분에 넣어주죠.

post 모듈 작성하기

post 모듈에서, 포스트의 정보를 불러오는 액션을 작성하고 상태 관리를 위한 코드를 작성해보세요. 방금 만든 getPost 함수를 불러와서 GET_POST 액션에서 사용하도록 설정하고, 요청이 성공 할 시 상태에 넣어주면 됩니다.

src/store/modules/post.js

import { createAction, handleActions } from 'redux-actions';

import { Map, fromJS } from 'immutable';
import { pender } from 'redux-pender';

import * as api from 'lib/api';

// action types
const GET_POST = 'post/GET_POST';

// action creators
export const getPost = createAction(GET_POST, api.getPost);

// initial state
const initialState = Map({
  post: Map({})
});

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

Post 컴포넌트 만들기

리덕스 스토어에 있는 데이터를 컴포넌트로 전달해주기 위하여, 우리는 Post 라는 컨테이너 컴포넌트를 만들겠습니다. 이 컴포넌트에서는 PostInfo 와 PostBody 컴포넌트를 불러오게 되며, componentDidMount 가 발생 할 때 props 로 받아온 id 를 사용하여 특정 id 를 가진 포스트를 불러옵니다.

렌더링을 하는 부분에서는, 로딩중일 때 아무것도 나타나지 않도록 null 을 반환해주었습니다.

src/containers/post/Post.js

import React, { Component } from 'react';
import PostInfo from 'components/post/PostInfo';
import PostBody from 'components/post/PostBody';
import * as postActions from 'store/modules/post';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';

class Post extends Component {
  initialize = async () => {
    const { PostActions, id } = this.props;
    try {
      await PostActions.getPost(id);
    } catch (e) {
      console.log(e);
    }
  }

  componentDidMount() {
    this.initialize();
  }

  render() {
    const { loading, post } = this.props;

    if(loading) return null; // 로딩중일땐 아무것도 보여주지 않음

    const { title, body, publishedDate, tags } = post.toJS();

    return (
      <div>
        <PostInfo title={title} publishedDate={publishedDate} tags={tags}/>
        <PostBody body={body}/>
      </div>
    )
  }
}

export default connect(
  (state) => ({
    post: state.post.get('post'),
    loading: state.pender.pending['post/GET_POST'] // 로딩상태
  }),
  (dispatch) => ({
    PostActions: bindActionCreators(postActions, dispatch)
  })
)(Post);

이제 이 컴포넌트를 PostPage 에서 렌더링해줍시다. id 값에는 현재 라우트의 id 를 넣어주세요.

import React from 'react';
import PageTemplate from 'components/common/PageTemplate';
import Post from 'containers/post/Post';

const PostPage = ({ match }) => {
  const { id } = match.params;
  return (
    <PageTemplate>
      <Post id={id}/>
    </PageTemplate>
  );
};

export default PostPage;

PostInfo 와 PostBody 에서 올바른 데이터 보여주기

현재 PostInfo 와 PostBody 에서는 props 로 받은 값을 보여주는 것이 아니라 우리가 사전에 설정한 텍스트가 보여지고 있습니다. 텍스트가 들어가는 부분에 우리가 props 로 넣어준 값들을 렌더링 해보세요.

우선, PostInfo 부터 작업을 해볼건데요, 이 컴포넌트에서는 포스트 날짜, 제목, 그리고 태그가 보여지지요. 이 과정에서, 태그는 배열 형태로 받아와서 Link 배열로 변환하여 렌더링해줍니다.

그리고, 날짜를 텍스트형태로 보여주는 부분에서는, 편의를 위하여 moment 라는 라이브러리를 설치하여 사용하겠습니다. 이 라이브러리는 날짜를 다양한 형식으로 텍스트변환을 해줍니다. (참조: https://momentjs.com/)

$ yarn add moment

PostInfo 컴포넌트를 다음과 같이 수정하세요:

src/components/post/PostInfo/PostInfo.js

import React from 'react';
import styles from './PostInfo.scss';
import classNames from 'classnames/bind';

import { Link } from 'react-router-dom';
import moment from 'moment';

const cx = classNames.bind(styles);

const PostInfo = ({ publishedDate, title, tags }) => (
  <div className={cx('post-info')}>
    <div className={cx('info')}>
      <h1>{title}</h1>
      <div className={cx('tags')}>
        {
          // tags 가 존재하는 경우에만 map 을 실행합니다.
          tags && tags.map(
            tag => <Link key={tag} to={`/tag/${tag}`}>#{tag}</Link>
          )
        }
      </div>
      <div className={cx('date')}>{moment(publishedDate).format('ll')}</div>
    </div>
  </div>
);

export default PostInfo;

그 다음에는, PostBody 컴포넌트를 수정하겠습니다. 이 부분은 매우 간단합니다. 우리가 Post 컴포넌트에서 전달해준 body 값을, 우리가 이전에 만들어놓았던 MarkdownRender 컴포넌트를 불러와서 주입해주면 됩니다.

src/components/post/PostBody/PostBody.js

import React from 'react';
import styles from './PostBody.scss';
import classNames from 'classnames/bind';
import MarkdownRender from 'components/common/MarkdownRender';

const cx = classNames.bind(styles);

const PostBody = ({ body }) => (
  <div className={cx('post-body')}>
    <div className={cx('paper')}>
      <MarkdownRender markdown={body}/>
    </div>
  </div>
);

export default PostBody;

자, 이제 포스트를 보여줄 준비가 거의 끝났습니다. 에디터에서 새 글을 작성해보세요. 마크다운이 제대로 렌더링 되었나요?

우리가 작성한 마크다운은 잘 렌더링 되지만, 코드 부분이 아직 색상이 안입혀졌습니다. 그 이유는, 현재 우리가 만들었던 MarkdownRender 컴포넌트에서 Prism.highlightAll() 이 componentDidUpdate 에서만 실행되기 때문입니다.

에디터에서 마크다운이 변경 될 때에는, 하이라이팅이 제대로 호출이 되지만, 지금의 경우처럼 처음부터 마크다운값이 있는 경우에는, componentWillMount 부분에서 마크다운 변환 작업이 일어나서 html 상태가 바뀌어도 componentDidUpdate 가 호출되지 않습니다. 따라서, 이를 해결하기 위해선, componentDidMount 에서도 Prism.highlightAll 을 호출해주면 됩니다.

MarkdownRender 컴포넌트를 열어서 componentDidMount 를 다음과 같이 작성하세요.

src/components/common/MarkdownRender/MarkdownRender.js - componentDidMount

  componentDidMount() {
    Prism.highlightAll();
  }

코드를 저장 후, 다시 페이지를 확인해보세요.

성공적으로 잘 보여졌나요?

results matching ""

    No results matching ""