ArticleView의 route
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import ArticleView from '@/views/ArticleView.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'ArticleView',
component: ArticleView
},
]
})
export default router
App 컴포넌트에 ArticleView 컴포넌트로 이동하는 RouterLink 작성
<!-- App.vue -->
<template>
<header>
<nav>
<RouterLink :to ="{name:'ArticleView'}">Articles</RouterLink>
</nav>
</header>
<RouterView />
</template>
<script setup>
import { RouterView , RouterLink} from 'vue-router'
</script>
<style scoped>
</style>
ArticleView 컴포넌트에 ArticleList 컴포넌트 등록
<!-- views/ArticleView.vue -->
<template>
<div>
<h1>Article Page</h1>
<ArticleList />
</div>
</template>
<script setup>
import ArticleList from '@/components/ArticleList.vue'
</script >
<style>
</style>
- store에 임시 데이터 articles 배열 작성하기
```javascript
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', () => {
const articles = ref([
{id:1, title:'Article1', content:'Content of article 1'},
{id:2, title:'Article2', content:'Content of article 2'},
])
return { articles }
}, { persist: true })
ArticleList 컴포넌트에서 게시글 목록 출력
store의 articles 데이터 참조
v-for를 활용하여 하위 컴포넌트에서 사용할 article 단일 객체 정보를 props로 전달
<!-- components/Articlist -->
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', () => {
const articles = ref([
{id:1, title:'Article1', content:'Content of article 1'},
{id:2, title:'Article2', content:'Content of article 2'},
])
return { articles }
}, { persist: true })
ArticleListItem 컴포넌트는 내려 받은 props를 정의 후 출력
<!-- components/ArticleListItem.vue -->
<template>
<div>
<h5>{{ article.id }}</h5>
<p>{{ article.title }}</p>
<p>{{ article.content }}</p>
</div>
</template>
<script setup>
defineProps({
article:Object
})
</script>
DRF 서버로의 AJAX 요청을 위한 axios 설치 및 관련 코드 작성
// <!-- vue 서버 종료 -> 설치 -> 서버 재실행 -->
// npm install axios
// store/ counter.js
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
import axios from 'axios'
export const useCounterStore = defineStore('counter', () => {
const articles = ref([])
const API_URL = 'http://127.0.0.1:8000'
}, { persist: true })
DRF 서버로 요청을 보내고 응답 데이터를 처리하는 getAticles 함수
export const useCounterStore = defineStore('counter', () => {
const getArticles = function(){
axios({
method:'get',
url:`${API_URL}/api/v1/articles`
})
.then(res => {
console.log(res)
console.log(res.data)
})
.catch(err => console.log(err))
}
return {articles, API_URL, getArticles}
}, { persist: true })
ArticleView 컴포넌트가 마운트 될 때 getArticles 함수가 실행되도록 함
<!-- views/ArticleView.vue -->
<script setup>
import { onMounted } from 'vue'
import { useCounterStore } from '@/stores/counter'
import ArticleList from '@/components/ArticleList.vue'
const store = useCounterStore()
onMounted(() => {
store.getArticles()
})
</script>
해당 과정 진행 시 브라우저에서 거부 -> CORS policy에 의해 차단
| URL | 결과 | 이유 |
|---|---|---|
| http://localhost:3000/articles/ | 성공 | Path만 다름 |
| http://localhost:3000/comments/3/ | 성공 | Path만 다름 |
| https://localhost:3000/articles/3/ | 실패 | Protocol만 다름 |
| http://localhost:80/articles/3/ | 실패 | Port가 다름 |
| http://localhost:3000/articles/3/ | 실패 | Host만 다름 |
기본적으로 웹 브라우저는 같은 출처에서만 요청하는 것을 허용하며, 다른 출처로의 요청은 보안상의 이유로 차단됨
하지만 현대 웹 애플리케이션은 다양한 출처로부터 리소스를 요청하는 경우가 많기 때문에 CORS 정책이 필요하게 되었음
설치
관련 코드 주석 해제
# settings.py
INSTALLED_APPS = [
'corsheaders',
]
MIDDLEWARE = [
'corsheaders.middleware.CprsMiddleware',
]
CORS_ALLOWED_ORIGINS = [
'http://127.0.0.1:5173',
'http://localhost:5173'
]
응답 받은 데이터에서 각 게시글의 데이터 구성 확인(id, title, content)
store에 게시글 목록 데이터 저장
export const useCounterStore = defineStore('counter', () => {
const getArticles = function(){
axios({
method:'get',
url:`${API_URL}/api/v1/articles/`
})
.then(res => {
articles.value = res.data
})
.catch(err => console.log(err))
}
return {articles, getArticles}
},{persist:true})
store에 저장된 게시글 목록 출력 확인
DetailVue 관련 route 주석 해제
import DetailView from '@/views/DetailView.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes:[
{
path:'/',
name:'ArticleView',
component:ArticleView
},
{
path:'/articles/:id',
name:'DetailView',
component:DetailView
}
]
})
ArticleListItem에 DetailView 컴포넌트로 가기 위한 RouterLink
<template>
<div>
<h5>{{ article.id }}</h5>
<p>{{ article.title }}</p>
<p>{{ article.content }}</p>
<RouterLink
:to="{ name: 'DetailView', params: { id: article.id }}"
>
[DETAIL]
</RouterLink>
<hr>
</div>
</template>
<script setup>
import { RouterLink } from 'vue-router'
defineProps({
article: Object
})
</script>
DetailView가 마운트 될 때 특정 게시글을 조회하는 AJAX 요청 진행
<script setup>
import axios from 'axios'
import { onMounted, ref } from 'vue'
import { useCounterStore } from '@/stores/counter'
import { useRoute } from 'vue-router'
const store = useCounterStore()
const route = useRoute()
const article = ref(null)
onMounted(() => {
axios({
method: 'get',
url: `${store.API_URL}/api/v1/articles/${route.params.id}/`
})
.then((response) => {
console.log(response.data)
article.value = response.data
})
.catch((error) => {
console.log(error)
})
})
</script>
CreateView 관련 route 주석 해제
import CreateView from '@/views/CreateView.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'ArticleView',
component: ArticleView
},
{
path: '/articles/:id',
name: 'DetailView',
component: DetailView
},
{
path: '/create',
name: 'CreateView',
component: CreateView
}
]
})
export default router
ArticleView에 CreateView 컴포넌트로 가기 위한 RouterLink 작성
<template>
<div>
<h1>Article Page</h1>
<RouterLink :to="{ name: 'CreateView' }">
[CREATE]
</RouterLink>
<ArticleList />
</div>
</template>
<script setup>
import { onMounted } from 'vue'
import { useCounterStore } from '@/stores/counter'
import { RouterLink } from 'vue-router'
import ArticleList from '@/components/ArticleList.vue'
const store = useCounterStore()
onMounted(() => {
store.getArticles()
})
</script>
<style>
</style>
v-model을 사용해 사용자 입력 데이터를 양방향 바인딩
v-model의 trim 수식어를 사용해 사용자 입력 데이터의 공백을 제거
<template>
<div>
<h1>게시글 작성</h1>
<input type="text" v-model.trim="title">
<textarea v-model.trim="content"></textarea>
<input type="submit">
</form>
</div>
</template>
<script setup>
import axios from 'axios'
import { ref } from 'vue'
import { useCounterStore } from '@/stores/counter'
import { useRouter } from 'vue-router'
const store = useCounterStore()
const title = ref(null)
const content = ref(null)
const router = useRouter()
</script>
<style>
</style>
게시글 생성 요청을 담당하는 createArticle 함수 작성
게시글 생성이 성공한다면 ArticleView 컴포넌트로 이동
import axios from 'axios'
import { ref } from 'vue'
import { useRouter } from 'vue-router'
const createArticle = function () {
axios({
method: 'post',
url: `${store.API_URL}/api/v1/articles/`,
data: {
title: title.value,
content: content.value
}
})
.then((response) => {
console.log(response.data)
router.push({ name: 'ArticleView' })
})
.catch((error) => {
console.log(error)
})
}
submit 이벤트가 발생하면 createArticle 함수를 호출
v-on의 prevent 수식어를 사용해 submit 이번테의 기본 동작 취소
<template>
<div>
<h1>게시글 작성</h1>
<form @submit.prevent="createArticle">
<input type="text" v-model.trim="title">
<textarea v-model.trim="content"></textarea>
<input type="submit">
</form>
</div>
</template>
최종 코드
<template>
<div>
<h1>게시글 작성</h1>
<form @submit.prevent="createArticle">
<input type="text" v-model.trim="title">
<textarea v-model.trim="content"></textarea>
<input type="submit">
</form>
</div>
</template>
<script setup>
import axios from 'axios'
import { ref } from 'vue'
import { useCounterStore } from '@/stores/counter'
import { useRouter } from 'vue-router'
const store = useCounterStore()
const title = ref(null)
const content = ref(null)
const router = useRouter()
const createArticle = function () {
axios({
method: 'post',
url: `${store.API_URL}/api/v1/articles/`,
data: {
title: title.value,
content: content.value
}
})
.then((response) => {
console.log(response.data)
router.push({ name: 'ArticleView' })
})
.catch((error) => {
console.log(error)
})
}
</script>
<style>
</style>