CSR 구현
① 페이지 로드 시 필요한 리소스를 클라이언트에 미리 선언
② 클라이언트에서 필요한 데이터를 비동기 호출 받음
③ 클라이언트가 전달받은 데이터를 가공, 리소스를 사용하여 화면에 표시.
클라이언트에 리소스 선언 -HTML Template
-클라이언트에 리소스 선언 하는 방법은 다양) 리엑트, 뷰
-현재는 HTML 템플릿 기능을 간단하게 사용해 브라우저에 표시되지 않는 HTML 요소를 작성해두고 Js로 이를 화면에 반복적으로 그려냄.
...
table
thead
tr
td(colspan="2")
input#content(type="text")
td: button(onclick="writeComment()") 댓글작성
tbody#comments
template#comment-template
tr
td.content
td.author
td.createdAt
-comment = 댓글
-mongoose의 sub-schema를 이용하여 Post 스키마에 Comment를 배열로추가
e.g) comments: [CommentSchema]
-populate를 사용할때, ObjectID만 저장 하는 것과는 다르게 Comment의 내용을 게시글이 포함하게 됨
※ sub-schema 내부에서도 populate가능
---PostSchema---
const CommentSchema = new Schema({
content: String,
author: {type: Schema.Types.ObjectId,
ref: 'User',},
}, {timestamps: true,}
);
const PostSchema= new Schema({
...
// comments에 서브스키마 삽입
comments: [CommentSchema],
...
-api 라우터를 추가하고, RESTful하게 api/posts/{postId}/comment 경로로 댓글작성 기능 구현
-게시글 업데이트시 $push를 사용하여 몽고DB를 통해 comments 배열에 새로 작성된 댓글 추가→동시에 들어오는 요청에 대해 정확히 처리
-api는 render 하지않고 json으로 응답
---routes/api.js---
...
//shortId로 작성한 댓글 경로
router.post('/posts/:shortId/comments', ... {
//shortId를 받아와 게시글 찾음
const { shortId } = req.params;
const { content } = req.body;
const author= await User.findOne({ shortId: req.user.shortId});
//shortid로 게시글 찾아 comments를 넣어 게시글 업데이트
await Post.updateOne({ shortId}, {
// Post내의 comments에 내용/작성자가 들어감
$push: { comments: {
content,
author,
}},
});
// api는 render 하지않고 json으로 응답
res.json({ result: 'success' });});
...
-/api/posts/{postId}/comments로 RESTful 경로 설정
-find에 populate 하지않고 User(model)로 populate를 사용하는 방법도 가능
...
//shortId를 받아 특정 게시글 찾아옴.
router.get('/posts/:shortId/comments', ... {
const { shortId } = req.params;
const post = await Post.findOne({ shortId });
// post.comments는 content, author를 가짐.
// 각 author에 User가 obi로 검색이 돼서 주입됨.
await User.populate(post.comments, {
path: 'author'
});
//json으로 보내게 되면 각 댓글에 작성자가 주입되어 사용 가능해짐.
res.json(post.comments);
});
...
-브라우저는 비동기 HTTP 요청을 fetch 함수를 이용해 제공 : 간단하게 Js로 HTTP 요청 처리.
-jQuery의 ajax와 유사한 기능인데 이를 사용하지 않고 HTTP 요청 구현 가능.
(1) 댓글 작성
-댓글 작성 버튼 클릭시 writeComment() 실행되는 코드임.
-input#content에서 내용을 읽어 fetch로 댓글 작성api 호출
-호출 결과의 성공 여부를 확인하여, 댓글 다시 불러오기 실행
---posts/view.pug
...
script.
function writeComment() {
const input = document.querySelector('#content');
const content = input.value;
---fetch 부분: JSON req를 보내기 위한 설정 부분---
fetch('/api/posts/#{post.shortId}/comments', {
method: 'post',
headers: { 'Content-Type': 'application/json'},
body: JSON.stringify({ content }),
})
.then(() => {
if (res.ok) {
input.value= '';
loadComments();
} else {
alert('오류가 발생 했습니다.');
}
});
(2) 댓글 목록 불러오기
---posts/view.pug---
// 댓글목록 api 호출
script.
loadComments(); //페이지 불러왔을때 댓글목록 불러옴.
function loadComments() {
document
.querySelector('#comments')
.innerHTML= ''; // 이전 목록 삭제
//작성한 comments api 호출
fetch('/api/posts/#{post.shortId}/comments')
.then((res) => {
if (res.ok) {
// 호출한 comments들을 json화
return res.json();
} else {
throw new Error('댓글을불러오지못했습니다');
}
})
//json으로 변환 성공시 댓글들을 순환하며 출력
.then((comments) => {
comments.forEach(addComment);
});
// 오류 발생시 캐치
.catch((err) => alert(err.message));
}
// HTMLTemplate 사용하여 댓글 화면에 출력
function addComment(comment) {
const template = document.querySelector('#comment-template');
//importNode : 노드를 만드는 함수. 아래는 만든 노드 구성.
const node = document.importNode(template.content, true);
//댓글 내용
node.querySelector('.content').textContent= comment.content;
//댓글 작성자
node.querySelector('.author').textContent= comment.author.name;
//댓글 작성 시간
node.querySelector('.createdAt').textContent= comment.createdAt;
//위의 코드들을 통해 만든 노드를 comments에 붙임
document.querySelector('#comments').appendChild(node);
}