Vue

황현근·2023년 6월 20일
0

Acorn-Academy

목록 보기
27/28
post-thumbnail

Vue.js란 무엇인가?

MVVM 패턴의 ViewModel 레이어에 해당하는 화면단 라이브러리

  • 데이터 바인딩화면 단위를 컴포넌트 형태로 제공하며, 관련 API 를 지원하는데에 궁극적인 목적이 있음
  • Angular에서 지원하는 양방향 데이터 바인딩을 동일하게 제공
  • 하지만 컴포넌트 간 통신의 기본 골격은 React의 단방향 데이터 흐름(부모 -> 자식)을 사용
  • 다른 프런트엔드 프레임워크(Angular, React)와 비교했을 때 상대적으로 가볍고 빠름.

💡 MVVM 패턴이란?

Backend 로직과 Client 의 마크업 & 데이터 표현단을 분리하기 위한 구조로 전통적인 MVC 패턴의 방식에서 기인하였다. 간단하게 생각해서 화면 앞단의 화면 동작 관련 로직과 뒷단의 DB 데이터 처리 및 서버 로직분리하고, 뒷단에서 넘어온 데이터를 Model 에 담아 View 로 넘겨준다.

Vue.js 시작하기

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

📌 Vue Instance 생성자

Vue를 사용할 때 필수로 생성이 필요한 코드
인스턴스 생성 시 Vue 개발자 도구에서 Root 컴포넌트로 인식

new Vue({
  // instance option properties
  template: "",
  el: "#app",
  data: {}
  methods: {}
  created: function() {}
});
  • el: app이라는 ID를 가진 태그를 찾아서 인스턴스를 붙여준다. 태그에 인스턴스를 붙여주면 view의 기능과 속성을 조작 가능
  • data: 뷰의 반응성(Reactivity)이 반영된 데이터 속성
  • template : 화면에 표시할 요소 (HTML, CSS 등)
  • methods : 화면의 동작과 이벤트 로직을 제어하는 메서드
  • created : 뷰의 라이프 사이클과 관련된 속성
  • watch : data에서 정의한 속성이 변화했을 때 추가 동작을 수행할 수 있게 정의하는 속성

📌 Components

화면의 영역을 영역별로 구분해서 코드로 관리

  • 화면의 영역을 구분하여 개발할 수 있는 뷰의 기능
  • 코드의 재사용성이 올라가고 빠른 화면 제작 가능
  • 전역 컴포넌트는 기본적으로 모든 인스턴스에 등록이 되어 있음
  • 지역 컴포넌트는 인스턴스마다 새로 생성이 필요
  • 다만 서비스를 구현할 때는 하나의 인스턴스에 컴포넌트를 붙여 나가는 형식으로 진행

    예시
<h1>component 사용예제</h1>
<div id="app">
    <h2>친구 목록 입니다.</h2>
    <friends-component v-bind:friends="friends"></friends-component>

    <h2>동물 친구 목록 입니다.</h2>
    <friends-component v-bind:friends="friends2"></friends-component>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
    let app = new Vue({
        el        : "#app",
        data      : {
            friends : ["김구라", "해골", "원숭이"],
            friends2: ["강아지", "고양이", "두더지"]
        },
        components: {
            "friends-component": {
                template: `
                    <ul>
                    <li v-for="item in friends">{{ item }}</li>
                    </ul>
                `,
                props   : ["friends"]
            }
        }
    });
</script>


📌 props

컴포넌트 간에 데이터를 전달할 수 있는 컴포넌트 통신 방법
상위 컴포넌트에서 하위 컴포넌트로 내려보내는 데이터 속성

상위 컴포넌트의 템플릿

<!-- 상위 컴포넌트 -->
<div id="app">
  <!-- 하위 컴포넌트에 상위 컴포넌트가 갖고 있는 값을 전달함 -->
  <child-component v-bind:프롭스 속성 명="상위 컴포넌트의 data 속성"/>
</div>

하위 컴포넌트의 컴포넌트

var childComponent = {
  props: ['프롭스 속성 명']
}

예 시

