티스토리 뷰

WEB

[PWA] 뷰 vue.js 고급 기능

마시멜로co. 2020. 10. 26. 22:58

저번 글에는 뷰의 기초를 공부하였습니다.

marshmello.tistory.com/48

 

[PWA] 뷰 vue.js 입문

뷰 vue.js 란 ? 에반 유가 개발한 자바스크립트 프레임워크입니다. 또한 뷰는 인터랙티브 웹 인터페이스를 개발하기 위한 PWA 자바스크립트 프레임워크입니다. 프로그레시브란 프로그램 실행에 필

marshmello.tistory.com

 

뷰의 가장 큰 특징은 HTML 기반을 둔 커스텀 엘리먼트인 컴포넌트를 제작하고 활용 할 수 있다는 점입니다.

그리고 컴포넌트의 상태값 관리와 뷰 간의 이동을 도와주는 Vuex와 라이터 또한 중요한 기능입니다.

 

Vuex와 라우터 기능을 활용하면 싱글 페이지 애플리케이션을 간편하게 제작 할 수 있습니다. 

 

1. computed 속성

vue.js 사이트에 가면, CDN와 샘플 소스를 볼수 있습니다. kr.vuejs.org/v2/guide/index.html에서 복사하여 <head>태그사이에 CDN을 연결합니다.

 

시작하기 — Vue.js

Vue.js - 프로그레시브 자바스크립트 프레임워크

kr.vuejs.org

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
</head>

VSCode를 열어 computed.html 파일을 생성합니다.

좌측 메뉴에 computed속성을 클릭하여 기본예제를 실행해봅니다.

최종소스

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
</head>

<body>
    <div id="example">
        <p>원본 메시지: "{{ message }}"</p>
        <p>역순으로 표시한 메시지: "{{ reversedMessage }}"</p>
    </div>

    <script>
        var vm = new Vue({
            el: '#example',
            data: {
                message: '안녕하세요'
            },
            computed: {
                // 계산된 getter
                reversedMessage: function () {
                    // `this` 는 vm 인스턴스를 가리킵니다.
                    return this.message.split('').reverse().join('')
                }
            }
        })
    </script>
</body>

</html>

실행결과

 

message라는 문자열 변수를 머스태시로 감싸서 바인딩한 후 <p> 앨리먼트의 값으로 표시합니다.

두번째는 머스태시안에 reversedMessage 함수를 실행해서 사용합니다. 함수가 실행된 후에 그 변환값이 <p> 엘리먼트의 값으로 랜더링됩니다.

 

data속성에는 message 문자열 변수의 초깃값을 '안녕하세요'로 설정했습니다. 이 변수는 앞의 머스태시에 전달되어 랜더링됩니다.

 

머스태시 안에 내용이 복잡하면 바인딩해서 사용하기에 부담스럽습니다. 이럴 떄는 computed 속성으로 자바스크립트의 내용을 함수로 바꾸고 머스태시를 이용해 해당 함수를 호출하는 형식으로 사용합니다.  그리고 computed 속성은 캐시 메모리에 저장되므로 반환값이 같을때 빠르게 실행 할 수 있습니다.

 

여기서 computed 속성 안에 reversedMessage() 함수를 정의했습니다. data속성에 정의된 message 문자열을 역순으로 바꾸어 반환하도록 정의했습니다. this를 사용해 data속성에 있는 message 문자열 변수에 접근하고, 변수 객쳏에 있는 reverse() 함수를 실행하여 역순이된 문자열을 반환합니다. 그러면 {{reversedMessage}}에서 반환값이그대로 랜더링되어화면에표시됩니다.

 

2. method 속성

메소드는 뷰 인스턴스에 포함해 사용하는 함수를 의미합니다. 특히 뷰에서 method 속성은 이벤트 핸들러를 사용해 마우스 클릭과 같은 이벤트가 발생했을때 실행되는 로직에 많이 활용됩니다. 

 

실습소스 method.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
</head>

<body>
    <div id="example-1">
        <p>클릭 숫자 : {{nClicks}}</p>
        <p>카운트 다운 : {{ fnCounter }}</p>
        <button v-on:click="fnIncrement">Click!</button>
    </div>
    <script>
        new Vue({
            el: '#example-1',
            data: {
                nClicks: 0
            },
            //computed는 머스태시 안의 로직이 복잡할 때 사용
            computed: {
                fnCounter: function () {
                    //nClicks값이 10을 기준으로 카운트 다운되도록 1만큼 감소시킴
                    return 10 - this.nClicks;
                }
            },
            //메서드는 이벤트 핸들러로직을 실행할 때 사용 
            methods: {
                //nClicks값을 1만큼 증가시킴
                fnIncrement: function () {
                    return this.nClicks++;
                }
            }
        })
    </script>
