Vue.js 3 & NodeJS/사내교육 - 점심 메뉴

[6] Frontend 개발 - 등록 뷰 (AddView) 생성

carrotweb 2026. 8. 3. 15:51
728x90
반응형

등록 뷰 (AddView) 생성

1. src\views\LunchMenu에서 LunchMenuAddView.vue 파일을 생성합니다.

<template>
    <div>점심 메뉴 등록</div>
</template>

<script>
export default {
    setup() {
    }
}
</script>

 

2. src\router\index.js에서 LunchMenuAddView.vue를 import하고 routes에 route를 추가합니다.

:
import LunchMenuAddView from '@/views/LunchMenu/LunchMenuAddView.vue'

const routes = [
  :
  {
    path: '/lunchmenu/',
    name: 'lunchmenulist',
    component: LunchMenuListView
  },
  {
    path: '/lunchmenu/add',
    name: 'lunchmenuadd',
    component: LunchMenuAddView
  }
]
:

 

 

중첩된 라우트

/lunchmenu/와 /lunchmenu/add를 App.vue에 있는 router-view 컴포넌트가 아닌 /lunchmenu에서 Vue 컴포넌트를 생성하고 자체적인 router-view 컴포넌트에서 처리할 수 있습니다.

 

/ → app.vue
+- <router-view/>
    +- /lunchmenu/ → LunchMenuListView.vue
    +- /lunchmenu/add → LunchMenuAddView.vue

App.vue에 있는 router-view에서 변경됨

 

/ → app.vue
+- <router-view/>
    +- /lunchmenu → LunchMenuView.vue
        +- <router-view/>
            +- / → LunchMenuListView.vue
            +- /add → LunchMenuAddView.vue

LunchMenuView에 있는 router-view에서 변경됨

 

중첩된 라우트에 연결된 Vue 컴포넌트에는 반드시 router-view 컴포넌트가 있어야 합니다.

 

App UI는 컴포넌트가 중첩되게 구성되어 있습니다.

웹 경로(URL)를 세그먼트(segment, 부분)하면 컴포넌트를 중첩 시킬 수 있습니다. 

 