<body>
  <div id="app">
    <!-- <app-header v-bind:프롭스 속성 이름="상위 컴포넌트의 데이터 이름"></app-header> -->
    <app-header v-bind:propsdata="message"></app-header>
    <app-content v-bind:propsdata="num"></app-content>
  </div>

  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script>
    var appHeader = {
      template: '<h1>{{ propsdata }}</h1>',
      props: ['propsdata'] // 하위 컴포넌트의 프롭스 속성명
    }
    var appContent = {
      template: '<div>{{ propsdata }}</div>',
      props: ['propsdata'] // 하위 컴포넌트의 프롭스 속성명
    }

    new Vue({
      el: '#app',
      components: {
        'app-header': appHeader,
        'app-content': appContent
      },
      data: {
        message: 'hi',
        num: 10
      }
    })
  </script>
</body>

같은 레벨의 컴포넌트 간 통신

동일한 상위 컴포넌트를 가진 하위 컴포넌트들 간의 통신은 아래와 같이 해야 한다.

  • Child(하위) -> Parent(상위) -> Children(하위 2개)

📢 컴포넌트 간의 직접적인 통신은 불가능하도록 되어 있는게 Vue 의 기본 구조


📌 Event Bus

상위 ↔ 하위 관계가 아닌 컴포넌트 간의 통신을 위해 Event Bus를 활용할 수 있다.

Event Bus를 사용하기 위해 새로운 뷰 인스턴스를 아래와 같이 생성한다.

// 화면 개발을 위한 인스턴스와 다른 별도의 인스턴스를 생성하여 활용
var eventBus = new Vue();

new Vue({
  // ...
});

이벤트를 발생시킬 컴포넌트에서 $emit() 호출

eventBus.$emit("refresh", 10);

이벤트를 받을 컴포넌트에서 $on() 이벤트 수신

// 이벤트 버스 이벤트는 일반적으로 라이프 사이클 함수에서 수신
new Vue({
  created: function() {
    eventBus.$on("refresh", function(data) {
      console.log(data); // 10
    });
  }
});

만약, eventBus의 콜백 함수 안에서 해당 컴포넌트의 메서드를 참고하려면 vm 사용

new Vue({
  methods: {
    callAnyMethod() {
      // ...
    }
  },
  created() {
    var vm = this;
    eventBus.$on("refresh", function(data) {
      console.log(this); // 여기서의 this는 이벤트 버스용 인스턴스를 가리킴
      vm.callAnyMethod(); // vm은 현재 인스턴스를 가리킴
    });
  }
});

사용 예시 1

<body>
<h1>자식 component 에서 발생하는 이벤트</h1>
<div id="app">
    <my-component v-on:mom="feed"></my-component>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
    Vue.component("my-component", {
        template: `
            <div>
            <button v-on:click="callMom">엄마!</button>
            </div>
        `,
        methods : {
            callMom() {
                // this.$emit("이벤트명", 전달할 data)
                this.$emit("mom", "배고파");
            }
        }
    });

    let app = new Vue({
        el     : "#app",
        methods: {
            feed(data) {
                alert(data);
            }
        }
    });
</script>
</body>

사용 예시 2

<body>
<h1>event emit 예제</h1>
<div id="app">
    <!-- list 라는 props "delete" 이벤트, "update" 이벤트가 특정 시점에 발생한다. -->
    <friend-component
        v-bind:list="members"
        v-on:delete="deleteMember"
        v-on:update="updateMember"></friend-component>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
    Vue.component("friend-component", {
        template: `
            <ul>
            <li v-for="(item, index) in list">
                {{ item }}
                <button v-on:click="updateItem(index)">수정</button>
                <button v-on:click="deleteItem(index)">삭제</button>
            </li>
            </ul>
        `,
        props   : ["list"],
        methods : {
            deleteItem(i) {
                this.$emit("delete", i);
            },
            updateItem(i) {
                const newName = prompt("수정할 이름을 입력하세요");

                //this.$emit("update", {i:i, newName:newName});
                this.$emit("update", {i, newName});
            }
        }
    });

    let app = new Vue({
        el     : "#app",
        data   : {
            members: ['김구라', '해골', '원숭이']
        },
        methods: {
            deleteMember(index) {
                //push 또는 splice 하면 배열의 방의 사이즈가 변경이 되기 때문에 UI 업데이트가 자동으로 된다.
                //this.members.splice(index, 1);
                //{ } 없이 return 예약어 없이 리턴할 값만 작성해도 된다.

                //새로운 배열의 참조값으로 덮어쓰면 모델이 변경된 것이기 때문에 UI 업데이트가 자동으로 된다.
                this.members = this.members.filter((item, i) => index != i);
            },
            updateMember(data) {
                //아래처럼 배열을 변경하면 변경이 감지가 안되기 때문에 화면 업데이트가 안된다.
                //this.members[data.i] = data.newName;

                //아래의 2가지 방법중 하나로 배열을 변경해야 한다.
                //Vue.set(this.members, data.i, data.newName);
                // $set(수정할 배열의 참조 값, 수정할 인덱스, 수정할 값)
                //this.$set(this.members, data.i, data.newName);

                // this.members = 아이템이 수정된 새로운 배열의 참조값

                //item 이 수정된 새로운 배열의 참조값을 얻어내서 모델을 변경해도 화면 update 가 일어난다.
                this.members = this.members.map(() => {
                    //만일 수정할 index 라면
                    if (index == data.i) {
                        //수정할 데이터를 리턴해준다.
                        return data.newName;
                    }
                    return item;
                });
            }
        }
    });
