Django(16) - AJAX, axios를 활용해 비동기 처리하기
2022, Oct 26
AJAX
Asynchronous JavaScript(비동기 자바스크립트)
const request = new XMLHttpRequest()
const URL = 'https://jsonplaceholder.typicode.com/todos/1/'
request.open('GET', URL)
request.send()
const todo = request.response
console.log('data: ${todo}')
console.log('hi')
setTimeout(function () {
console.log('작업중')
})
console.log('bye')
비동기의 특징
- 병렬적 작업 수행
- 요청을 보낸 후 응답을 기다리지 않고 다음 동작이 이루어진다.
- 결과적으로 변수 todo에는 응답 데이터가 할당되지 않고 빈 문자열이 출력됨
- 그렇다면 JS는 왜 기다려주지 않는 방식으로 동작하는가?
Event Loop 기반 동시성 모델
Call Stack
- 요청이 들어올 때마다 해당 요청을 순차적으로 처리하는 Stack(LIFO) 형태의 자료구조
Web API(Browser API)
- JS 엔진이 아닌 브라우저 영역에서 제공하는 API
- 예) setTimeout(), DOM event, AJAX
Task Queue(Event Queue, Message Queue)
- 비동기 처리된 callback 함수가 대기하는 Queue(FIFO) 형태의 자료 구조
- main thread가 끝난 후 실행되어 후속 JavaScript 코드가 차단되는 것을 방지함
Event Loop
- Call Stack이 비어있는지 확인
- 비어 있는 경우 Task Queue에서 실행 대기 중인 callback 함수가 있는지 확인
- Task Queue에 대기중인 callback 함수가 있다면 가장 앞에 있는 callback 함수를 Call Stack으로 push
AJAX?
- Asynchoronous JavaScript And XML(최근에는 JSON을 더 많이 사용한다)
- 비동기 통신을 이용하면 화면 전체를 새로고침 하지 않아도 서버로 요청을 보내고, 데이터를 받아 화면의 일부분만 업데이트 하는것이 가능하다
- 이러한 비동기 통신 웹 개발 기술을 AJAX라고 한다.
- 비동기 웹 통신을 위한 라이브러리 중 하나가 Axios
좋아요 기능 비동기로 처리하기
articles > detail.html
- i 태그에 id와 data-article-id를 부여해준다.
...
{% if request.user.is_authenticated %}
{% if request.user in article.like_users.all %}
<i id="like-btn" data-article-id="{{ article.pk }}" class="bi bi-heart-fill"></i>
{% else %}
<i id="like-btn" data-article-id="{{ article.pk }}" class="bi bi-heart"></i>
{% endif %}
{% endif %}
<span id="like-count">{{ article.like_users.count }}</span>
...
detail.html의 script부
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
// (1) 좋아요 버튼
const likeBtn = document.querySelector('#like-btn')
// (2) 좋아요 버튼을 클릭하면, 함수 실행
likeBtn.addEventListener('click', function(event){
// 서버로 비동기 요청을 하고싶음
console.log(event.target.dataset)
axios({
method: 'get',
url: `/articles/${event.target.dataset.articleId}/like/`
})
.then(response => {
console.log(response)
console.log(response.data)
//event.target.classList.toggle('bi-heart')
//event.target.classList.toggle('bi-heart-fill')
if (response.data.isLiked === true ) {
event.target.classList.add('bi-heart-fill')
event.target.classList.remove('bi-heart')
} else {
event.target.classList.add('bi-heart')
event.target.classList.remove('bi-heart-fill')
}
const likeCount = document.querySelector('#like-count')
likeCount.innerText = response.data.likeCount
})
})
</script>
views.py
@login_required
def like(request, pk):
article = get_object_or_404(Article, pk=pk)
# 만약에 로그인한 유저가 이 글을 좋아요를 눌렀다면,
# like_users.all() 안에 그 안에 유저가 있으면 (apple 에 a가 포함되어있나? 정도 느낌)
if request.user in article.like_users.all():
# 좋아요 삭제
article.like_users.remove(request.user)
is_liked = False
else:
# 좋아요 추가
article.like_users.add(request.user)
is_liked = True
# 상세 페이지로 redirect
context = {'isLiked': is_liked,
'likeCount': article.like_users.count(),
}
return JsonResponse(context)
댓글 작성 비동기 처리
- 어떤 이벤트일때 요청을 보낼지
- form을 작성하면..
- /article/
<pk>
/comments/
- 서버에서 어떤 응답을 JSON으로 보내서
- 댓글 정보를 보내서
- DOM 조작을 어떻게 할지
- 댓글 목록에 추가해줌
articles > detail.html
- 기존의 댓글 form에 id값과 data-article-id를 부여한다.
...
<h4 class="my-3">댓글</h4>
{% if request.user.is_authenticated %}
<form id="comment-form" data-article-id="{{ article.pk }}">
{% csrf_token %}
{% bootstrap_form comment_form layout='inline' %}
{% bootstrap_button button_type="submit" content="OK" %}
{% comment %} {% endcomment %}
</form>
{% endif %}
<hr>
...
detail.html 의 script부
- 기존의 댓글이 생성되는 부분에 div를 추가해서 id를 부여해준다
<script>
// 1. form을 작성
const commentForm = document.querySelector('#comment-form')
// 2. 제출하면, 함수 실행
// csrf
const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value
commentForm.addEventListener('submit', function(event) {
event.preventDefault();
axios({
method: 'post',
url: `/articles/${event.target.dataset.articleId}/comments/`,
headers: { "X-CSRFToken": csrftoken },
data: new FormData(commentForm) // 폼에 있는 정보를 data로 넘겨줄 수 있도록 변환
})
.then(response => {
console.log(response.data)
const comments = document.querySelector('#comments')
const p = document.createElement('p')
p.innerText = `${response.data.userName} | ${response.data.content}`
const hr = document.createElement('hr')
comments.append(p, hr)
commentForm.reset()
})
})
</script>
views.py
- context로 내용과 유저명을 담아서 JsonResponse로 context를 반환
from django.http import JsonResponse
@login_required
def comment_create(request, pk):
article = Article.objects.get(pk=pk)
comment_form = CommentForm(request.POST)
if comment_form.is_valid():
comment = comment_form.save(commit=False)
comment.article = article
comment.user = request.user
comment_form.save() # 모델 인스턴스의 save()
context = {
'content': comment.content,
'userName': comment.user.username
}
return JsonResponse(context)