
이전 게시글에서는 스크립트 링크를 html 파일 내에 작성하여 Vue 앱을 이용하였다.
이렇게 해서 브라우저에서 해당 index.html 파일을 그대로 여는 방식보다는 node.js 를 이용하여 http localhost 를 이용한 개발 방식을 구축하는것이 좀 더 큰 프로젝트를 진행할때도 용이하다.
node.js 설치
먼저 node.js 는 다른 블로그를 참고하여 설치를 진행하였다 (M1 mac 기준)
homebrew 를 이용하여 nvm (node version manager) 을 먼저 다운로드 후 nvm 을 통해 node 버전별 다운로드 및 관리를 진행
$ brew install nvm
이후 nvm 환경변수 등록
현재 zshell 을 사용하고 있으므로 .zshenv 파일을 작성
$ vi ~/.zshenv
vi 편집기에 진입 후 아래 코드를 입력
export NVM_DIR="$HOME/.nvm"
[ -s "/opt/homebrew/opt/nvm/nvm.sh" ] && \. "/opt/homebrew/opt/nvm/nvm.sh" # This loads nvm
[ -s "/opt/homebrew/opt/nvm/etc/bash_completion.d/nvm" ] && \. "/opt/homebrew/opt/nvm/etc/bash_completion.d/nvm" # This loads nvm bash_completion
환경변수 적용
$ source ~/.zshenv
이후 nvm 명령어를 사용하여 lts 버전의 node 를 다운로드해주자
$ nvm install --lts

node 와 npm 모두 lts 버전으로 정상적으로 설치완료!
Vue CLI 설치
공식사이트를 참고하여 설치
Vue CLI 를 이용하면 리액트 초기 앱을 구축하는것과 같이
vue 커맨드를 이용하여 초기 vue 앱을 간단히 설정가능하다.
또한 .vue 확장자의 컴포넌트 파일 관리도 용이해진다.
npm install -g @vue/cli
# OR
yarn global add @vue/cli
npm 이나 yarn 패키지 매니저를 사용하여 VueCLI 설치
이후엔 vue create <app-name> 명령어로 vue app 을 생성 가능

이후 여러 선택 안내문이 나오는데, 사용할 vue 버전에 따라 선택 후
특별히 같이 설치할 패키지가 없다면 기본 상태로 설치 진행

