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();
}
코드를 저장 후, 다시 페이지를 확인해보세요.

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