목차 돌아가기: binaryjourney.tistory.com/pages/ReactCRUD-create-board-tutorial
화면 이동이 해결되었으니 이제 내가 등록한 글의 내용이 화면에 나타나는 것을 구현해보도록 해보자
첫번째로 articleSlice에 서버에서 내용을 불러오는 부분과 불러온 내용을 알맞은 형식으로 바꿔주는 액션을 만들어준다.
(registerArticleAsync 는 쓸 일이 없을 것 같아 지워버렸다)
// articleSlice.js
export const articleSlice = createSlice({
name: "article",
initialState: {
id: 0,
title: "",
content: "",
views: 0,
date: new Date(Date.now()),
editDate: new Date(Date.now()),
},
reducers: {
registerArticle: (state, { payload: article }) => {
console.log(article); // saga에서 감시용
},
getArticle: (state, { payload: id }) => {
console.log(id); // saga에서 감시용
},
getArticleAsync: (state, { payload: article }) => {
console.log(article); // saga에서 호출용
return {
...state,
id: article.id,
title: article.title,
content: article.content,
date: article.date,
editDate: article.editDate,
views: article.views,
};
},
},
});
getArticle은 rootSaga에서 게시글 내용 조회 액션이 dispatch됐을 때 그 액션을 catch하기 위해 만든 것이고
실질적으로 불러온 내용을 뿌려주는 것은 아래 getArticleAsync이다.
그리고 articleSaga로 가서 getArticle 액션이 dispatch 됐을 때 호출시킬 함수를 만든다.
// articleSaga.js
import { put } from "redux-saga/effects";
export function* getArticleAsync(action) {
const id = action.payload;
const response = yield Axios.get(`http://localhost:4000/board/${id}`);
console.log(response.data);
yield put(articleActions.getArticleAsync(response.data));
}
위 코드를 보면 getArticle 액션을 가로채서 payload로 보낸 id로 게시글을 조회해온다.
이 때 Axios.get 을 이용한다.
json-server로 board 안의 id가 1인 데이터 객체을 가져올 것이다.
그리고 조회해온 내용(response.data)을 getArticleAsynce 액션 호출(put)의 payload로 같이 보내준다.
이제 rootSaga에 getArticle 감시 코드를 만들자
// rootSaga.js
import { takeEvery, takeLatest } from "redux-saga/effects";
import { articleActions } from "../slice/articleSlice";
import { registerArticleAsync, getArticleAsync } from "./articleSaga";
const { registerArticle, getArticle } = articleActions;
export default function* rootWatcher() {
yield takeLatest(registerArticle.type, registerArticleAsync);
yield takeEvery(getArticle.type, getArticleAsync);
}
여기까지 됐다면 이제 view인 ArticlePage에서 화면 조회 부분을 추가하자.
// ArticlePage.js
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { articleActions } from "../../../slice/articleSlice";
function ArticlePage({ match, location }) {
const dispatch = useDispatch();
useEffect(() => {
dispatch(articleActions.getArticle(match.params.articleId));
}, [match.params.articleId]);
const { id, title, content } = useSelector((state) => ({
id: state.articleReducers.id,
title: state.articleReducers.title,
content: state.articleReducers.content,
}));
const date = useSelector((state) => state.articleReducers.date);
const views = useSelector((state) => state.articleReducers.views);
return (
<div>
ArticlePage
<br />
글번호 : {id}
<br />
제목 : {title}
<br />
내용: {content}
<br />
{/* {date} */}
<br />
조회수: {views}
</div>
);
}
export default ArticlePage;
useEffect로 게시글 id가 바뀔 때마다 dispatch로 id를 보내 내용을 조회하도록 만들었다.
(이는 나중에 조회수 증가에도 쓰일 예정이다.)
그리고 articleSlice reducer에서 바뀐 state를 들고와야 하므로 useSelector를 사용해줬다.
date 부분은 자꾸 오류나서 일단 제외했다.
id, title, content, views 라도 잘 보이는지 글을 등록해보고 화면을 확인해보자.
redux-toolkit의 편한 점은 reducer 에서 상태관리 할 때 ...state 나 state.concat 과 같이 원본 값을 복제해서 새로운 내용을 반영하는 기존 방법과는 달리 바로 추가(push) 해도 된다는 점이다.
articleSlice.js 에서 getArticleAsync 액션함수의 return 값을 다음과 같이 수정하면
앞에 ...staate를 지웠기 때문에
아래처럼 로딩 직전에 initialState가 잠깐 뜨는 경우가 없을 것이라 생각되지만
여전히 있다!
바로 써줘도 redux-toolkit에서는 문제가 없다.
=> 조회수 state 초기화가 되지 않아 조회수가 0부터 시작하지 않게 된다. ...state 써줘야 한다.
부정확한 정보는 취소선처리한다.
유지보수하기 쉽도록 ArticlePage를 컨테이너 컴포넌트로 사용하고 프레젠테이셔널 컴포넌트로 ArticleDetail.js 파일을 추가하겠다.
프레젠테이셔널 컴포넌트인 ArticleDetail 컴포넌트에는 ArticlePage의 return 부분만 긁어와서 props만 추가해주면 된다.
// ArticleDetail.js
import React from "react";
function ArticleDetail(props) {
return (
<div>
<div>
ArticlePage
<br />
글번호 : {props.id}
<br />
제목 : {props.title}
<br />
내용: {props.content}
<br />
{/* {date} */}
<br />
조회수: {props.views}
</div>
</div>
);
}
export default ArticleDetail;
ArticlePage 컴포넌트에서 달라진 부분은 다음과 같다.
// ArticlePage
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { articleActions } from "../../../slice/articleSlice";
import ArticleDetail from "./Sections/ArticleDetail"; // 추가된 부분
function ArticlePage({ match, location }) {
console.log(match.params.articleId);
const dispatch = useDispatch();
useEffect(() => {
dispatch(articleActions.getArticle(match.params.articleId));
}, [match.params.articleId]);
const { id, title, content } = useSelector((state) => ({
id: state.articleReducers.id,
title: state.articleReducers.title,
content: state.articleReducers.content,
}));
const date = useSelector((state) => state.articleReducers.date);
const views = useSelector((state) => state.articleReducers.views);
return ( // 수정된 부분
<div>
<ArticleDetail
id={id}
title={title}
content={content}
views={views}
date={date}
/>
</div>
);
}
export default ArticlePage;
이제 게시글 화면을 조금 게시판스럽게(?) 바꿔줄 차례다.
// import React from "react";
function ArticleDetail(props) {
return (
<div style={{ width: "80%", margin: "3rem auto" }}>
<div style={{ textAlign: "center" }}>
<h1>게시글</h1>
</div>
<div>
<table>
<colgroup>
<col width="10%" />
<col width="40%" />
<col width="10%" />
<col width="40%" />
</colgroup>
<tr>
<th>번호</th>
<td>{props.id}</td>
<th>조회수</th>
<td>{props.views}</td>
</tr>
<tr>
<th>제목</th>
<td colspan="3">{props.title}</td>
</tr>
<tr>
<th>내용</th>
<td colspan="3">{props.content}</td>
</tr>
</table>
</div>
</div>
);
}
export default ArticleDetail;
아주 기본이긴 하지만 그래도 알아볼만큼 형태가 잡혔다.
그리고 이번주 내로 github에 올려 소스를 공유할 계획이다.
이번주까지 기획한 대로 잘 진행되면 좋겠다.
목차 돌아가기: binaryjourney.tistory.com/pages/ReactCRUD-create-board-tutorial