설치가 완료되면 React 와 유사하게 package.json, node 모듈 폴더, public 및 src 폴더가 생성된다.
초기 create 명령어로 생성된 vue app 은 node 모듈 폴더도 자동 생성하나, GitHub 와 같은 곳에서 공유받은 파일은 크기가 상대적으로 큰 모듈 폴더가 제외되어 있으므로
npm install명령어로 패키지파일 설치를 진행해주자.
이후 npm run serve 명령을 통해 개발용 localhost 서버를 띄운 후
(아마도 기본은 8080 포트)
개발을 진행하면 된다!
하나의 HTML 페이지 내에서 여러 독립적인 Vue 앱을 생성 후 제어할 수 있다.
반면에 SPA(single page application) 를 구축하는 경우 일반적으로
하나의 '루트 앱' 으로 작업하고 여러 컴포넌트를 사용하여 사용자 인터페이스를 개발한다.
Vue 앱은 서로 독립적이므로 통신이 불가능(앱 간에 데이터 공유 및 업데이트 불가)
이에 반해 컴포넌트는 컴포넌트 간에 데이터를 교환할 수 있는 특정 통신 매커니즘이 존재.
따라서 여러 컴포넌트를 연결시켜 하나의 루트 앱으로 작업해도 하나의 연결된 UI 를 구축 가능함.
<template>
<li>
<h2>{{ name }}</h2>
<button @click="toggleDetails">
{{ detailsAreVisible ? 'Hide' : 'Show' }} Details
</button>
<ul v-if="detailsAreVisible">
<li>
<strong>Phone:</strong>
{{ phoneNumber }}
</li>
<li>
<strong>Email:</strong>
{{ emailAddress }}
</li>
</ul>
</li>
</template>
<script>
export default {
data() {
return {
detailsAreVisible: false,
friend: {
id: 'manuel',
name: 'Manuel Lorenz',
phone: '0123 45678 90',
email: 'manuel@localhost.com',
},
};
},
methods: {
toggleDetails() {
this.detailsAreVisible = !this.detailsAreVisible;
},
},
};
</script>
<style>
...
</style>
컴포넌트 사용 간단 예시
React 에서의 컴포넌트와 유사하게 사용자 정의 커스텀 태그를 만들고, 해당 태그 내에서
관리할 데이터 프로퍼티나, 로직을 설정해주면 된다.
template 영역은 로직이 출력될 html 코드 부분을 작성하고 한 컴포넌트 내에서는 해당 script 영역과 로직, 데이터를 공유한다.
script 영역은 vue app 구성 객체와 같이 해당 컴포넌트내에서 사용될 data, methods, computed 프로퍼티등을 작성한다.
style 영역은 css 에 사용된다.
import { createApp } from 'vue';
import App from './App.vue';
import FriendContact from './components/FriendContact.vue';
const app = createApp(App);
app.component('friend-contact', FriendContact);
app.mount('#app');
이렇게 작성된 컴포넌트는 최상위 부모 컴포넌트인 App.vue 에서 관리되도록 하며, main.js 파일에서 index.html 파일과 마운트 및 vue 메인 앱 - 컴포넌트 간의 연결을 설정한다.
app.component(_) 코드로 작성하며, 첫번째 인자는 컴포넌트 이름을 지정
이때 '-' 대시 기호를 사용하여 두개의 문자로 지어주어야 한다.(기본 HTML 태그와의 중복 방지)
두번째 인자로 프로퍼티와 로직을 선언한 객체(컴포넌트)를 전달해준다. (.vue 확장자인 컴포넌트 파일 전달)
이렇게 컴포넌트를 활용하면 각 컴포넌트가 캡슐화되어 독립적으로 작용하게 된다.
UI 를 컴포넌트별로 분리하여 구현하였더라도 각 컴포넌트끼리 통신이 필요한 경우가 있다.
해당 경우에 사용될 props 와 이벤트처리 emits 에 대해 알아보자.
<template>
<section>
<header>
<h1>My Friends</h1>
</header>
<new-friend @add-contact="addContact"></new-friend>
<ul>
<friend-contact
v-for="friend in friends"
:key="friend.id"
:id="friend.id"
:name="friend.name"
:phone-number="friend.phone"
:email-address="friend.email"
:is-favorite="friend.isFavorite"
@toggle-favorite="toggleFavoriteStatus"
@delete-contact="deleteContact"
></friend-contact>
</ul>
</section>
</template>
먼저 App.vue 파일을 살펴보면 최상위 부모 컴포넌트로써 자식 커스텀 태그인
friend-contact, new-friend 컴포넌트를 지니고 있다.
이때 부모 -> 자식으로 단방향 데이터 전달이 가능한데 이때 사용되는것이 props 이다.
현재 위 코드에선 friend-contact 컴포넌트에 동적으로 key, id, name, ..., is-favorite 등 여러 props 를 전달하고 있다.
<template>
<li>
<h2>{{ name }} {{ isFavorite ? '(favorite)' : '' }}</h2>
<button @click="toggleFavorite">Toggle Favorite</button>
<button @click="toggleDetails">
{{ detailsAreVisible ? 'Hide' : 'Show' }} Details
</button>
<ul v-if="detailsAreVisible">
<li>
<strong>Phone:</strong>
{{ phoneNumber }}
</li>
<li>
<strong>Email:</strong>
{{ emailAddress }}
</li>
</ul>
<button @click="$emit('delete-contact', id)">Delete</button>
</li>
</template>
<script>
export default {
// props: ['name', 'phoneNumber', 'emailAddress', 'isFavorite'],
props: {
id: {
type: String,
required: true,
},
name: {
type: String,
required: true,
},
phoneNumber: {
type: String,
required: true,
},
emailAddress: {
type: String,
required: true,
},
isFavorite: {
type: Boolean,
required: false,
default: false,
// validator: function (value) {
// return value === '1' || value === '0';
// },
},
},
emits: ['toggle-favorite', 'delete-contact'],
data() {
return {
detailsAreVisible: false,
friend: {
id: 'manuel',
name: 'Manuel Lorenz',
phone: '0123 45678 90',
email: 'manuel@localhost.com',
},
};
},
methods: {
toggleDetails() {
this.detailsAreVisible = !this.detailsAreVisible;
},
toggleFavorite() {
this.$emit('toggle-favorite', this.id);
},
},
};
</script>
friend-contact 컴포넌트는 script 영역에서 props 프로퍼티로 해당 props 를 받을 수 있는데, 이때 받을 타입과 필수 여부를 검증할 수 있다.
다만 문제점이 있는데, 데이터의 흐름방향은 단방향이기 때문에 자식에서 변경사항이 있을 경우, 부모 컴포넌트에게 전달할 수가 없다는 것!
이럴 때 사용하는 방법으로 이벤트 발생기, 즉 emits 이 있다.
위 코드에서는 즐겨찾기 친구와, 특정 친구의 연락처를 삭제하는 기능을 emits 으로 구현하였다.
emits 은 vue 내장객체로 $ 달러기호를 사용하여 호출하고, 상위 컴포넌트에 사용자가 지정한 문자열 이름으로 된 이벤트를 전달한다. 이때 매개변수도 같이 넘길 수 있다.
data 프로퍼티와 같이 emits 프로퍼티를 작성하면 개발 시, 해당 컴포넌트가 어떠한 이벤트를 발생시키고 있는지 확인할 수 있어 편리하다.
<template>
<section>
<header>
<h1>My Friends</h1>
</header>
<new-friend @add-contact="addContact"></new-friend>
<ul>
<friend-contact
v-for="friend in friends"
:key="friend.id"
:id="friend.id"
:name="friend.name"
:phone-number="friend.phone"
:email-address="friend.email"
:is-favorite="friend.isFavorite"
@toggle-favorite="toggleFavoriteStatus"
@delete-contact="deleteContact"
></friend-contact>
</ul>
</section>
</template>
<script>
export default {
data() {
return {
friends: [
{
id: 'manuel',
name: 'Manuel Lorenz',
phone: '0123 45678 90',
email: 'manuel@localhost.com',
isFavorite: true,
},
{
id: 'julie',
name: 'Julie Jones',
phone: '0987 654421 21',
email: 'julie@localhost.com',
isFavorite: false,
},
],
};
},
methods: {
toggleFavoriteStatus(friendId) {
const toggleFriend = this.friends.find(
(friend) => friend.id === friendId
);
toggleFriend.isFavorite = !toggleFriend.isFavorite;
},
deleteContact(friendId) {
const newFriendList = this.friends.filter(
(friend) => friend.id !== friendId
);
this.friends = newFriendList;
},
},
};
</script>
App.vue 파일에서 v-on (@ 기호) 를 이용하여 지정한 이름의 이벤트를 감지할 수 있고, 첨부된 매개변수를 이용하여 특정 요소를 삭제하는 로직도 구현이 가능하다.
이렇게 기본적으로 컴포넌트간 통신은 props, emits 을 이용하여 이루어진다.
하지만 컴포넌트내에 또 컴포넌트가 있고, 징검다리형태로 데이터가 전달되는데 해당 징검다리가 여러개, 즉 데이터를 전달하는 컴포넌트와 사용할 컴포넌트의 거리가 멀때 provide, inject 라는 내장 기능을 활용할 수 있다.
<template>
<div>
<active-element
:topic-title="activeTopic && activeTopic.title"
:text="activeTopic && activeTopic.fullText"
></active-element>
<knowledge-base></knowledge-base>
</div>
</template>
<script>
export default {
data() {
return {
topics: [
{
id: 'basics',
title: 'The Basics',
description: 'Core Vue basics you have to know',
fullText:
'Vue is a great framework and it has a couple of key concepts: Data binding, events, components and reactivity - that should tell you something!',
},
{
id: 'components',
title: 'Components',
description:
'Components are a core concept for building Vue UIs and apps',
fullText:
'With components, you can split logic (and markup) into separate building blocks and then combine those building blocks (and re-use them) to build powerful user interfaces.',
},
],
activeTopic: null,
};
},
provide() {
return {
topics: this.topics,
};
},
};
</script>
<template>
<section>
<h2>Select a Topic</h2>
<knowledge-grid></knowledge-grid>
</section>
</template>
<script>
export default {};
</script>
<template>
<ul>
<knowledge-element
v-for="topic in topics"
:key="topic.id"
:id="topic.id"
:topic-name="topic.title"
:description="topic.description"
></knowledge-element>
</ul>
</template>
<script>
export default {
inject: ['topics'],
};
</script>
다른 예시 파일의 컴포넌트를 보자.
여기서 knowledgeBase.vue 컴포넌트는 사실상 중간다리에 불과하고
실제로는 knowledgeGrid.vue 에서 topics 가 사용되므로
이때 provide, inject 를 사용하여 먼 거리의 통신을 간단하게 수행할 수 있다.
그럼 props, emits 대신 모두 다 provide, inject 를 사용하는건 어떨까?
물론 provide, inject 를 사용하여 중간다리를 건너뛰고 실제 사용되는곳에서만 사용되는건 굉장히 합리적이고 간결한 코드 작성이 될 수 있다.
하지만 props, emits 는 기본적인 컴포넌트간 통신 매커니즘이고, provide, inject 를 남용하게 되면 간단한 프로젝트면 몰라도 규모가 클 경우 어디에서 어디로 참고가 되고있는지 컴포넌트를 싹 다 확인해야 하기에
징검다리의 개수가 정말 많거나(컴포넌트간 거리가 멀거나) 할때만 사용하는 것이 좋을 것 같다.
VueCLI - installation
[Node.js] Mac 맥(m1) - homebrew, node, npm, nvm 설치 관련