안녕하세요, Brad입니다. 오늘은 XHR과 Ajax을 수업시간에 실습해봤는데요. 이에 대해서 간략하게 정리해볼게요.
Introduction
모바일과 달리 웹은 모든 자원이 서버에 존재합니다. 그렇기 때문에 해당 url에 접속하게 되면 웹브라우저가 HTML을 위에서 부터 읽어오면서 필요한 자원을 모두 가져오죠. 우리가 흔히 쓰는 네이버도 처음 접속시 엄청 많은 양의 자원을 서버로부터 요청받아 받아옵니다.
그런데 네이버 웹에서 만약 하나의 부분만 바꾸고 싶다면 어떻게 해야할까요? 지금까지 배운대로라면 '새로고침'하여 다시 저렇게 많은 자원을 다시 요청해야 합니다. 특정 부분만 바꾸고 싶은데 대부분의 자원을 모두 다시 받아와야 하는 것입니다. 정말 비효율적이죠. 그렇기 때문에 등장한 것이 Ajax입니다.
Ajax는 Asynchronous JavaScript And XML의 약어로 언어나 프레임워크가 아닌 구현하는 방식을 의미합니다. 이 기술을 구현하는데 반드시 필요한 것이 XMLHttpRequest(이하 XHR) 객체인 것이죠. 그럼 일단 이 정도만 알아보고 기존 애플리케이션에서 Ajax을 어떻게 구현할 수 있는지 순서대로 정리해봅시다.
실습
- 이벤트 핸들러에 등록 - 버튼이 되었던 특정 시간으로 설정하든 상관없습니다. 저의 경우 댓글에 대한 이벤트이기 때문에 버튼에 등록시켰죠
// 클릭하는 순간 addAnswer()를 실행할 것
// .은 클래스명을 의미, 그 자식 중 button이고 submit일 때
// js고치고 새로고침 한번해야 적용이 됩니다.
$(".submit-write button[type=submit]").click(addAnswer);
- 서버로 보낼 form 데이터 묶기 - 답변 form에 있던 데이터들을 묶어줍니다.
function addAnswer(e) {
console.log("add answer");
e.preventDefault(); //submit 이 자동으로 동작하는 것을 막는다.
// form data들을 자동으로 묶어준다.(key-value 형태로)
// key는 html에 있는 name이 됩니다.
var queryString = $(".submit-write").serialize();
console.log("query : "+ queryString);
- 서버로 데이터 보내기 - form의 action에 있던 url까지 가져오고 설정을 더해준다음 서버로 보냅니다.
function addAnswer(e) {
console.log("add answer");
e.preventDefault();
var queryString = $(".submit-write").serialize();
console.log("query : "+ queryString);
var url = $(".submit-write").attr("action"); // action에 있던 url을 읽어와 설정한다
console.log("url : " + url);
$.ajax({
type : 'post', // delete, put도 그대로 사용가능
url : url, // 서버의 path로 씁니다.
data : queryString, // 아까 묶어둔 데이터
dataType : 'json',
error: onError,
success : onSuccess,
});
}
- 서버에서 데이터 처리 및 JSON 응답 - 서버 컨트롤러에서 처리를 해준다음 String값의 url이 아닌 JSON으로 전달할 객체(Answer)로 반환합니다.
// json으로 처리할 때는 RestController로 쓰는 것이 좋습니다.
@RestController
@RequestMapping("/api/questions/{questionId}/answers")
public class ApiAnswerController {
@Autowired
private QuestionRepository questionRepository;
@Autowired
private UserRepository userRepository;
@Autowired
private AnswerRepository answerRepository;
@PostMapping("/{id}") // 다른점은 Answer이 반환점
public Answer post(@PathVariable long questionId, @PathVariable long id, HttpSession session, String comment, Model model) {
Result result = valid(session);
if(!result.isValid()) {
model.addAttribute("errorMessage", result.getErrorMessage());
return null;
}
Answer answer = new Answer(
questionRepository.findById(questionId).orElseThrow(() -> new QuestionNotFoundException("해당 질문을 찾을 수 없습니다.")),
userRepository.findById(id).orElseThrow(() -> new UserNotFoundException("해당 유저를 찾을 수 없습니다.")),
comment,
false
);
return answerRepository.save(answer);
}
- 클라이언트(크롬)에서 JSON값 확인 - JSON으로 보낸 객체가 제대로 들어왔는지 크롬 개발자 도구를 통해 확인해봅니다. 위에서 성공시 onError()를 호출하고 성공시 onSuccess()를 호출하기로 했는데요. 이에 대한 정의를 이전에 해두어야합니다.
// 성공할때와 실패할때 정의해놓아야함
function onError() {
console.log('error');
}
function onSuccess(data, status) {
console.log(data);
}
- 클라이언트에서 JSON 데이터 처리 - 마지막 단계로 JSON값을 이용하여 동적으로 html을 생성하고 또 필요한 값을 바꿔줍니다
function onSuccess(data, status) {
console.log(data);
var answerTemplate = $("#answerTemplate").html();
var template = answerTemplate.format(data.user.id, data.user.name, data.createDate, data.comment, data.question.id, data.id);
$(".qna-comment-slipp-articles").prepend(template);
$("textarea[name=comment]").val(""); // 답변은 비워둠
$(".qna-comment-count strong").html(data.question.notDeletedAnswersSize); // 답변갯수 값 바꾸기
}
유의사항
오늘 실습을 하면서 5번에서 자꾸 '무한루프'가 발생하여서 진행하는데 어려움이 있었습니다. 그 이유는 JSON객체로 다시 클라이언트로 넘겨줄 때 반환 객체(Answer
) 안에 쓰이고 있는 getter메서드의 값들을 다 가져오는데요. Question과 Answer 사이에 bidirect(양방향) 매핑이 되어있기 때문입니다. 다시 말하면 Answer
에서 Question
getter가 있으니 Question
으로 갑니다. 그런데 Question
내에서는 Answer
를 getter하는 메서드(List<Answer>
)가 존재하고 있으니 이게 왔다갔다 왔다갔다 계쏙 무한루프에 빠지게 되는 것이죠. 재미있는건 제가 조금전에 말했다싶이 getter메서드의 값들을 다 가져오는데 반환값이 Answer
이나 Question
만 아니면 다시 그쪽으로 안간다는 것입니다.
public long getNotDeletedAnswersSize() {
return answers.stream()
.filter(answer -> !answer.isDeleted())
.count();
}
다시 말하면 위 코드의 경우 List<Answer> answers
로 선언된 answers를 사용하고 있지만 사용하고 나서 long타입의 값들 가지기 때문에 다시 Answer
로 가서 탐색하지 않습니다.
@JsonIgnore
public List<Answer> getNotDeletedAnswers() {
return answers.stream()
.filter(answer -> !answer.isDeleted())
.collect(Collectors.toList());
}
하지만 이 코드의 경우 List<Answer>
로 반환되기 때문에 여기있는 Answers
의 값들을 찾으러 또 다시 Answer
로 탐색하러 가게됩니다. 그렇기 때문에 이러한 무한루프로 막기위해선 이 부분은 Json탐색시 무시한다는 의미인 @JsonIgnore
를 명시하여야 합니다. 그렇게 되면 이 부분은 탐색하지 않기 때문에 무한루프에서 벗어날 수 있겠죠. 물론 탐색하지 않기 때문에 JSON 객체 안에 그 값은 없구요. 제가 받았던 JSON 객체 값은 다음과 같습니다.
'TIL' 카테고리의 다른 글
Today's Dev Notes(2018-12-03) (0) | 2018.12.03 |
---|---|
Today's Dev Notes(2018-12-02) (0) | 2018.12.02 |
Today's Dev Notes(2018-11-26) (0) | 2018.11.27 |
Today's Dev Notes(2018-11-25) (0) | 2018.11.25 |
Today's Dev Notes(2018-11-22) (0) | 2018.11.22 |