전체적인 사용 스펙은 전의 사용했던 스펙과 동일하다.
페이징으로 불러온 리스트를 보여준다.
맨 아래 있는 페이징 컴포넌트를 통해, 게시판 페이지 이동이 가능하다.
리스트의 제목을 클릭하면 게시물 개별 화면으로 이동하게 된다.
맨 아래 게시물 작성 기능 또한 추가해 놓는다.
단일 게시물 한개를 보여준다.
수정과 삭제도 가능하다.
화면적 구성으로는 게시물 아래, 추천과 댓글이 위치해있다.
게시물과 댓글 사이에 화면상 위치해있으며, 추천 수 조회와, 추천, 추천 취소(삭제)가 가능하다.
추천 아래에 위치해있으며, 댓글 조회와, 댓글 작성, 댓글 삭제가 가능하다.
react-router-dom
으로 구성한 pages
컴포넌트를 중심으로 작동 구조를 파악해보자.
만들어낼 pages
컴포넌트는 총 4개이며, 화면 또한 총 4개로 구성된다.
먼저 페이징 된 게시판을 보여주는 ArticleListPage.tsx
와 개별 게시물 하나를 보여주는 ArticleOnePage.tsx
이다.
ArticleListPage.tsx
는 ArticleList.tsx
하나로 구성되어 있으며, ArticleList.tsx
는 페이징 이동 컴포넌트인 Paging.tsx
를 가지고 있다.
ArticleOnePage.tsx
는 게시물 자체만을 보여주는 ArticleOne.tsx
와 Recommend.tsx
, Comment.tsx
로 구성되어 있다. 이 중 ArticleOne.tsx
는 삭제, 수정 등의 버튼을 제외한 게시물만의 정보를 보여주는 Article.tsx
를 소유하고 있다.
ArticleList.tsx
의 게시물 가운데 하나의 제목을 누르게 되면 자동으로 ArticleOnePage.tsx
로 이동하게 되어 페이지를 보여주게 된다.
두개의 page
컴포넌트 모두, useParams
를 통해 id
를 포함하고 있는 컴포넌트에 주입하며, 각각 컴포넌트는 이를 기반으로 컴포넌트 내에 있는 함수들을 실행하여, CRUD
를 수행한다.
나머지 2개의 컴포넌트는 CreateArticlePage.tsx
와 UpdateArticlePage.tsx
이며, CreateArticleForm.tsx
이라는 하나의 컴포넌트로 이 2개의 page
컴포넌트를 수행해낸다.
CreateArticlePage.tsx
는 말 그래도, 게시물을 생성하는 것이기 때문에, 서버로부터 받아와야할 데이터가 없지만, UpdateArticlePage.tsx
는 이전의 게시물을 수정하는 것이기 때문에, useParams
를 통한 articleId
를 통해 컴포넌트 내에 있는 함수를 실행하여, 이전의 게시물 데이터를 불러온다.
이전 글에서도 말했듯이, 나는 로그인 로직을 짤 때, 구조가 많이 반복되어 있었기 때문에 Fetch에 관한 부분은 따로 ts
파일을 만들어 추상화 했으며
side effect를 불러일으킬 수 있는 action들을 따로 분리하여 ts파일로 만들어, 이후 Context API
에서 action
을 함수로 호출하여 전역상태와 연결하는 방식으로 로직을 구현했다.
게시판 로직도 마찬가지의 방식으로 구현했다.
/store/article-action.ts
import { GET, POST, PUT, DELETE } from "./fetch-action";
interface PostArticle {
id? : string,
title: string,
body: string
}
const createTokenHeader = (token:string) => {
return {
headers: {
'Authorization': 'Bearer ' + token
}
}
}
export const getPageList = (param:string) => {
const URL = '/article/page?page=' + param;
const response = GET(URL, {});
return response;
}
export const getOneArticle = (param:string, token?:string) => {
const URL= '/article/one?id=' + param;
if (!token) {
const response = GET(URL, {});
return response;
} else {
const response = GET(URL, createTokenHeader(token));
return response;
}
};
export const makeArticle = (token:string, article:PostArticle) => {
const URL = '/article/';
const response = POST(URL, article, createTokenHeader(token));
return response;
};
export const getChangeArticle = (token:string, param:string) => {
const URL = '/article/change?id=' + param;
const response = GET(URL, createTokenHeader(token));
return response;
};
export const changeArticle = (token:string, article:PostArticle) => {
const URL = '/article/';
const response = PUT(URL, article, createTokenHeader(token));
return response;
};
export const deleteArticle = (token:string, param:string) => {
const URL = '/article/one?id=' + param;
const response = DELETE(URL, createTokenHeader(token));
return response;
}
하나하나 설명을 하자면, 먼저 PostArticle
은 주어진 데이터가 워낙 많이 쓰이기 때문에 하나의 interface
로 만들어서, 변수가 인터페이스를 타입으로 쓰는 방식으로 로직을 단순화 했다.
createTokenHeader
는 이전에서도 사용했듯이, 토큰을 만들기 위한 함수다.
getPageList
는 원하는 페이지가 string으로 주어지면, 그것을 바탕으로 URL을 만들어서 서버에 요청하여 데이터를 받는 함수다.
getOneArticle
또한 마찬가지의 로직이지만, 수정/삭제 와 같은 버튼이 표기되어야 하는가 아닌가의 여부가 정해져야 하므로, 토큰을 넣을 수 있게 함수를 만들었다.
makeArticle
은 게시물을 만드는 함수다. article
이라는 매개변수가 PostArticle
이라는 인터페이스를 타입으로 사용한다. 토큰값이 필요하다.
getChangeArticle
은 수정하기 위해, 이전에 쓰여졌던 데이터를 불러오는 함수다. 게시물의 id와 토큰값이 필요하다.
changeArticle
은 수정된 게시물을 서버에 등록하는 함수다. 게시물의 id와 토큰값이 필요하다.
deleteArticle
은 게시물을 삭제하기 위한 함수다. 게시물의 id와 토큰값이 필요하다.
/store/recommend-action.ts
import { GET, POST, DELETE } from "./fetch-action";
const createTokenHeader = (token:string) => {
return {
headers: {
'Authorization': 'Bearer ' + token
}
}
}
export const getRecommends = (param:string, token?:string) => {
const URL = '/recommend/list?id=' + param;
const response = (token ? GET(URL, createTokenHeader(token)) : GET(URL, {}));
return response;
}
export const makeRecommend = async (id_str:string, token:string) => {
const URL = '/recommend/'
const id = +id_str
const response = POST(URL, { id:id }, createTokenHeader(token));
return response;
}
export const deleteRecommend = (param:string, token:string) => {
const URL = '/recommend/one?id=' + param;
const response = DELETE(URL, createTokenHeader(token));
return response;
}
추천 위와 비슷한 로직으로 되어있다.
getRecommends
는 추천의 숫자와 추천여부를 조회하는 함수이며, token
여부에 따라 다른 메소드를 보내며, article
의 id를 매개변수로 가진다.
makeRecommend
는 추천을 생성하는 함수로, 마찬가지로 article
의 id를 매개변수로 가지며, token이 포함되어있다.
deleteRecommend
는 추천을 삭제하는 함수로, 마찬가지로 article
의 id를 매개변수로 가지며, token이 포함되어있다.
/store/comment-action.ts
import { GET, POST, DELETE } from "./fetch-action";
type Comment = {
articleId: string,
body: string
}
const createTokenHeader = (token:string) => {
return {
headers: {
'Authorization': 'Bearer ' + token
}
}
}
export const getComments = (param:string, token?:string) => {
const URL = '/comment/list?id=' + param;
const response = (token ? GET(URL, createTokenHeader(token)) : GET(URL, {}));
return response;
}
export const makeComment = (comment:Comment, token:string) => {
const URL = '/comment/'
const response = POST(URL, comment, createTokenHeader(token));
return response;
}
export const deleteComment = (param:string, token:string) => {
const URL = '/comment/one?id=' + param;
const response = DELETE(URL, createTokenHeader(token));
return response;
}
댓글도 Article
을 interface
로 타입을 설정한것 처럼, Comment
을 type
객체로 만들어서, 매개변수의 type
으로 지정했다.
getComments
은 댓글 조회함수이며, token
여부에 따라 다른 메소드를 보내며, article
의 id를 매개변수로 가진다.
makeComment
는 추천을 생성하는 함수로, 마찬가지로 article
의 id를 매개변수로 가지며, token이 포함되어있다.
deleteComment
는 추천을 삭제하는 함수로, 마찬가지로 article
의 id를 매개변수로 가지며, token이 포함되어있다.
Context에 있어서는 구조가 이전의 auth-context.tsx
와 다르다.
왜냐하면, 기본적으로 전역상태에 놓이는 게시물, 댓글, 추천등은 객체 형식으로 이루어져 있기 때문에 재사용성을 위해 따로 type으로 분리해놓기 때문이다.
그러나 type으로 분리해놓으면 createContext
를 통해, 전역상태의 기본값을 설정하는 객체에서, state의 value를 type으로 지정할 수 없게 된다.
따라서 새로운 interface를 생성하여, 기본적인 값을 세팅한 다음, createContext
의 제네릭 값을 해당 interface로 설정하는 방식으로 구조를 바꾸었다.
/store/article-context.ts
import React, { useState, useEffect, useCallback, useRef } from "react";
import * as articleAction from './article-action';
type Props = { children?: React.ReactNode }
type ArticleInfo = {
articleId: number,
memberNickname: string,
articleTitle: string,
articleBody: string,
cratedAt: string,
updatedAt?: string,
isWritten?: boolean
};
interface PostArticle {
id? : string,
title: string,
body: string
}
interface Ctx {
article?: ArticleInfo | undefined;
page: ArticleInfo[];
isSuccess: boolean;
isGetUpdateSuccess: boolean;
totalPages: number;
getPageList: (pageId: string) => void;
getArticle: (param:string, token?:string) => void;
createArticle: (article:PostArticle, token:string) => void;
getUpdateArticle: (token:string, param:string) => void;
updateArticle: (token:string, article:PostArticle) => void;
deleteArticle: (token:string, param:string) => void;
}
const ArticleContext = React.createContext<Ctx>({
article: undefined,
page: [],
isSuccess: false,
isGetUpdateSuccess: false,
totalPages: 0,
getPageList: () => {},
getArticle: ()=>{},
createArticle: ()=>{},
getUpdateArticle: ()=>{},
updateArticle: ()=>{},
deleteArticle: ()=>{}
});
export const ArticleContextProvider:React.FC<Props> = (props) => {
const [article, setArticle] = useState<ArticleInfo>();
const [page, setPage] = useState<ArticleInfo[]>([]);
const [totalPages, setTotalPages] = useState<number>(0);
const [isSuccess, setIsSuccess] = useState<boolean>(false);
const [isGetUpdateSuccess, setIsGetUpdateSuccess] = useState<boolean>(false);
const getPageHandlerV2 = async (pageId: string) => {
setIsSuccess(false);
const data = await articleAction.getPageList(pageId);
const page:ArticleInfo[] = data?.data.content;
const pages:number = data?.data.totalPages;
setPage(page);
setTotalPages(pages);
setIsSuccess(true);
}
const getArticleHandler = (param:string, token?:string) => {
setIsSuccess(false);
const data = (token ?
articleAction.getOneArticle(param, token)
: articleAction.getOneArticle(param))
data.then((result) => {
if (result !== null) {
const article:ArticleInfo = result.data;
setArticle(article);
}
})
setIsSuccess(true);
}
const createArticleHandler = (article:PostArticle, token:string) => {
setIsSuccess(false);
const data = articleAction.makeArticle(token, article);
data.then((result) => {
if (result !== null) {
console.log(isSuccess);
}
})
setIsSuccess(true);
}
const getUpdateArticleHancler = async (token:string, param:string) => {
setIsGetUpdateSuccess(false);
const updateData = await articleAction.getChangeArticle(token, param);
const article:ArticleInfo = updateData?.data;
setArticle(article);
setIsGetUpdateSuccess(true);
}
const updateArticleHandler = (token:string, article:PostArticle) => {
setIsSuccess(false);
console.log('update api start')
const data = articleAction.changeArticle(token, article);
data.then((result) => {
if (result !== null) {
}
})
setIsSuccess(true);
}
const deleteArticleHandler = (token:string, param:string) => {
setIsSuccess(false);
const data = articleAction.deleteArticle(token, param);
data.then((result) => {
if (result !== null) {
}
})
setIsSuccess(true);
}
const contextValue:Ctx = {
article,
page,
isSuccess,
isGetUpdateSuccess,
totalPages,
getPageList: getPageHandlerV2,
getArticle: getArticleHandler,
createArticle: createArticleHandler,
getUpdateArticle: getUpdateArticleHancler,
updateArticle: updateArticleHandler,
deleteArticle: deleteArticleHandler
}
return (
<ArticleContext.Provider value={contextValue}>
{props.children}
</ArticleContext.Provider>)
}
export default ArticleContext;
하나하나 살펴보도록 하자.
먼저 Props
에 관한 부분은 지난 글에 다뤘기 때문에 생략한다.
type ArticleInfo = {
articleId: number,
memberNickname: string,
articleTitle: string,
articleBody: string,
cratedAt: string,
updatedAt?: string,
isWritten?: boolean
};
interface PostArticle {
id? : string,
title: string,
body: string
}
ArticleInfo
는 Context
뿐만 아니라 다른 컴포넌트에서도 많이 쓰이게 되는 type이며, PostArticle
은 컴포넌트로부터, 게시물의 수정이나 삭제를 위해 받는 인터페이스다.
interface Ctx {
article?: ArticleInfo | undefined;
page: ArticleInfo[];
isSuccess: boolean;
isGetUpdateSuccess: boolean;
totalPages: number;
getPageList: (pageId: string) => void;
getArticle: (param:string, token?:string) => void;
createArticle: (article:PostArticle, token:string) => void;
getUpdateArticle: (token:string, param:string) => void;
updateArticle: (token:string, article:PostArticle) => void;
deleteArticle: (token:string, param:string) => void;
}
const ArticleContext = React.createContext<Ctx>({
article: undefined,
page: [],
isSuccess: false,
isGetUpdateSuccess: false,
totalPages: 0,
getPageList: () => {},
getArticle: ()=>{},
createArticle: ()=>{},
getUpdateArticle: ()=>{},
updateArticle: ()=>{},
deleteArticle: ()=>{}
});
Ctx
는 앞서 말했듯이, 객체를 전역상태로 돌리기 위한 인터페이스며, 그것을 ArticleContext
가 createContext
의 제네릭값으로 삼았다.
그리고 Ctx
안에서 article
의 type을 ArticleInfo | undefined
으로 설정해놨기 때문에 객체 안의 article
의 value를 undefined
로 삼을 수 있게 되었다.
상세한 값들은 Provider
에서 설명하도록 한다.
export const ArticleContextProvider:React.FC<Props> = (props) => {
const [article, setArticle] = useState<ArticleInfo>();
const [page, setPage] = useState<ArticleInfo[]>([]);
const [totalPages, setTotalPages] = useState<number>(0);
const [isSuccess, setIsSuccess] = useState<boolean>(false);
const [isGetUpdateSuccess, setIsGetUpdateSuccess] = useState<boolean>(false);
...
const contextValue:Ctx = {
article,
page,
isSuccess,
isGetUpdateSuccess,
totalPages,
getPageList: getPageHandlerV2,
getArticle: getArticleHandler,
createArticle: createArticleHandler,
getUpdateArticle: getUpdateArticleHancler,
updateArticle: updateArticleHandler,
deleteArticle: deleteArticleHandler
}
return (
<ArticleContext.Provider value={contextValue}>
{props.children}
</ArticleContext.Provider>)
}
Context
의 변화를 알리는 Provider
컴포넌트를 반환하는 함수다.
전역상태를 보자면 article
은 앞서 설정한 ArticleInfo
의 형태로 이루어질 전역상태이며,
page
는 ArticleInfo
로 이루어진 리스트 형태로 이루어진 전역상태다.
전체 페이지를 알려주는 totalPages
는 number
로 되어있고,
각각 fetch함수의 성공 여부를 통해 컴포넌트가 실행해야할 값을 알려주는 isSuccess
와 isGetUpdateSuccess
는 boolean
값으로 되어있다.
const getPageHandler = async (pageId: string) => {
setIsSuccess(false);
const data = await articleAction.getPageList(pageId);
const page:ArticleInfo[] = data?.data.content;
const pages:number = data?.data.totalPages;
setPage(page);
setTotalPages(pages);
setIsSuccess(true);
}
게시물 리스트를 얻는 함수다. 매개변수는 원하는 페이지의 페이지 값이다.
앞서 백엔드에서 구축한 값에서 Page
객체로 이루어진 값을 보내기 때문에, 리스트는 물론 전체 페이지수에 대한 값도 추출해낼 수 있다.
const getArticleHandler = (param:string, token?:string) => {
setIsSuccess(false);
const data = (token ?
articleAction.getOneArticle(param, token)
: articleAction.getOneArticle(param))
data.then((result) => {
if (result !== null) {
const article:ArticleInfo = result.data;
setArticle(article);
}
})
setIsSuccess(true);
}
게시물 한 개를 얻는 함수다. token
여부에 따라 action
함수에 넣는 매개변수의 값이 달라진다.
const createArticleHandler = (article:PostArticle, token:string) => {
setIsSuccess(false);
const data = articleAction.makeArticle(token, article);
data.then((result) => {
if (result !== null) {
console.log(isSuccess);
}
})
setIsSuccess(true);
}
게시물 생성함수다. 토큰값과 PostArticle
타입의 객체를 매개변수로 받는다.
const getUpdateArticleHancler = async (token:string, param:string) => {
setIsGetUpdateSuccess(false);
const updateData = await articleAction.getChangeArticle(token, param);
const article:ArticleInfo = updateData?.data;
setArticle(article);
setIsGetUpdateSuccess(true);
}
수정을 할 때 이 전에 적었던 게시물 내용을 불러오는 함수다.
수정할 게시물의 번호와 토큰을 매개변수로 받는다.
수정은 생성과 동일한 컴포넌트를 사용하여 만들 것이기 때문에, 성공 여부를 알려주는 전역상태는 isSuccess
가 아니라isGetUpdateSuccess
를 사용한다.
const updateArticleHandler = (token:string, article:PostArticle) => {
setIsSuccess(false);
const data = articleAction.changeArticle(token, article);
data.then((result) => {
if (result !== null) {
}
})
setIsSuccess(true);
}
수정한 게시물을 서버에 등록하는 함수다. 생성과 마찬가지로 PostArticle
타입의 객체를 매개변수로 삼는다.
const deleteArticleHandler = (token:string, param:string) => {
setIsSuccess(false);
const data = articleAction.deleteArticle(token, param);
data.then((result) => {
if (result !== null) {
}
})
setIsSuccess(true);
}
게시물을 삭제하는 함수다. 토큰값과 삭제할 게시물의 id를 매개변수로 갖는다.
추천도 게시판 논리와 매우 유사하다.
/store/recommend-context.ts
import React, { useState } from "react";
import * as recommendAction from './recommend-action';
type Props = { children?: React.ReactNode }
type Recommends = {
recommendNum: number
recommended: boolean
}
interface RecommendCtx {
recommends: Recommends | undefined;
isSuccess: boolean;
isChangeSuccess: boolean;
getRecommends: (param:string, token?:string) => void;
postRecommend: (id:string, token:string) => void;
deleteRecommend: (param:string, token:string) => void;
}
const RecommendContext = React.createContext<RecommendCtx>({
recommends: undefined,
isSuccess: false,
isChangeSuccess: false,
getRecommends: () => {},
postRecommend: () => {},
deleteRecommend: () => {},
});
export const RecommendContextProvider:React.FC<Props> = (props) => {
const [recommends, setRecommends] = useState<Recommends>();
const [isSuccess, setIsSuccess] = useState<boolean>(false);
const [isChangeSuccess, setIsChangeSuccess] = useState<boolean>(false);
const getRecommendsHandler = (param:string, token?:string) => {
setIsSuccess(false);
const data = (token ?
recommendAction.getRecommends(param, token)
: recommendAction.getRecommends(param));
data.then((result) => {
if (result !== null) {
const recommends:Recommends = result.data;
setRecommends(recommends);
}
})
setIsSuccess(true);
}
const postRecommendHandler = async (id:string, token:string) => {
setIsChangeSuccess(false);
const postData = await recommendAction.makeRecommend(id, token);
const msg = await postData?.data;
console.log(msg);
const getData = await recommendAction.getRecommends(id, token);
const recommends:Recommends = getData?.data;
setRecommends(recommends);
setIsChangeSuccess(true);
}
const deleteRecommendHancler = async (param:string, token:string) => {
setIsChangeSuccess(false);
const deleteData = await recommendAction.deleteRecommend(param, token);
const msg = await deleteData?.data;
const getData = await recommendAction.getRecommends(param, token);
const recommends:Recommends = getData?.data;
setRecommends(recommends);
setIsChangeSuccess(true);
}
const contextValue:RecommendCtx = {
recommends,
isSuccess,
isChangeSuccess,
getRecommends: getRecommendsHandler,
postRecommend: postRecommendHandler,
deleteRecommend: deleteRecommendHancler
}
return(
<RecommendContext.Provider value={contextValue}>
{props.children}
</RecommendContext.Provider>
)
}
export default RecommendContext;
여기서는 추천 수와, 추천 여부로 이루어진 Recommends
타입을 사용한다. 이외의 기본적인 type
과 interface
그리고 createContext
에 관련된 내용은 중복되므로 생략하겠다.
const [recommends, setRecommends] = useState<Recommends>();
const [isSuccess, setIsSuccess] = useState<boolean>(false);
const [isChangeSuccess, setIsChangeSuccess] = useState<boolean>(false);
3가지의 state를 사용하며, 하나는 앞서 말한 추천 객체인 Recommends
, 또 하나는 Article
에서도 사용하는, 성공 여부를 알려주는 isSuccess
, 나머지 하나는 추천 생성, 삭제가 제대로 이루어져있는지 알려주는 isChangeSuccess
다.
const getRecommendsHandler = (param:string, token?:string) => {
setIsSuccess(false);
const data = (token ?
recommendAction.getRecommends(param, token)
: recommendAction.getRecommends(param));
data.then((result) => {
if (result !== null) {
const recommends:Recommends = result.data;
setRecommends(recommends);
}
})
setIsSuccess(true);
}
추천을 불러오는 함수다. 매개변수는 토큰의 여부에 따라 갈릴 수 있다.
const postRecommendHandler = async (id:string, token:string) => {
setIsChangeSuccess(false);
const postData = await recommendAction.makeRecommend(id, token);
const msg = await postData?.data;
const getData = await recommendAction.getRecommends(id, token);
const recommends:Recommends = getData?.data;
setRecommends(recommends);
setIsChangeSuccess(true);
}
추천을 생성하는 함수다. 게시물의 id와 토큰을 매개변수로 받는다.
const deleteRecommendHancler = async (param:string, token:string) => {
setIsChangeSuccess(false);
const deleteData = await recommendAction.deleteRecommend(param, token);
const msg = await deleteData?.data;
const getData = await recommendAction.getRecommends(param, token);
const recommends:Recommends = getData?.data;
setRecommends(recommends);
console.log(recommends);
setIsChangeSuccess(true);
}
추천을 삭제하는 함수다. 마찬가지로 게시물의 id와 토큰을 매개변수로 받는다.
댓글도 마찬가지로 유사하므로 간단하게 짚고만 넘어가겠다.
import React, { useState } from "react";
import * as commentAction from './comment-action';
type Props = { children?: React.ReactNode }
type CommentInfo = {
commentId: number,
memberNickname: string,
commentBody: string,
createdAt: Date,
written: boolean
}
type PostComment = {
articleId: string,
body: string
}
interface CommentCtx {
commentList: CommentInfo[];
isSuccess: boolean;
getComments: (param:string, token?:string) => void;
createComment: (comment:PostComment, token:string) => void;
deleteComment: (param:string, id:string, token:string) => void;
}
const CommentContext = React.createContext<CommentCtx>({
commentList: [],
isSuccess: false,
getComments: () => {},
createComment: () => {},
deleteComment: () => {},
});
export const CommentContextProvider:React.FC<Props> = (props) => {
const [commentList, setCommentList] = useState<CommentInfo[]>([]);
const [isSuccess, setIsSuccess] = useState<boolean>(false);
const getCommentsHandler = async (param:string, token?:string) => {
setIsSuccess(false);
const data = (
token ? await commentAction.getComments(param, token) : await commentAction.getComments(param)
);
const comments:CommentInfo[] = await data?.data;
setCommentList(comments);
setIsSuccess(true);
}
const createCommentHandler = async (comment:PostComment, token:string) => {
setIsSuccess(false);
const postData = await commentAction.makeComment(comment, token);
const msg = await postData?.data;
const getData = await commentAction.getComments(comment.articleId, token);
const comments:CommentInfo[] = getData?.data;
setCommentList(comments);
setIsSuccess(true);
};
const deleteCommentHandler = async(param:string,id:string, token:string) => {
setIsSuccess(false);
const deleteData = await commentAction.deleteComment(param, token);
const msg = deleteData?.data;
const getData = await commentAction.getComments(id, token);
const comments:CommentInfo[] = getData?.data;
setCommentList(comments);
setIsSuccess(true);
};
const contextValue:CommentCtx = {
commentList,
isSuccess,
getComments: getCommentsHandler,
createComment: createCommentHandler,
deleteComment: deleteCommentHandler
}
return (
<CommentContext.Provider value={contextValue}>
{props.children}
</CommentContext.Provider>
)
}
export default CommentContext;
type CommentInfo = {
commentId: number,
memberNickname: string,
commentBody: string,
createdAt: Date,
written: boolean
}
type PostComment = {
articleId: string,
body: string
}
댓글의 정보를 가져오는 CommentInfo
타입과, 댓글을 작성하기 위해 필요한 PostComment
타입으로 구성한다.
const [commentList, setCommentList] = useState<CommentInfo[]>([]);
const [isSuccess, setIsSuccess] = useState<boolean>(false);
댓글들을 CommentInfo
타입의 리스트로 만드는 commentList
와 댓글 작성, 삭제 여부를 체크하는 isSuccess
로 이루어져 있다.
const getCommentsHandler = async (param:string, token?:string) => {
setIsSuccess(false);
const data = (
token ? await commentAction.getComments(param, token) : await commentAction.getComments(param)
);
const comments:CommentInfo[] = await data?.data;
setCommentList(comments);
setIsSuccess(true);
};
댓글을 불러오는 함수다. 댓글 삭제를 위해, 토큰 여부에 따라 매개변수가 달라진다.
const createCommentHandler = async (comment:PostComment, token:string) => {
setIsSuccess(false);
const postData = await commentAction.makeComment(comment, token);
const msg = await postData?.data;
const getData = await commentAction.getComments(comment.articleId, token);
const comments:CommentInfo[] = getData?.data;
setCommentList(comments);
setIsSuccess(true);
};
댓글을 생성하는 함수다. 앞서말한 PostComment
타입 객체와, 토큰을 매개변수로 받는다.
댓글을 생성했으면, 자동으로 댓글 리스트의 값이 달라지므로, 바로 이후에 댓글을 불러오는 함수를 실행하여, state
를 재정립한다.
const deleteCommentHandler = async(param:string,id:string, token:string) => {
setIsSuccess(false);
const deleteData = await commentAction.deleteComment(param, token);
const msg = deleteData?.data;
const getData = await commentAction.getComments(id, token);
const comments:CommentInfo[] = getData?.data;
setCommentList(comments);
setIsSuccess(true);
};
댓글을 삭제하는 함수다. .
이 또한 생성과 마찬가지로 먼저 삭제하기 위한 댓글의 id를 불러온 다음, 삭제 시킨 다음, 댓글 리스트를 다시 불러와 state
에 재정립시킨다.
이로써, 프론트엔드에서 Context
에 관한 부분은 모두 끝났다.
이제 이 Context
를 다시 컴포넌트에 적용시켜보자.