[Vue] Vue with DRF 1

Jingi·2024년 5월 13일

Web

목록 보기
39/40
post-thumbnail

게시글 목록 출력

메인 페이지 구현

  • ArticleView 컴포넌트에 ArticleList 컴포넌트와 ArticleListItem 컴포넌트 등록 및 출력하기
  • ArticleList와 ArticleListItem은 각각 게시글 출력을 담당

게시글 목록 출력

  • 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와의 요청과 응답

DRF로 부터 응답 데이터 받기

  • DRF 서버에 요청하여 데이터를 응답받아 store에 저장 후 출력하기

DRF와의 요청과 응답

  • 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에 의해 차단

CORS Policy

SOP(Same-origin policy)

  • 동일 출처 정책
  • 어떤 출처에서 불러온 문서나 스크립트가 다른 출처에서 가져온 리소스와 상호 작용하는 것을 제한하는 보안 방식
    • 다른 곳에서 가져온 자료는 일단 막는다
    • 웹 애플리케이션의 도메인이 다른 도메인의 리소스에 접근하는 것을 제어하여 사용자의 개인 정보와 데이터의 보안을 보호하고, 잠재적인 보안 위협을 방지
    • 잠재적으로 해로울수 있는 문서를 분리함으로써 공격받을 수 있는 경로를 줄임

Origin (출처)

  • URL의 Protocol, Host, Port를 모두 포함하여 출처라고 부름
  • same Origin 예시
  • 예시
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 policy의 등장

  • 기본적으로 웹 브라우저는 같은 출처에서만 요청하는 것을 허용하며, 다른 출처로의 요청은 보안상의 이유로 차단됨

    • SOP에 의해 다른 출처의 리소스와 상화작용 하는 것이 기본적으로 제한하기 때문
  • 하지만 현대 웹 애플리케이션은 다양한 출처로부터 리소스를 요청하는 경우가 많기 때문에 CORS 정책이 필요하게 되었음

    • CORS는 웹 서버가 리소스에 대한 서로 다른 출처 간 접근을 허용하도록 선택할 수 있는 기능을 제공

CORS

  • 교차 출처 리소스 공유
  • 특정 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제
    • 만약 다른 출처의 리소스를 가져오기 위해서는 이를 제공하는 서버가 브라우저에게 다른 출처지만 접근해도 된다는 사실을 알려야 함

CORS policy

  • 교차 출처 리소스 공유 정책
  • 다른 출처에서 온 리소스를 공유하는 것에 대한 정책
  • 서버에서 설정되며, 브라우저가 해당 정책을 확인하여 요청이 허용되는지 여부를 결정
    • 다른 출처의 리소스를 불러오려면 그 다른 출처에서 올바른 CORS header를 포함한 응답을 반환해야 함

CORS policy 정리

  • 웹 애플리케이션이 다른 도메인에 있는 리소스에 안전하게 접근할 수 있도록 허용 또는 차단하는 보안 메커니즘
  • 서버가 약속된 CORS Header를 포함하여 응답한다면 브라우저는 해당 요청을 허용
    • 서버에서 CORS Header를 만들어야 한다

CORS Headers 설정하기

  • Django에서는 django-cors-headers 라이브러리 화용
    • 손쉽게 응답 객체에 CORS headers를 추가해주는 라이브러리

django-cors-headers 사용하기

  • 설치

    • pip install django-cors-headers
  • 관련 코드 주석 해제

    # settings.py
    
    INSTALLED_APPS = [
      'corsheaders',
    ]
    MIDDLEWARE = [
      'corsheaders.middleware.CprsMiddleware',
    ]
    
    CORS_ALLOWED_ORIGINS = [
      'http://127.0.0.1:5173',
      'http://localhost:5173'
    ]

Aticle CR 구현

전체 게시글 조회

  • 응답 받은 데이터에서 각 게시글의 데이터 구성 확인(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에 저장된 게시글 목록 출력 확인

    • pinia-plugin-persistedstate에 의해 브라우저 Local Storage에 저장됨

단일 게시글 조회

  • 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>
    
profile
데이터 분석에서 백엔드까지...

0개의 댓글