</script>
</body>

📌 Example

사용자 입력 폼 예시

<template>
  <!-- button의 submit 동작 시 submitForm 메서드 호출-->
  <form v-on:submit.prevent="submitForm">
    <div>
      <label for="username">id: </label>
      <input id="username" type="text" v-model="username">
    </div>
    <div>
      <label for="password">pw: </label>
      <input id="password" type="password" v-model="password">
    </div>
    <button type="submit">login</button>
  </form>
</template>

<script>
import axios from 'axios';

export default {
  data: function() {
    return {
      username: '',
      password: '',
    }
  },
  methods: {
    submitForm: function() {
      // event.preventDefault(); -> form 의 새로고침/이동 동작을 막기 위해 사용했던 것을 v-on:submit.prevent 로 해결
      var url = '';
      var data = {
        username: this.username,
        password: this.password
      }
      axios.post(url, data)
        .then(function(response) {
          console.log(response);
        })
        .catch(function(error) {
          console.log(error);
        });
    }
  }
}
</script>

📖 Axios

뷰에서 권고하는 HTTP 통신 라이브러리이다.
Promise 기반(JS 비동기 처리 패턴)이며 상대적으로 다른 HTTP 통신 라이브러리들에 비해 문서화가 잘되어 있고 API가 다양하다

Install Axios

<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

사용 예시

<body>
  <div id="app">
    <button v-on:click="getData">get user</button>
    <div>
      {{ users }}
    </div>
  </div>

  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
  <script>
    new Vue({
      el: '#app',
      data: {
        users: []
      },
      methods: {
        getData: function() { 
          var vm = this;
          axios.get('https://jsonplaceholder.typicode.com/users/')
            .then(function(response) {
              console.log(response.data);
              vm.users = response.data;
            })
            .catch(function(error) {
              console.log(error);
            });
        }
      }
    })
  </script>
</body>

fetch 를 이용한 json 요청

<body>
<h1>ajax 요청을 통해서 받아온 데이터 사용하기</h1>
<p>페이지 전환 없이 서버에 요청하는 것을 ajax 라고 생각하면 된다.</p>
<div id="app">
    <br>
    <table>
        <thead>
        <tr>
            <th>번호</th>
            <th>작성자</th>
            <th>제목</th>
        </tr>
        </thead>
        <tbody>
        <!-- 글번호는 pk 이기 때문에 -->
        <tr v-bind:key="tmp.num" v-for="tmp in list">
            <td>{{tmp.num}}</td>
            <td>{{tmp.writer}}</td>
            <td>{{tmp.title}}</td>
        </tr>
        </tbody>
    </table>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
    new Vue({
        el  : "#app",
        data: {
            list: [],
        },
        created() {
            //Vue 가 준비가 되었을 때 (root component 가 준비되었을 때) 최초 한 번 호출된다.
            //fetch 를 이용해서 서버에 데이터를 요청한다.
            fetch("../cafe/json_list.jsp")
                .then(res => res.json())
                .then(data => {
                    //data 는 글목록이 들어 있는 배열이다.
                    this.list = data;
                });
        },
        methods: {},
    })
</script>
</body>

0개의 댓글