</body>

</html>

 

실행결과

nClicks 변수는 클릭한 개수가 들어있는 변수값을 바인딩하여 p 엘리먼트값으로 렌더링합니다. 

fnCounter() 함수는 카운트다운하며 변경되는 값을 반환하는 역할을 합니다.

 

v-on 디렉티브는 버튼을 누르면 fnIncrement() 함수를 이벤트 핸들러로 연결하여 실행합니다. 이때 중요한 것은

fnIncrement() 함수의 구체적인 기능을 methods 속성에서 정의한다는 점입니다.

data 속성에는 nClicks라는 클릭한 횟수를 저장하는 변수를 초기값 0으로 정의합니다. 그리고 computed 속성에는 머스태시의 선언 내용 중에서 fnCounter() 함수 내용 부분을 구체적으로 기술합니다.

 

소스에서 fnCounter() 함수는 10을 기준으로 지금까지 클릭한 횟수를 저장하는 nClicks 변수값과 뺄셈을 수행해서 클릭할수록 10,9,8...로 표현되도록 합니다. 이처럼 머스태시에 선언된 내용의 로직이 복잡해지면 함수로 바꿔서 computed 속성에 정의하면 됩니다.

 

fnIncrement() 함수의 여러 기능이 methods에 정의되어 있습니다. fnIncrement() 함수는 지금까지 클릭한 횟수를 저장한 nClicks 변수값을 ++ 연산자를 이용해서 하나 증가시킨 후 반환하는 아주 간단한 기능을 수행합니다. data 속성에 정의된 nClicks 변수에 접근하기 위해 this를 사용하는 것을 유의해서 보세요.

 

3. componet로 HTML element 만들기

뷰의 중요한 특징인 컴포넌트는 HTML의 기본 엘리먼트외에 자신만의 엘리먼트를 만들어 쓰는 모듈을 의미합니다.

 

컴포넌트의 사용법은 HTML element와 같습니다. 미리 만든 컴포넌트 이름으로 여는 태그< >와 닫는태그< />에 적용해 사용합니다.

kr.vuejs.org/v2/guide/components.htmlkr.vuejs.org/v2/guide/components.html

뷰 공식홈페이지에 컴포넌트 가이드를 확인합니다.

 

소스코드

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
</head>

<body>
    <div id="example">
        <my-component></my-component>
    </div>
    <script>

        // 등록
        Vue.component('my-component', {
            template: '<span>{{ message }}</span>',
            data: function () {
                return {
                    message: 'hello'
                }
            }
        })

        // 루트 인스턴스 생성
        new Vue({
            el: '#example'
        })
    </script>
</body>

</html>

실행화면

 

뷰 가이드 페이지에서 컴포넌트 사용법이 자세히 나와있으니 순서대로 해보는 것을 추천합니다.

 

템플릿은 컴포넌트를 새로운 엘리먼트처럼 사용할 수 있게 합니다. 컴포넌트를 하나의 신규 엘리먼트로 본다면 화면에 표시할 부분을 처리하는 속성이 필요한데, 이것을 템플릿 속성이라고 합니다. 

 