중첩된 라우트에 대한 설명을 참고하세요.
(https://router.vuejs.kr/guide/essentials/nested-routes.html)

 

3. src\views\LunchMenu에서 LunchMenuView.vue 파일을 생성합니다.

<template>
    <div class="lunch-menu">점심 메뉴</div>
    <div class="lunch-view">
        <router-view/>
    </div>
</template>

<style>
.lunch-menu {
    width: 540px;
    margin: 10px auto;
}
.lunch-view {
    background-color: #dddddd;
    width: 540px;
    padding: 12px 0px;
    margin: auto auto;
}
</style>

 

4. src\router\index.js에서 LunchMenuView.vue를 import하고 중첩된 웹 경로(부모 경로)를 분리하여 route에 추가하고 중첩되지 않는 웹 경로(자식 경로)는 children(칠드런)에 추가합니다.

:
import LunchMenuView from '@/views/LunchMenu/LunchMenuView.vue'

const routes = [
  :
  {
    path: '/lunchmenu’,
    component: LunchMenuView,
    children : [
      {
        path: '',
        name: 'lunchmenulist',
        component: LunchMenuListView
      },
      {
        path: 'add',
        name: 'lunchmenuadd',
        component: LunchMenuAddView
      }
    ]
  }
]
:

 

5. 웹 브라우저에서 확인하기 (http://127.0.0.1:8080/lunchmenu/)

 

6. src\views\LunchMenu\LunchMenuListView.vue 파일에 등록 뷰 컴포넌트로 이동하기 위해 router-link를 추가합니다. <style>에 있는 menu-list를 수정합니다.

<template>
    <div>점심 메뉴 리스트</div>
    <div class="buttons">
        <div class="right">
            <router-link :to="{ name: 'lunchmenuadd' }" class="button blue">
                <span>등록</span>
            </router-link>
        </div>
    </div>
    :
</template>

<style>
.menu-list {
    margin: 10px 10px;
    padding: 0;
}
:
</style>
  • to 속성에 path(웹 경로) 대신 route(라우트) 이름을 사용하기 위해서는 :to 속성을 사용해야 합니다.
  • :to는 v-bind:to를 단축한 겁니다. v-bind는 속성 값에 데이터를 바인딩해줍니다.
  • name에는 route(라우트)를 생성할 때 부여한 이름을 사용합니다.
  • /lunchmenu/add로 링크가 생성됩니다.

 

네임드 라우트

router-link 컴포넌트는 path 대신 route(라우트)를 생성할 때 부여한 이름으로 사용할 수 있습니다.
네임드 라우트 (https://router.vuejs.kr/guide/essentials/named-routes.html)

 

to 속성에 path(웹 경로)를 사용 → route(라우트)를 생성할 때 등록한 path를 사용

v3.x

<router-link to="/lunchmenu/add">등록</router-link>
<router-link :to="{ path: '/lunchmenu/add' }">등록</RouterLink>

 

v4.x

<RouterLink to="/lunchmenu/add">등록</RouterLink>
<RouterLink :to="{ path: '/lunchmenu/add' }">등록</RouterLink>

 

:to(v-bind:to) 속성에 name(이름)을 사용 → route(라우트)를 생성할 때 부여한 이름을 사용

v3.x

<router-link :to="{ name: 'lunchmenuadd' }">등록</router-link>

 

v4.x

<RouterLink :to="{ name: 'lunchmenuadd' }">등록</RouterLink>

 

7. public에 css 디렉터리를 생성하고 default.css 파일을 생성합니다. 공통으로 사용할 버튼 CSS를 추가합니다.

.buttons {
    position: relative;
    width: 500px; 
    height: 32px;
    margin-bottom: 20px;
    margin: auto auto;
}
.buttons > div.right {
    position: absolute;
    height: 32px;
    right: 0;
}
.buttons > div > a.button {
    display: inline-block;
    min-width: 95px;
    height: 32px;
    line-height: 32px;
    vertical-align: middle;
    font-size: 13px;
    text-align: center;
    text-decoration: none;
    border-radius: 20px;
    cursor: pointer;
}
.buttons > div > .button.blue {
    color: #fff; 
    border-color: #0099d2 !important; 
    background: #0099d2 !important;
}

 

8. link로 스타일시트를 추가합니다.

<!DOCTYPE html>
<html lang="">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <link rel="stylesheet" href="<%= BASE_URL %>css/default.css">
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body>
    <noscript>
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

<%= BASE_URL %> 대신 /를 사용해도 됩니다. → <link rel="stylesheet" href="/css/default.css">
vue.config.js에서 module.exports에 publicPath가 없으면 <%= BASE_URL %>은 /입니다. 

 

BASE_URL 변경 방법

vue.config.js에서 module.exports에 publicPath로 경로를 추가하고 다시 시작(npm run serve)하면 BASE_URL이 publicPath로 변경됩니다.

 

9. 웹 브라우저에서 확인하기 (http://127.0.0.1:8080/lunchmenu/)

 

10. 웹 브라우저에서 확인하기 (http://127.0.0.1:8080/lunchmenu/add)

 

11. <template>에 점심 메뉴 입력 폼을 추가하고 이 컴포넌트에서만 사용하는 CSS를 <style>에 추가합니다.

<template>
    <div class="test">점심 메뉴 등록</div>
    <form class="lunchMenuForm">
        <div>
            <label for="lunchMenuName">점심 메뉴 명 :</label>
            <input type="text" id="lunchMenuName" 
                placeholder="점심 메뉴 명을 입력하세요."/>
        </div>
    </form>
    <div class="buttons">
        <div class="right">
            <button class="button blue">등록</button>
            <button class="button">취소</button>
        </div>
    </div>
</template>

<script>
export default {
    setup() {
    }
}
</script>

<style scoped>
.lunchMenuForm {
    margin: 10px 10px; padding: 10px 10px 0px 10px; background-color: white;
}
.lunchMenuForm > div {
    padding-bottom: 10px; display: flex;
}
.lunchMenuForm label {
    flex: 0 0 120px; line-height: 38px;
}
.lunchMenuForm input[type="text"] {
    width: 100%; font-size: 14px;
}
button {
    display: inline-block; padding: 0.375rem 0.75rem; min-width: 95px;
    border: 1px solid #6c757d; border-radius: 0.375rem; margin-left: 10px;
    cursor: pointer;
}
button:first-child {
    margin-left: 0;
}
</style>

<style>에서 scoped를 설정하면 컴포넌트에서만 적용되는 스타일이 됩니다.

 

12. 웹 브라우저에서 확인하기 (http://127.0.0.1:8080/lunchmenu/add)

 

13. setup()에 점심 메뉴 명이 문자열로 저장될 lunchMenuName이 반응형이 되도록 reactive(리엑티브) 함수를 사용합니다. 그리고 <template>에서 사용할 수 있게 리턴합니다.

<script>
import { reactive } from 'vue';

export default {
    setup() {
        const state = reactive({
            lunchMenuName: ""
        });

        state.lunchMenuName = "Hello";

        return {
            state
        }
    }
}
</script>

값이 반응형 상태가 되도록 하는 ref(레프) 함수와 다르게 reactive(리엑티브) 함수는 객체 자체를 반응형으로 처리합니다. (https://ko.vuejs.org/guide/essentials/reactivity-fundamentals#reactive)

 

14. <template>에서 lunchMenuName과 <input> 태그가 양방향 바인딩되게 v-model 디렉티브를 사용합니다.

<template>
    <div class="test">점심 메뉴 등록</div>
    <form class="lunchMenuForm">
        <div>
            <label for="lunchMenuName">점심 메뉴 명 :</label>
            <input type="text" id="lunchMenuName" 
                placeholder="점심 메뉴 명을 입력하세요."
                v-model="state.lunchMenuName"/>
        </div>
    </form>
    <div class="buttons">
        <div class="right">
            <button class="button blue">등록</button>
            <button class="button">취소</button>
        </div>
    </div>
</template>

Form 입력 바인딩에 대한 설명을 참고하세요.
(https://ko.vuejs.org/guide/essentials/forms.html#form-input-bindings)

 

15. 웹 브라우저에서 확인하기 (http://127.0.0.1:8080/lunchmenu/add)

 

toRef vs toRefs

반응형 객체인 state의 모든 속성에 대해 toRef()로 반환되게 하기 위해서는 toRefs() 함수를 사용합니다.

<script>
import { reactive, toRefs } from 'vue';

export default {
    setup() {
        const state = reactive({
            lunchMenuName: ""
        });

        state.lunchMenuName = "Hello";

        const { lunchMenuName } = toRefs(state);
        lunchMenuName.value = "Hello World!!";

        return {
            lunchMenuName
        }
    }
}
</script>

toRefs()는 반응형 객체의 모든 속성에 대해 ref를 만들어 리턴합니다.
(https://ko.vuejs.org/api/reactivity-utilities.html#torefs)

 

모든 속성이 분해 할당됩니다. Destructuring(디스트럭처링, 구조 분해 할당)

const { lunchMenuName } = toRefs(state);

 

반응형 객체인 state의 모든 속성에 대해 toRef()로 반환되게 하기 위해서는 toRefs() 함수를 사용합니다.

<script>
import { reactive, toRefs } from 'vue';

export default {
    setup() {
        const state = reactive({
            lunchMenuName: ""
        });

        state.lunchMenuName = "Hello World!!";

        return {
            ...toRefs(state)
        }
    }
}
</script>

Spread Operator(스프레드 오퍼레이터)를 사용하여 모든 속성을 분해합니다. → 모든 속성들이 ref()로 리턴

 

웹 브라우저에서 확인하기 (http://127.0.0.1:8080/lunchmenu/add)

 

16. 버튼이 클릭되면 함수(핸들러)가 호출되도록 @click(v-on:click)를 사용합니다.

<template>
    <div class="test">점심 메뉴 등록</div>
    <form class="lunchMenuForm">
        <div>
            <label for="lunchMenuName">점심 메뉴 명 :</label>
            <input type="text" id="lunchMenuName" 
                placeholder="점심 메뉴 명을 입력하세요."
                v-model="lunchMenuName"/>
        </div>
    </form>
    <div class="buttons">
        <div class="right">
            <button class="button blue" @click="addClick">등록</button>
            <button class="button" @click="cancelClick">취소</button>
        </div>
    </div>
</template>

@는 v-on 디렉티브를 단축한 겁니다.

@이벤트=함수(핸들러)

함수(핸들러)는 setup()에 함수로 정의합니다.

 

이벤트 핸들링에 대한 설명을 참고하세요.

(https://ko.vuejs.org/guide/essentials/event-handling.html)

 

v-on에 대한 설명을 참고하세요.

(https://ko.vuejs.org/api/built-in-directives#v-on)

17. setup()에 버튼 클릭에 대한 함수(핸들러)를 정의하고 리턴합니다.

<script>
import { reactive, toRefs } from 'vue';

export default {
    setup() {
        const state = reactive({
            lunchMenuName: ""
        });

        function addClick() {
        }

        function cancelClick() {
        }

        return {
            ...toRefs(state),
            addClick,
            cancelClick
        }
    }
}
</script>

 

18. 취소 버튼이 클릭되면 이전 경로로 이동하기 위해서 라우터 인스턴스를 받아서 이전 경로로 이동하도록 router.back() 또는 router.go() 함수를 사용합니다. → Router 프로그램 방식

<script>
import { reactive, toRefs } from 'vue';
import { useRouter } from 'vue-router';

export default {
    setup() {
        const router = useRouter();
        :
        function cancelClick() {
            router.back();
        }
        :
    }
}
</script>

router.back();은 router.go(-1);과 같습니다.

 

특정 히스토리로 이동합니다.

(https://router.vuejs.kr/guide/essentials/navigation.html#Traverse-history)

 

19. 등록 버튼이 클릭되면 axios(엑시오스)의 post() 함수를 사용하여 APIServer에 점심 메뉴가 추가 되도록 합니다.

<script>
:
import axios from 'axios';

export default {
    setup() {
        :
        const state = reactive({
            lunchMenuName: ""
        }); 

        function addClick() {
            const lunchMenuItem = { menuName : state.lunchMenuName };
            axios.post("http://127.0.0.1:9000/api/v1/lunchmenus/", lunchMenuItem).then((res) => {
                console.log(res);
            }).catch((err) => {
                console.log(err);
            });
        }
        :
    }
}
</script>

 

20. 웹 브라우저에서 확인하기 (http://127.0.0.1:8080/lunchmenu/add)

 

21. 점심 메뉴가 추가되면 점심 메뉴 리스트로 이동되도록 router.replace() 함수를 사용합니다.

<script>
:

export default {
    setup() {
        const router = useRouter();
        
        const state = reactive({
            lunchMenuName: ""
        }); 

        function addClick() {
            const lunchMenuItem = { menuName : state.lunchMenuName };
            axios.post("http://127.0.0.1:9000/api/v1/lunchmenus/", lunchMenuItem).then(() => {
                router.replace({name : 'lunchmenulist'});
            }).catch((err) => {
                console.log(err);
            });
        }
        :
    }
}
</script>

 

라우터 이동

router.push() 함수와 router.replace() 함수는 이동할 위치 정보를 이용하여 vue 컴포넌트로 이동합니다.

router.push() 함수를 사용하면 히스토리에 추가됩니다. 웹 브라우저에서 이전 버튼을 클릭하면 현재 링크(등록 뷰 컴포넌트)로 이동합니다.

router.push({name : 'lunchmenulist'});

router.replace() 함수를 사용하면 현재 히스토리가 변경됩니다. 웹 브라우저에서 이전 버튼을 클릭하면 현재 링크(등록 뷰 컴포넌트)의 이전 링크(리스트 뷰 컴포넌트)로 이동합니다.

router.replace({name : 'lunchmenulist'});

 

21. 웹 브라우저에서 확인하기 (http://127.0.0.1:8080/lunchmenu)

728x90
반응형