소스 : https://github.com/donghyeon0725/vue_tutorials

 

donghyeon0725/vue_tutorials

Contribute to donghyeon0725/vue_tutorials development by creating an account on GitHub.

github.com

목표 : 싱글페이지 어플리케이션(SPA)을 만들며, vue.js 가 내부적으로 어떻게 동작하는지 알아보는 것

 

 

Vue 인스턴스 생성

<script src="https://unpkg.com/vue@2.5.13/dist/vue.js"></script>
// Vue 인스턴스 내부에 전달할 객체를 options 이라고 부를 것이다.
new Vue({ /*options*/ })

뷰는 기본적으로 CLI를 사용하는 방법도 있겠지만, js 파일을 불러와 Vue 인스턴스를 생성하는 방법으로도 사용할 수 있다.

 

 

 

 

 

 

10번 줄 처럼 뷰와 양방향 바인딩을 맺을 요소를 DOM에 생성한다 생성하고 

뷰 인스턴스인 vm을 생성한다. 

그리고 options 내부의 el 프로퍼티를 이용해서 css 선택자로 선택하면 뷰를 사용할 준비가 끝난 것이다.

 

 

 

 

 

양방향 바인딩

let vm = new Vue({
	el: '#app',
	data: {
		messages: [],
		newMessage: ''
	},
    ...

 

 

초기값을 설정해야 양방향 바인딩이 수월하다.

또한 들어가야할 데이터가 어떤 것인지 한눈에 보인다.

 

컴포넌트 내부에 모듈을 넣을 때

  1. 탬플릿으로 구성한 뒤 모듈 하나하나 구현하는 방법
  2. 직접 내부에 HTML 요소 부착한 뒤에 직접 바인딩하기

 

 

직접 내부에 HTML 요소 부착한 뒤에 직접 바인딩하기

<div id="app" v-cloak>
	<ul>
		<li v-for="message in messages">
			{{ message.text.toLowerCase() }} - {{ message.createdAt }}
			<button @click="deleteMessage(message)">X</button>
		</li>
	</ul>
	<form v-on:submit.prevent="addMessage">
		<textarea v-model="newMessage" placeholder="Leave a message"></textarea>
		<div>
			<button type="submit" :disabled="addDisabled">Add</button>
		</div>
	</form>
</div>
<script src="https://unpkg.com/vue@2.5.13/dist/vue.js"></script>
<script>
    let vm = new Vue({
        el: '#app',
        data: {
            //뷰 내부적으로 이 값을 this 키워드가 참조하게 되고, 이 값들이 태그와 값을 양방향바인딩 한다.
            messages: [],
            newMessage: ''
        },
        methods: {
            addMessage(event) {
                if (!this.newMessage) {return;}
                this.messages.push({
                    text: this.newMessage, createdAt: new Date()
                });
                this.newMessage = '';
            },
            deleteMessage(message) {
                this.messages.splice(this.messages.indexOf(message),1);
            }
        },
        computed: {
            addDisabled() {
                return this.messages.length > 10 || this.newMessage.length > 50;
                //v-bind:disabled="messages.length >= 10 || this.newMessage.length > 50"
            }
        }
    });
</script>
  1. v-cloak 은 div의 속성 => 잠시 display:none 하기 위해 사용 (보간법을 위해 사용한 이중괄호를 노출시키지 않기 위함)
  2. v-for 은 반복을 지시하는 지시자. 내부의 messages는 Vue 인스턴스의 data 객체 내부의 프로퍼티를 가리킴. 뷰가 이를 랜더링한다. 리스트에서 꺼낸 값을 message(별칭)에 담아줌. 이 때 messages에 접근하기 위해 굳이 vm.messages 로 접근할 필요가 없음. 지시자를 사용해서 양방향 바인딩이 되었다.
  3. {{}} => 이중괄호. 내부의 값을 가져옴. 이때 toLowerCase와 같은 자바스크립트 표현식을 사용할 수 있다.
  4. @click은 v-on의 줄임표현. 이벤트를 달아준다.
  5. deleteMessage는 뷰 인스턴스의 methods 프로퍼티 내부의 값
  6. v-on:submit.prevent="addMessage" 으로 폼에 addMessage 함수를 부탁했는데, 이 때 submit이 자동으로 되는 것을 방지하기 위해 prevent를 덧붙였다.
  7. 그리고 textarea의 v-model 지시자를 통해서 양방향바인딩을 생성했다. 양방향 바인딩 된 객체는 vue가 데이터를 계속 보고 있다가 변화가 생기면 textarea 의 값을 변경시킬 것이다.
  8. methods를 작성할 때 화살표 함수는 사용하면 안된다. 이유는 this키워드를 통해 vm 객체에 접근할 수 없게 되기 때문이다.
  9. addMessage 내부의 this 키워드가 vm이 아닌, data를 가리키는 이유는 vue가 내부적으로 프록시패턴(대리인, wrapper)을 사용해서 실행컨택스트를 data로 바꿔 놓았기 때문이다(아마 apply 나 bind 같은 메소드로 바꾸어 놓았을 것 같음)
  10. 양방향 바인딩(vm 인스턴스 내부 데이터 변화가 생기면 DOM에 부착된 요소도 변경이 생기는 이유)이 가능한 이유는 옵저버 패턴을 통해서 와쳐라는 것이 뷰의 인스턴스를 계속 관찰하고 있기 때문이다.
  11. 양방향 바인딩 될 값이 아닌, 계산된 값을 일방향으로 바인딩 하고 싶은 경우 computed 프로퍼티를 고려해볼 수 있다. 내부에 추가해주면 된다.

 

  • index.html
<div id="app" v-cloak>
    <message-list :items="messages" @ondelete="deleteMessage"></message-list>
    <ul>
        <li v-for="message in messages">
            {{ message.text.toLowerCase() }} - {{ message.createdAt }}
            <button @click="deleteMessage(message)">X</button>
        </li>
    </ul>
</div>

<script src="https://unpkg.com/vue@2.5.13/dist/vue.js"></script>
<script type="module">
import MessageList from './components/MessageList.js'
let vm = new Vue({
    el: '#app',
    data: {
        messages: [],
        newMessage: ''
    },
    methods: {
        addMessage(event) {
            if (!this.newMessage) {return;}
            this.messages.push({
                text: this.newMessage, createdAt: new Date()
            });
            this.newMessage = '';
        },
        deleteMessage(message) {
            this.messages.splice(this.messages.indexOf(message),1);
        }
    },
    components: {
        MessageList
        /*
        MessageList: MessageList 가
        파스칼 표기법인 MessageList 가 케밥표기법인 message-list 으로 변환되고
        message-list
        위와 같은 이름이 id로 사용된다.
        * */
    }
});
</script>
  • <script type="module"> 이라는 타입으로 스크립트를 열어야 다른 모듈을 import 할 수 있다.
  • <message-list :items="messages" @ondelete="deleteMessage"></message-list> 에서 message-list라는 파스칼표기법에서 케밥표기법인 MessageList으로 작성해서 components 란 프로퍼티로 넘겨주면 태그를 모듈 속 template 속성의 태그로 랜더링 해준다.
  • 속성 items 을 이용해서 값을 자식 컴포넌트로 전달할 수 있다.
  • ondelete이란 이름의 이벤트를 만들었고, 해당 이벤트가 동작하는 시점을 자식 컴포넌트가 결정할 예정이다.

 

 

MessageList.js
import MessageListItem from "./MessageListItem.js";
export default {
    name: 'MessageList',
    template:   `
        <ol type="I">
            <message-list-item 
                :key="item.id" v-for="item in items" 
                :item="item" value="3" :itemCount="true"
                @delete="deleteMessage(item)"
            >
            </message-list-item>
        </ol>
    `,
    // 바인딩은 기본적으로 태그안에 속성값을 두는 것이고, 자식 모듈이 props를 통해 값을 가져가서 바인딩하는 경우 해당 속성을 가지게 됨
    props: {
        //고립된 모듈의 스코프 특성상, 부모 컴포넌트의 값을 전달 받을 수 있도록 해놓은 장치
        items: {
            type: Array,
            required: true
        }
    },
    methods: {
        deleteMessage(message) {
            this.$emit('ondelete', message);
        }
    },
    components: {
        MessageListItem
    }
}
  • 기본적으로 디버깅을 위해서 name이라는 속성은 추가하는 것이 좋다. 
  • 모듈이므로 export 하고 있다.
  • 자식 컴포넌트에서 또 자식 컴포넌트를 사용할 수 있다.
  • props 라는 프로퍼티로 부모 태그의 속성값을 받을 수 있다. 이때 속성의 이름과 프로퍼티의 이름이 같아야 한다. 꼭 있어야 하는 값이 아닌 경우, [items] 같이 배열의 형태로 줄 수도 있다.
  • 자식컴포넌트에서 클릭시 deleteMessage 메소드가 호출되고 이때 부모의 이벤트중 ondelete 이라는 이벤트를 호출한다. 해당 이벤트를 트리거 해주는 키워드가 $emit 이다. 한편 이벤트의 첫번 째 인자로 message를 전달하기 위해서 $emit의 두번째 인자로 message를 넣어주었다. 

 

 

MessageListItem.js
//import lifecycleLogger from '../mixins/lifecycle-logger.mixin.js';
export default {
    name: 'MessageListItem',
    //mixins: [lifecycleLogger],
    template:   `
        <li :value="value">
            {{ item.text }} - {{  item.createdAt }}
            <button @click="deleteClicked" :disabled="itemCount">X</button>
        </li>
    `,
    props: {
        item: {
            type: Object,
            required: true
        },
        itemCount: {
            type: Boolean,
            required: true
        },
        value: {
            type: String,
            required: true
        }
        //['item', 'itemCount', 'value'] => required false 일 때
    },
    methods: {
        deleteClicked() {
            this.$emit('delete');
        }
    }
}

 

'모던 웹 애플리케이션 개발 > 뷰(Vue)' 카테고리의 다른 글

3. 반응형 시스템이 가능한 이유  (0) 2021.03.05
2. 라이프 사이클  (0) 2021.03.05