사용방법은 template:을 먼저 작성하고 그 다음에 문자열로 HTML 문서의 내용을 작성하면 됩니다. 문자열에서 일반적으로 작은 따옴표 (')와 큰따옴표(")를 사용하는데 둘 다 줄바꿈을 하기 어렵다는 단점이 있습니다.

 template: `
            <div>
                <div v-for="item in aFruits" class="fruit_style">
                  <p>좋아하는 과일: {{ item.sFruit_name }}</p>
                </div>
            <br>
            </div>`,

그래서 일반적으로는 역따옴표(`)를 사용합니다. 역따옴표를 사용해 문자열을 선언하면 줄바꿈이 있어도 HTML 문서로 자동인식합니다.

 

data속성은 변수나 함수로 선언 할 수 있습니다. 그러나 컴포넌트에서 data 속성을 사용할 때는 반드시 함수로 사용해야합니다.

 Vue.component('my-component', {
    data: function () {
      return {
       ...
      }
    },

 

컴포넌트 안에 data 속성을 변수로 선언하면 실행시 인스턴스가 생성되지 않으므로 'Reference Error : message is no defined' 라는 오류가 발생합니다. 이것은 같은 컴포넌트를 여러개 사용할 때 data 속성 변수값들이 별도의 메모리 공간에서 개별적으로 관리되게 하려는 의도입니다. 따라서 컴포넌트 내부는 반드시 함수로 작성해야합니다. 

 

 

4. 컴포넌트 속성 props

컴포넌트 속성(props)은 컴포넌트에서 전달되는 어트리뷰트의 값을 말하며 문자열이나 객체의 배열 형식으로 되어 있습니다.kr.vuejs.org/v2/guide/components.html#Props역시 뷰 공식사이트에서 사용방법과 샘플 소스를 확인합니다.

component_prop.html 파일을 생성합니다.

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
    <script>
        Vue.component('child', {
            // props 정의
            props: ['message'],
            // 데이터와 마찬가지로 prop은 템플릿 내부에서 사용할 수 있으며
            // vm의 this.message로 사용할 수 있습니다.
            template: '<span>{{ message }}</span>'
        })
    </script>
</head>

<body>
    <div id="main">
        <child message="안녕하세요!"></child>
    </div>
    <script>
        new Vue({
            el: '#main'
        });

    </script>
</body>

</html>

컴포넌트는 HTML 앨리먼트와 똑같이 사용되므로 어트리뷰트도 사용할 수 있어야합니다. 방법은 컴포넌트를 등록할 때 props 속성을 선언해주면됩니다. 그러면 선언된 이름을 다른 속성에서 전달받아, 매개 변수처럼 사용할 수 있습니다. 

 

위 소스에서는 child라는 이름으로 컴포넌트를 먼저 등록합니다.그리고 props 속성에 대괄호[]를 사용해서 어트리뷰트 이름을 문자열로 선언합니다. 이렇게 하면 HTML에서 child라는 이름으로 컴포넌트를 사용할 때 다음과같이 mesage 어트리뷰트에 값을 전달 할 수 있습니다. 

 <child message="안녕하세요!"></child>

 

5. 상태값 관리와 Vuex

하나의 뷰 또는 복수의 화면 사이에서 여러 개의 컴포넌트를 사용하면 각 컴포넌트 간에 상태값을 전달하거나 공유하기가 쉽지 않습니다. 이 문제를 해결하기 위해 Vuex라는 상태값 관리 전문 라이브러리가 준비되어 있습니다. 이것이 바로 뷰의 막강한 기능이라고 할 수 있습니다. 

 

Vuex에는 state, mutations(setters), getters 그리고 actions까지 총 4가지 속성이 있습니다. 각각 어떤 경우에 사용하는지 꼭 기억해야 합니다.

뷰의 동작 원리

속성 기능
state 공유한상태값 데이터 정의
mutations setters의 의미. 외부에서 동기 방식으로 저장할 때 사용
getters state의 데이터 값을 외부에서 읽어 올 때 사용
actions 외부의 API 실행 같은 비동기 실행을 관리할 때 사용

vuex.vuejs.org/kr/

 

Vuex가 무엇인가요? | Vuex

Vuex가 무엇인가요? Vuex는 Vue.js 애플리케이션에 대한 상태 관리 패턴 + 라이브러리 입니다. 애플리케이션의 모든 컴포넌트에 대한 중앙 집중식 저장소 역할을 하며 예측 가능한 방식으로 상태를

vuex.vuejs.org

vuex 라이브러리 공식 사이트를 토대로 학습하겠습니다.

설치 방법을 토대로 <head></head>사이에 CDN로 연결해줍니다.

    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
    <script src="https://unpkg.com/vuex"></script>

샘플 소스코드 vuex.vuejs.org/kr/guide/

// 모듈 시스템을 사용하는 경우 Vue.use(Vuex)를 먼저 호출해야합니다.

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  }
})

Vuex 객체를 사용하려면 Vuex의 Store()함수로 필요한 속성을 정의하면 됩니다. Store 객체를 new로 생성할 때 매개변수를 활용해 JSON형식으로 정의한 속성을 전달하면됩니다. 

Vuex객체의 인스턴스는 const문을 사용해서 store 변수에 상수로 저장됩니다. 이는 나중에 다른 값으로 변경 할 수 없음을 의미합니다.  state 속성에 count 변수값을 0으로 초기화 했습니다. count 변수에는 Vuex를 통해서 전역으로 사용되는 데이터를 담습니다. 

 

mutations와 getters의 기능은 서로 반대라고 생각하면 간단합니다. 외부에서 Vuex에 접근할 때 데이터 변경을 대신 수행해줄 에이전트가 필요합니다. 그 역할을 수행할 함수를 선언하는 것이 바로 mutations 속성입니다. 

반대로, Vuex안의 데이터 값을 외부로 반환해 주는 것은 getters 속성에 정의된 함수로 할 수 있습니다. 

 

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title></title>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
    <!-- Vuex를 사용하기 위해 CDN 연결 -->
    <script src="https://unpkg.com/vuex"></script>
</head>

<body>
    <div id="app">
        <h1>안녕하세요!</h1>
        <!-- 카운터 신규 엘리먼트 2개 사용. msg 속성에 이름만 다르게 적용 -->
        <com-counter msg="카운터1"></com-counter>
        <com-counter msg="카운터2"></com-counter>
    </div>
</body>

<script>
    // Vuex의 store 중앙에 state, mutations(setters), getters, actions을 정의함
    const store = new Vuex.Store({
        // count 값을 상태값으로 정의
        state: {
            count: 0
        },
        // mutations는 getters의 대칭되는 setters의 역할을 설정
        mutations: {
            // 일반적인 함수 형식으로 count 상태값을 증가 시키도록 정의
            fnIncData: function (state) {
                return state.count++
            },
            // 함수를 간단하게 1줄로 압축하여 => 연산자를 활용해 count 상태값을 감소 시키도록 정의
            fnDecData: state => state.count--
        },
        getters: {
            // 상태값을 반환함
            fnGetData(state) {
                return state.count;
            },
        },
        actions: {
            // 상태값을 감소시키는 함수는 서버단에서 실행한다는 가정을 해봄. 따라서 비동기 실행으로 정의하기 위해 async를 사용하고 매개변수로 commit 객체 전달
            async fnDecData({
                commit
            }, state) {
                // 가상으로 만든 원격 API 실행
                const result = await api.fnDecrement();
                // 원격 API가 성공할 경우에 비로소 fnDecData 함수 실행
                if (result == true) commit('fnDecData')
            }
        }
    })

    // 타이머를 사용해 1초 후 True 값을 반환하도록 가상으로 원격 서버 API 정의
    const api = {
        fnDecrement() {
            // 비동기 계산을 수행하기 위해 Promise를 사용하고 그에 따른 성공 값을 반환하기 위해 resolve 함수 실행
            return new Promise((resolve) => {
                setTimeout(() => {
                    resolve(true);
                }, 1000); // 원격서버 느낌을 내기 위해 1초 딜레이 시킴
            });
        },
    };

    // com-counter라는 신규 엘리먼트 등록
    Vue.component('com-counter', {
        // 카운터 제목은 엘리먼트의 msg 속성값을 받아서 렌더링 함
        props: ['msg'],
        template: `
            <div>
                <h2>{{ msg }}</h2>
                <p>카운터: {{ fnGetCount }}</p>
                <button @click="fnIncCount">+1 증가</button>
                <button @click="fnDecCount">-1 감소(원격 API 실행)</button>
                <hr>
            </div>`,
        computed: {
            // 카운터 값은 store에서 getters에 접근하여 값을 가져온 후 렌더링함
            fnGetCount() {
                return store.getters.fnGetData;
            }
        },
        methods: {
            // 카운터 증가는 동기실행을 가정하고 직접 store의 mutations에 접근하여 실행
            fnIncCount() {
                store.commit('fnIncData')
            },
            //카운터 감소는 원격 서버 API로 비동기 실행을 가정하고 actions에 접근하여 실행
            fnDecCount() {
                store.dispatch('fnDecData')
            }
        }
    })

    var app = new Vue({
        el: '#app',
        // store 사용을 선언함
        store
    })
</script>

</html>

외부에서 mutations 속성에 접근하여 함수를 실행할 때는 다음처럼 사용합니다.

store.commit(fnDecData);// 스토어에 있는 fnDecData 실행

commt() ㅎ마수를 이용해서 fnDecData() 함수에 접근한다는 점을 유의하세요!

이것은 동기 방식으로 차례차례 실행되게 하는 뷰의 처리 방법입니다. 이렇게 처리순서를 확인 할 수 있어 디버깅이 가능합니다. 

 

반면에 getters 속성에 정의된 fnGetDate() 함수는 외부에서 store 내부의 status 속성값을 가져 올수 있도록 합니다.

 

외부에서 getters 속성의 fnGetData()함수를 사용하는 방법은 다음과 같습니다.

result = store.getters.fnGetData; //store에 접근하여 fnGetData 함수 실행

비동기 실행이 필요한 함수는 이름앞에 async 키워드를 붙여서 관리해주어야 합니다. 이번 소스는 마치 외부 API의 함수를 실행하는 것을 흉내 내기위해서 API 모듈을 별도로 만들고 그 API의 fnDecrement() 을 실행하는 구조로 되어 이습니다. 

    const api = {
        fnDecrement() {
            // 비동기 계산을 수행하기 위해 Promise를 사용하고 그에 따른 성공 값을 반환하기 위해 resolve 함수 실행
            return new Promise((resolve) => {
                setTimeout(() => {
                    resolve(true);
                }, 1000); // 원격서버 느낌을 내기 위해 1초 딜레이 시킴
            });
        },
    };

그리고 외부 실행 결과가 성공하면 그때 비로소 state 속성에 있는 값을 감소시키기 위해 mutations의 fnDecData()함수에 접근해 실행합니다. 

    // com-counter라는 신규 엘리먼트 등록
    Vue.component('com-counter', {
        // 카운터 제목은 엘리먼트의 msg 속성값을 받아서 렌더링 함
        props: ['msg'],
        template: `
            <div>
                <h2>{{ msg }}</h2>
                <p>카운터: {{ fnGetCount }}</p>
                <button @click="fnIncCount">+1 증가</button>
                <button @click="fnDecCount">-1 감소(원격 API 실행)</button>
                <hr>
            </div>`,
        computed: {
            // 카운터 값은 store에서 getters에 접근하여 값을 가져온 후 렌더링함
            fnGetCount() {
                return store.getters.fnGetData;
            }
        },
        methods: {
            // 카운터 증가는 동기실행을 가정하고 직접 store의 mutations에 접근하여 실행
            fnIncCount() {
                store.commit('fnIncData')
            },
            //카운터 감소는 원격 서버 API로 비동기 실행을 가정하고 actions에 접근하여 실행
            fnDecCount() {
                store.dispatch('fnDecData')
            }
        }
    })

 

 com-counter라는 신규 엘리먼트 등록하고 msg라는 이름으로 어트리뷰트를 받도록 합니다. 전달받은 어트리뷰트를 출력하고 현재 카운터값과 <+1증가>, <-1감소(원격 API실행)> 버튼으로 값을 증가시키거나 감소시키는 뷰를 디자인합니다. 

 fnIncCount() {
                store.commit('fnIncData')
            },

컴포넌트 +1 증가 버튼을 클릭해서 값이 증가되는 경우입니다.  이벤트 핸들러이므로 methods 속성에 fnInCount() 함수를 배치합니다. 그리고 store라는 스토어의 mutations에 접근할 것이므로 commit()함수를 이용해서 fnIncData()함수를 실행합니다. 이처럼 컴포넌트가 아무리 많아져도 같은 스토어에 있는 값을 공유해서 사용할 수 있습니다.

    methods: {
            ........
            fnDecCount() {
                store.dispatch('fnDecData')
            }

Vuex의 actions 속성에 있는 쓰기에 접근해 보겠습니다. <-1감소> 버튼을 클릭하면 원격에 있는 API서비스 함수를 실행하므로 언제 결과가 나올지 예상하기 어렵습니다.

이때는 store에 있는 actions 속성에 접근합니다. 즉 dispatch() 함수를 통해서 actions 속성에 접근한 후 1만큼 감소시키기 위해 fnDecData()함수를 실행합니다 이처럼 Vuex는 동기실행과 비동기 실행을 구분하며, 비동기는 반드시 dispatch() 를 통해서 Vuex의 actions 속성에 접근한다는 것을 기억해야합니다.

 

  var app = new Vue({
        el: '#app',
        // store 사용을 선언함
        store
    })

이제 Vuex를 사용하기 위해 Vue 객체에 Vuex 인스턴스 등록을 합니다. 위처럼 Vue 객체를 생성할 때 store 옵션을 지정하면됩니다. 

 

6. 네비게이션과 라우터

라우터란 페이지끼리 이동할 수 있는 기능을 의미합니다. 라우터를 사용하여 경로와 컴포넌트를 등록하면 싱글 페이지 애플리케이션 SPA 사용자가 클릭한 경로로 화면이 쉽게 이동하도록 도와줍니다. 라우터는 이러한 SPA를 만들때 중요한 역할을 합니다 . 메인과 서브페이지 사이를 오가는 간단한 API를 살펴보도록 하겠습니다. 

 

SPA는 페이지 화면이 바뀌어도 웹 브라우저에서 새로고침이 일어나지 않는 형식으로 되어있습니다. 즉, 하나의 페이지안에서 다양한 페이지를 이동해도 마치 네이티브 앱처럼 동작하는 효과를 얻을 수 있습니다. 이때 페이지끼리 이동 링크를 관리하는 방법으로 라우터를 사용하면 뷰 영역의 배치와 링크 관계를 쉽게 설정할 수 있으므로 편리합니다. 

 

router.vuejs.org/kr/installation.html

vue router 공식홈페이지에 접속합니다.

vue_router.html 파일을 생성합니다.

 

CDN으로 vue-router.js와 vue.js를 연결합니다. vue router 공식홈페이지에서 제공되는 샘플 소스를 적용해봅니다.

최종 코드

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
    <script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
</head>

<body>
    <div id="app">
        <h1>Hello App!</h1>
        <p>라우터 사용 : 
            <!-- 네비게이션을 위해 router-link 컴포넌트를 사용합니다. -->
            <!-- 구체적인 속성은 `to` prop을 이용합니다. -->
            <!-- 기본적으로 `<router-link>`는 `<a>` 태그로 렌더링됩니다.-->
            <router-link to="/foo">Go to Foo</router-link>
            <router-link to="/bar">Go to Bar</router-link>
        </p>
        <!-- 라우트 아울렛 -->
        <!-- 현재 라우트에 맞는 컴포넌트가 렌더링됩니다. -->
        <router-view></router-view>
    </div>
    <script>
       // 0. 모듈 시스템 (예: vue-cli)을 이용하고 있다면, Vue와 Vue 라우터를 import 하세요
        // 그리고 `Vue.use(VueRouter)`를 호출하세요


        // 1. 라우트 컴포넌트를 정의하세요.
        // 아래 내용들은 다른 파일로부터 가져올 수 있습니다.
        const Foo = { template: '<div>foo</div>' }
        const Bar = { template: '<div>bar</div>' }

        // 2. 라우트를 정의하세요.
        // Each route should map to a component. The "component" can
        // 각 라우트는 반드시 컴포넌트와 매핑되어야 합니다.
        // "component"는 `Vue.extend()`를 통해 만들어진
        // 실제 컴포넌트 생성자이거나 컴포넌트 옵션 객체입니다.
        // 이동할 주소명과 사용할 컴포넌트를 등록하여 라우터를 정의합니다. 
        const routes = [
            { path: '/foo', component: Foo },
            { path: '/bar', component: Bar }
        ]

        // 3. `routes` 옵션과 함께 router 인스턴스를 만드세요.
        // 추가 옵션을 여기서 전달해야합니다.
        // 지금은 간단하게 유지하겠습니다.
        const router = new VueRouter({
            routes // `routes: routes`의 줄임
        })

        // 4. 루트 인스턴스를 만들고 mount 하세요.
        // router와 router 옵션을 전체 앱에 주입합니다.
        const app = new Vue({
            router
        }).$mount('#app')

// 이제 앱이 시작됩니다!

    </script>
</body>

</html>

먼저 라우터를 사용해 보겠습니다.

router-view 엘리먼트를 사용하면, 그 영역은 라우터에 의해 정의된 뷰가 됩니다. 그리고 라우터의 뷰페이지 변경은 router-link 엘리먼트를 사용합니다. 

 

소스의 라우터를 사용해 다른 HTML 엘리먼트와 함께 어떻게 사용할 수 있는지 보여줍니다.

 

먼저<h1>엘리먼트로 제목을 표시합니다. 그리고 이어서 router-view 엘리먼트로 라우터 영역이 나타납니다. 다음으로 p 앨리먼트 안에 router-link 엘리먼트를 사용하여 페이지끼리 이동할 수 있는지 보여줍니다. 이동해야 할 대상 페이지 이름은 to 속성에 라우터 페이지 이름을 지정하면 됩니다. 이렇게 하면 router-link 를 일반 HTML 링크를 담당하는 a 엘리먼트와 비슷하게 사용 할 수 있습니다. 

 

라우터를 등록할 때는 배열 변수를 사용하여 화면에 보여줄 페이지를 JSON 형식으로 구분한 뒤 이동할 페이지 이름과 해당되는 컴포넌트 템플릿을 정의하면 됩니다. 

  const routes = [
            { path: '/foo', component: Foo },
            { path: '/bar', component: Bar }
        ]

router 인스턴스 생성하고 routes 옵션 전달

const router = new VueRouter({
            routes // `routes: routes`의 줄임
        })

 

routers 옵션에 routers 값을 전달합니다. VueRouter 객체의 인스턴스는 router 상수 변수에 저장했는데 이 이름은 변경하지 말고 사용해야합니다. 뷰 객체 이름으로도 사용됩니다.

const app = new Vue({
            router
        }).$mount('#app')

Vue객체를 생성할 때 router 옵션을 지정하면 됩니다. 

 

소스 실행결과

7. SPA 만들기

 웹앱은 서버에 매번 요청하더라도 새로 고침이 불필요한 SPA로 제작하는 추세입니다. 뷰는 SPA를 만드는데 유용합니다. 특히 Vue-CLI를 통해 템플릿을 내려받아서 모듈 단위로 프로젝트 파일을 관리하면 SPA를 더욱 손쉽게 제작 할 수 이습니다. 

 

 Vue-CLI(Vue command line interface)란 명랑창의 명령으로 프로젝트 파일을 설치하고 빌드하는 작업을 도와주는 인터페이스입니다. 

 

nodejs.org/ko/

 

Node.js

Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine.

nodejs.org

먼저, Node.js가 설치되어 있어야 합니다. 위 사이트에서 LTS 버전을 받아 설치합니다.

Vue-CLI는 VSCode의 통합 터미널 창에서 설치할 수 있스비다. 통합 터미널 창은 보기 -> 터미널 메뉴를 선택하거나 단축키 ctrl+`를 누르면 됩니다. 

터미너 창을 열고 다음 내용을 입력합니다.

npm install -g @vue/cli

만약 에러가 발생한다면, visual studio code를 껐다가 관리자권한으로 켜서 다시 터미널에 위 명령어를 실행해봅니다.

 

웹팩을 사용한 프로젝트 폴더 구조

 

웹팩(webpack)이란? 자바스크립트 개발에 필요한 파일 (.vue, .js, .css, 각종 이미지 파일 등)을 단일 파일로 묶어주고 실행성능이 빨라지도록 최적화 해주는 Node.js 기반의 오픈 소스 모듈 번들러 도구입니다. 

 

자바스크립트를 통한 대규모 웹 애플리케이션을 개발하려면 복잡한 의존성을 가지는 모듈은 효율적으로 관리할 수있어야합니다. 또한 최종 배포 파일은 네트워크 트래픽을 절약 할 수 있습니다. 또한 전통적인 언어의 컴파일과 유사한 단계를 거치게 되는데 뷰는 웹팩을 이용할 때 이 과정을 빌드라고 부릅니다. 뷰에서는 /src 폴더에서 개발하고 최종빌드하면 /dist 폴더에 웹팩을 통해 정적 파일이 생성됩니다. 최초 배포때에는 이 파일들을 웹호스팅에 업로드해서 사용합니다.

 

 

터미널로 프로젝트 생성 명령어

vue create 프로젝트명

에러가 발생한다면,

PS C:\> executionpolicy
RemoteSigned
 
---> 현재는 스크립트 막음
 
PS C:\> set-executionpolicy unrestricted
PS C:\> executionpolicy
Unrestricted
 
---> 스크립트 허용

<Vue-CLI 프로젝트 옵션설정>

please pick a preset -> Manually select featres 선택

신규 프로젝트 템플릿을 기본으로 할지 아니면 사용자 정의로 할지 결정

check the features needs for your project -> [Router]만 선택 나머지 모두 선택해제

이 메뉴로 Babel, TypeScript, PWA등의 다양한 기능을 템플릿에 담을 수 있음 (스페이스바로 선택/해제)

User history model for router? 기본값 Y 선택( 주소 표시줄에 나타나는 #을 제거해줌)

라우터에서 히스토리모드의 사용여부 결정

where do you profer placing config for..->기본값 그대로 config 파일 선택

설정값을 config 파일과 package.json 파일 중에 어디에 저장할지 결정

save this as a preset for future projects?(Y/N) -> 기본값 N 선택

지금까지 선택한 기능을 다음 프로젝트에도 사용할지 여부

 

화살표로 위와 같이 선택한 후 엔터를 눌러 설치완료하였으면 위에서 만든 프로젝트를 VSCode에 폴더열기로 프로젝트를 열어줍니다.

프로젝트를 열면 파일구성은 아래와 같습니다.

프로젝트의 src폴더를 펼쳐 하위파일 App.vue파일을 엽니다.

App.vue 파일을 아래와 같이 수정합니다.

<!-- 첫 화면에 표시될 내용  -->
<template>
  <div id="app">
    <h1>Hello App!</h1>

    <!-- 현재 라우트에 맞는 컴포넌트가 렌더링됩니다. -->
    <router-view></router-view>
    <hr>
    <p>라우터 링크 사용 :
      <router-link to="/main">메인 페이지</router-link>
      <router-link to="/sub">서브 페이지</router-link>
    </p>
    <!-- 라우트 아울렛 -->
  </div>
</template>

src 폴더 밑에 main_page.vue 파일을 만들고 다음과 같이 작성합니다.

<!-- 메인페이지 마크업  -->
<template lang="html">
  <div>
    <h2>메인 페이지입니다.</h2>
    <button @click="fnSubPage">서브 페이지로 이동(라우터 함수 사용)</button>
  </div>
</template>

<script>
export default{
    methods:{
        fnSubPage(){
            //라우터 함수로 이동할 때 $router 글로벌 객체에 이동할주소를 넣어줌
            this.$router.push('/sub')
        }
    }
}
</script>

버튼을 누르면 라우터 뷰가 이동하도록 이벤트 핸들러를 사용하는 내용입니다. 먼저 버튼을 눌렀을때 fnSubPage() 함수를 실행하도록 v-on디렉티브로 바인딩합니다. v-on:click 대신 @click 라고 작성하였습니다.

 

fnSubPage() 함수의 내용은 $router 객체에 이동하고자 하는 컴포넌트의 경로이름 /sub을 push() 함수로 넣어 주면됩니다. main.js 파일에 정의된 router 변수에 접근하기 위해 먼저 뷰 객체를 가리키는 this를 사용합니다.

그리고 router 이름 앞에 달러 기호를 표기함으로써 뷰 객체에 정의된 router 변수값에 접근할 수 있도록 합니다.

 

이어서 폴더 src 하위에 sub_page.vue 파일을 생성하여 아래와 같이 코드를 작성합니다.

<!-- 서브 페이지 마크업  -->
<template lang="html">
  <div>
    <h2>서브 페이지입니다.</h2>
    <button @click="fnMainPage">메인 페이지로 이동(라우터 함수 사용)</button>
  </div>
</template>

<script>
export default{
    methods:{
        fnMainPage(){
            //라우터 함수로 이동할 때 $router 글로벌 객체에 이동할 주소를 넣어줌
            this.$router.push('/main')
        }
    }
}
</script>

 

마지막으로 폴더 src 하위에 router/index.js 파일을 작성해야합니다. 이 파일도 뷰 프로젝트를 생성하면, 자동으로 생깁니다. 여기서 라우터와 연결할 뷰 파일의 경로를 설정하면 됩니다.

 

import Vue from 'vue'
import VueRouter from 'vue-router'
//import Home from '../views/Home.vue'
import main_page from '../main_page.vue'
import sub_page from '../sub_page.vue'

Vue.use(VueRouter)

const routes = [
  {
    path: '/main',
    name: 'main_page',
    component: main_page
  },
  {
    path: '/sub',
    name: 'sub_page',
    component: sub_page
  }
]

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})

export default router

Router 모듈을 Vue.use()함수의 매개변수로 전달하면 Vue 객체에 사용할 수 있습니다.

Router 객체를 신규로 생성하고 필요한 옵션을 매개변수로 지정합니다.

라우터의 mode 옵션은 hash와 history 모드가 있습니다. 기본 설정은 hash 모드인데 해시 기호 # 를 기준으로 앞주소는 브라우저에 보내고 뒤 주소는 뷰가 SPA가 동작하도록 처리합니다. 'history' 모드는 해시 기호를 URL 주소에서 제거합니다. mode 옵션에 history 를 지정해서 일반적으로 알고있는 깨끗한 주소가 되게 설정합니다.

http://localhost:8080/#/main   -> http://localhost:8080/main

process.env.BASE_URL은 루드 URL의 환경변수값인데 '/'의 값을 가지고 있습니다.

그리고 라우터 속성에는 메인 페이지와 서브 페이지에 관한 경로와 컴포넌트 이름을 지정해줍니다. 이것만 지정하면 라우터를 사용하는데 필요한 준비는 모두 끝났습니다.

 

src 폴더에있는 assets, components, views 폴더는 필요없으므로 모두 삭제합니다.

완성된 src 폴더의 내용은 다음과 같습니다.

 

이제 웹팩 모듈을 설치하고 실행해 봅시다.

VSCode 터미널 창을열어 아래의 코드를 입력하여 웹팩 모듈을 설치할  폴더로 이동합니다.

* npm은 Node Packaged Manager의 약자

Node.js 패키지설치 명령어 npm install

PS D:\workspace-webapp\webpack> npm install

다음명령어를 입력하면 서버가 개발자 모드로 열립니다. 프로그램 로직과 디자인 UI가 제대로 동작하는지 결과를 확인하는용도로 이용하면좋습니다.npm run serve

PS D:\workspace-webapp\webpack> npm run serve

ctrl키를 누른채 터미널 창에 표시된 내용http://localhost:8080을 누릅니다.

웹브라우저가 실행되면서 아래와 같은 실행화면이 나타납니다.

메인페이지와 서브 페이지 를 클릭하며 라우터가 정상 동작하는지 확인해봅니다.

 

npm run serve 로 실행한 개발자 모드에서 핫 리로드라는 기능이 있어서 코드를 수정하고 저장하면 결과가 자동으로 브라우저에 반영됩니다. 실무에서 매우 편리한 기능이니 꼭 숙지하시기 바랍니다. 실습결과를 확인했으면 VSCode터미널 창에서 ctrl + C를 눌러 서버를 종료합니다.

 

이제 vue.js 심화편도 끝났습니다.

 

다음 게시글에는 뷰티파이 라는 UI프레임워크에 대해 작성하겠습니다.

수고하셨습니다. !

'WEB' 카테고리의 다른 글

[PWA] 뷰티파이 심화편  (0) 2020.10.28
[PWA] 뷰티파이 기초편  (1) 2020.10.28
[PWA] 뷰 vue.js 입문  (1) 2020.10.26
[PWA] 자바스크립트로 PWA 개발  (1) 2020.10.26
[PWA] 모던자바스크립트 ES6 입문  (1) 2020.10.23
댓글
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크