vue.js
Vue Component
코딩 화이팅
2023. 5. 4. 16:23
JavaScript Module
Module
- 프로그램을 기능별로 여러 개의 파일로 나누는 형태
- 브라우저의 지원 여부 확인
Module 시스템
- CommonJS(NodeJS)-웹 브라우저 밖에서도 동작될 수 있는 모듈 규칙 설립
- AMD(Asynchronous Module Definition)-비동기적으로 모듈을 로딩
- ESM(ECMAScript Module, ECMA215, es6)-자바스크립트 자체 모듈
Module 정의 및 사용
- 가져오기 : import
- 내보내기 : export
module1.js
const title = "계산기 모듈";
function add(i, j) {
return i + j;
}
function sub(i, j) {
return i - j;
}
export { title, add, sub };
=======================================================================
test01.js
import { title, add, sub } from "./module1.js";
console.log(title);
const app = new Vue({
el: "#app",
data() {
return {
num1: 0,
num2: 0,
op: "-",
result: 0,
};
},
methods: {
doCal() {
if (this.op === "+") this.result = add(this.num1, this.num2);
else this.result = sub(this.num1, this.num2);
},
},
watch: {
// watch : 감시자
op(val) {
if (val === "+") this.result = add(this.num1, this.num2);
else this.result = sub(this.num1, this.num2);
},
},
});
=======================================================================
test01.html
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.13/dist/vue.js"></script>
<title>Vue</title>
</head>
<body>
<div id="app">
<input type="text" v-model.number="num1">
<select v-model="op">
<option value="+">+</option>
<option value="-">-</option>
</select>
<input type="text" v-model.number="num2">
<button @click="doCal">계산</button>
<div>{{result}}</div>
</div>
<script type="module" src="./test01.js"></script>
</body>
</html>
module2.js
export default {
title: "계산기 모듈",
add(i, j) {
return i + j;
},
sub(i, j) {
return i - j;
},
};
==================================================================
test02.js
import cal from "./module2.js";
const app = new Vue({
el: "#app",
data() {
return {
num1: 0,
num2: 0,
op: "-",
result: 0,
};
},
methods: {
doCal() {
if (this.op === "+") this.result = cal.add(this.num1, this.num2);
else this.result = cal.sub(this.num1, this.num2);
},
},
watch: {
op(val) {
if (val === "+") this.result = cal.add(this.num1, this.num2);
else this.result = cal.sub(this.num1, this.num2);
},
},
});
==================================================================
test02.html
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.13/dist/vue.js"></script>
<title>Vue</title>
</head>
<body>
<div id="app">
<input type="text" v-model.number="num1">
<select v-model="op">
<option value="+">+</option>
<option value="-">-</option>
</select>
<input type="text" v-model.number="num2">
<button @click="doCal">계산</button>
<div>{{result}}</div>
</div>
<script type="module" src="./test02.js"></script>
</body>
</html>
Vue Component
- Vue의 가장 강력한 기능 중 하나
- 기본 HTML 엘리먼트를 확장하여 재사용 가능한 코드를 캡슐화하는데 도움이 됨
- VueComponent는 Vue Instance이기도함.
(루트에서만 사용하는 옵션을 제외하고 모든 옵션 객체 사용 가능) - Life Cycle Hook 사용 가능
- 전역 컴포넌트와 지역 컴포넌트 사용
Vue Component Template
- DOM을 템플릿으로 사용할 때, Vue는 템플릿 콘텐츠만 가져올 수 있기 때문에 HTML이 작동하는 방식에 고유한 몇 가지 제한 사항이 적용됨
- 가능하다면 문자열 템플릿을 사용하는 것이 좋다.
Vue Component(전역)
- 전역 컴포넌트를 등록하려면 Vue.Component(tagName, options)를 사용
- 권장하는 컴포넌트 이름 : 케밥 케이스
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<title>Vue</title>
</head>
<body>
<!-- 전역컴포넌트 -->
<div id="app">
<my-global></my-global>
<my-global></my-global>
<my-global></my-global>
</div>
<div id="app2">
<my-global></my-global>
</div>
<script>
// Vue.component('comp-name', {옵션})
Vue.component("my-global", {
template: "<h2>전역 컴포넌트</h2>",
// template : 안에 있는 내용을 실제로 붙여주는 옵션
});
// my-global이라는 컴포넌트를 하나 만들어놈
const app = new Vue({
el: "#app",
});
const app2 = new Vue({
el: "#app2",
});
</script>
</body>
</html>
Vue Component(지역)
- 모든 컴포넌트를 전역으로 등록할 필요X
- 컴포넌트를 components 인스턴스 옵션으로 등록함으로써 다른 인스턴스/컴포넌트의 범위에서만 사용할 수 있는 컴포넌트 생성 가능
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<title>Vue</title>
</head>
<body>
<!-- 지역컴포넌트 -->
<div id="app">
<my-local></my-local>
<my-local></my-local>
</div>
<!-- 이건 인식할수 없어~~ -->
<div id="app2">
<my-local></my-local>
</div>
<script>
const app = new Vue({
el: '#app',
components: {
'my-local': {
template: '<h2>지역컴포넌트</h2>',
},
},
});
const app2 = new Vue({
el: '#app2',
});
</script>
</body>
</html>
Vue Component data
- data는 반드시 함수여야 한다.
- data 객체를 공유하는 문제를 막고 새로운 데이터 객체를 반환해서 해결한다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<title>Vue</title>
</head>
<body>
<div id="app">
<my-comp></my-comp>
</div>
<template id="mycomponent">
<!-- <div>얍</div> 이거 안돼 최상위 태그가 하나 있어야한다. -->
<div>
<h2>{{msg}}</h2>
</div>
</template>
<script>
// 컴포넌트에서 객체형태로는 data를 사용하면 안된다.
// Vue.component('my-comp', {
// template: '#mycomponent',
// data: {
// msg: 'hello',
// },
// });
Vue.component("my-comp", {
template: "#mycomponent",
data() {
return {
msg: "hello",
};
},
});
const app = new Vue({
el: "#app",
});
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<title>Vue</title>
</head>
<body>
<div id="app">
<count-view></count-view>
</div>
<template id="count-view">
<div>
<h2>{{count}}</h2>
<button @click="count++">클릭</button>
</div>
</template>
<script>
let data = {
count: 0,
};
Vue.component("count-view", {
template: "#count-view",
data() {
return data;
},
});
const app = new Vue({
el: "#app",
});
</script>
</body>
</html>
클릭하면 숫자가 오른다.
버튼을 두 개 만들어 클릭을 눌러준다면?
나머지 코드는 위와 동일
<body>
<div id="app">
<count-view></count-view>
<count-view></count-view>
</div>
한 클릭만 눌러도 두 개가 같이 올라간다.
let data = {
count: 0,
};
여기서 한 개의 데이터를 공유를 하고 있기 때문에 두 개는 하나의 데이터를 공유하고 있다.
이것을 방지하기 위해
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<title>Vue</title>
</head>
<body>
<div id="app">
<count-view></count-view>
<count-view></count-view>
</div>
<template id="count-view">
<div>
<h2>{{count}}</h2>
<button @click="count++">클릭</button>
</div>
</template>
<script>
// let data = {
// count: 0,
// };
// 원래 이렇게 있던 걸 밑에 return값에 넣어줘 새로운 객체가 만들어지게 해준다.
Vue.component('count-view', {
template: '#count-view',
data() {
return {
count: 0,
};
},
});
const app = new Vue({
el: '#app',
});
</script>
</body>
</html>
원래 let으로 만들었던 객체 하나를 return 값에 넣어주어 새로운 객체를 만들어 다른 객체를 사용할 수 있게끔 해준다.
Vue Component 통신
- 컴포넌트는 부모-자식 관계에서 가장 일반적으로 함께 사용하기 위한 것(트리 구조)
- 부모는 자식에게 데이터를 전달(Pass Props)
- 자식은 부모에게 일어난 일을 알림(Emit Event)
- 부모와 자식이 명확하게 정의된 인터페이스를 통해 격리된 상태 유지
- props는 아래로, events는 위로
부모 컴포넌트-> 자식 컴포넌트
- 상위 컴포넌트에서 하위 컴포넌트로 데이터 전달
- 하위 컴포넌트는 상위 컴포넌트의 값을 직접 참조 불가능
- data와 마찬가지로 props 속성의 값을 template에서 사용 가능
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<title>Vue</title>
</head>
<body>
<!-- 부모 -->
<div id="app">
<h2>부모컴포넌트</h2>
<!-- 자식 컴포넌트를 넣는다. -->
<!-- 정적 props -->
<child-view propsdata="정적인메시지"></child-view>
</div>
<!-- 자식 -->
<template id="child-view">
<div>
<h3>자식컴포넌트</h3>
<div>{{propsdata}}</div>
</div>
</template>
<script>
Vue.component("child-view", {
template: "#child-view",
props: ["propsdata"],
});
const app = new Vue({
el: "#app",
data() {
return {
msg: "",
};
},
});
</script>
</body>
</html>
Dynamic Props
- v-bind를 사용하여 부모의 데이터에 props를 동적으로 바인딩할 수 있음
- 데이터가 상위에서 업데이트 되루 때마다 하위 데이터로도 전달
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<title>Vue</title>
</head>
<body>
<!-- 부모 -->
<div id="app">
<h2>부모컴포넌트</h2>
<!-- 자식 컴포넌트를 넣는다. -->
<!-- 정적 props -->
<child-view propsdata="정적인메시지"></child-view>
<!-- 동적 props -->
<input type="text" v-model="msg" />
<child-view :propsdata="msg"></child-view>
</div>
<!-- 자식 -->
<template id="child-view">
<div>
<h3>자식컴포넌트</h3>
<div>{{propsdata}}</div>
</div>
</template>
<script>
Vue.component("child-view", {
template: "#child-view",
props: ["propsdata"],
});
const app = new Vue({
el: "#app",
data() {
return {
msg: "",
};
},
});
</script>
</body>
</html>
Dynamic Props 객체의 속성 전달
- 객체의 모든 속성을 props로 전달할 경우 인자없이 v-bind를 사용
- 위의 코드는 아래와 같이 동작
단방향 데이터 흐름
- 모든 props는 하위 속성과 상위 속성 사이의 단방향 바인딩을 형성
- 상위 속성이 업데이트되면 하위로 흐르게 되지만 반대로는 안됨
- 하위 컴포넌트가 실수로 부모의 상태를 변경하여 앱의 데이터 흐름을 이해하기 어렵게 만드는 일 방지
- 상위 컴포넌트가 업데이트 될 때마다 하위 컴포넌트의 모든 prop들이 최신 값으로 갱신
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<title>Vue</title>
</head>
<body>
<div id="app">
<h2>부모컴포</h2>
<child-view
area="속초"
:msg="msg[parseInt(Math.random()*5)]"
></child-view>
<child-view
area="일본"
:msg="msg[parseInt(Math.random()*5)]"
></child-view>
<child-view
area="수유"
:msg="msg[parseInt(Math.random()*5)]"
></child-view>
<child-view
area="부산"
:msg="msg[parseInt(Math.random()*5)]"
></child-view>
<child-view
area="통영"
:msg="msg[parseInt(Math.random()*5)]"
></child-view>
</div>
<template id="child-view">
<div>
<h3>{{area}} 지역</h3>
<div>{{msg}}</div>
</div>
</template>
<script>
Vue.component('child-view', {
template: '#child-view',
props: ['area', 'msg'],
});
const app = new Vue({
el: '#app',
data() {
return {
msg: [
'오향족발먹으로',
'고양이를 보러',
'경섭이가 놀러가서',
'라면먹으로',
'예비군하러',
],
};
},
});
</script>
</body>
</html>
위의 코드를 줄일 수 있다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<title>Vue</title>
</head>
<body>
<div id="app">
<h2>부모컴포</h2>
<child-view
v-for="area in areas"
:area2="area"
:msg="msg[parseInt(Math.random()*5)]"
></child-view>
</div>
<template id="child-view">
<div>
<h3>{{area2}} 지역</h3>
<div>{{msg}}</div>
</div>
</template>
<script>
Vue.component('child-view', {
template: '#child-view',
props: ['area2', 'msg'],
});
const app = new Vue({
el: '#app',
data() {
return {
areas: ['속초', '일본', '수유', '부산', '통영'],
msg: [
'오향족발먹으로',
'고양이를 보러',
'경섭이가 놀러가서',
'라면먹으로',
'예비군하러',
],
};
},
});
</script>
</body>
</html>
결과는 같다.
객체를 내려보내기
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<title>Vue</title>
</head>
<body>
<div id="app">
<h2>부모컴포</h2>
<child-view :user="person"></child-view>
</div>
<template id="child-view">
<div>
<h3>{{user.name}} 님의 신상명세서입니다. 킬러님</h3>
<div>나이 : {{user.age}}세</div>
<div>이멜 : {{user.email}}</div>
</div>
</template>
<script>
Vue.component("child-view", {
template: "#child-view",
props: ["user"],
});
const app = new Vue({
el: "#app",
data() {
return {
person: {
name: "유지나",
age: 20,
email: "ssafy.com",
},
};
},
});
</script>
</body>
</html>
사용자 정의 이벤트
- 컴포넌트 및 props와는 달리, 이벤트는 자동 대소문자 변환 제공X
- 대소문자를 혼용하는 대신 emit할 정확한 이벤트 이름을 작성하는 것을 권장
- DOM 템플릿의 v-on이벤트 리스너는 항상 자동으로 소문자 변환(v-on:myEvent->v-on:myevent)
- $on(eventName): 이벤트 수신
- $emit(eventName): 이벤트 발생, 추가 인자는 리스너의 콜백 함수로 전달
- 부모 컴포넌트는 자식 컴포넌트가 사용되는 템플릿에서 v-on을 사용하여 자식 컴포넌트가 보낸 이벤트를 청취
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<title>Vue</title>
</head>
<body>
<div id="app">
<button @click="doAction">메시지전송</button>
</div>
<script>
const app = new Vue({
el: '#app',
data() {
return {};
},
methods: {
doAction() {
this.$emit('sendMsg', '안녕하세요...');
},
},
created() {
this.$on('sendMsg', (msg) => {
alert(msg);
});
},
});
</script>
</body>
</html>
자식 컴포넌트->부모 컴포넌트
- 이벤트 발생과 수신을 이용
- 자식 컴포넌트에서 부모 컴포넌트가 지정한 이벤트를 발생($emit)
- 부모 컴포넌트는 자식 컴포넌트가 발생한 이벤트를 수신(on)하여 데이터 처리
총 투표 수
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<title>Vue</title>
</head>
<body>
<!-- 총투표수 -->
<!-- 코딩 / 알고리즘 -->
<div id="app">
<h2>여러분의 선호도는~~</h2>
<h3>총 투표수는 : {{total}}</h3>
<child-view @add-count="addTotalCount" title="코딩"></child-view>
<child-view @add-count="addTotalCount" title="알고리즘"></child-view>
</div>
<!-- 자식컴포넌트를 정의 해보자 -->
<template id="child-view">
<div>
<h2>{{title}} 의 득표수는 {{count}}</h2>
<button @click="addCount">투표</button>
</div>
</template>
<script>
Vue.component('child-view', {
template: '#child-view',
data() {
return {
count: 0,
};
},
props: ['title'],
methods: {
addCount() {
this.count += 1;
//부모에게 알려줘야된다.
this.$emit('add-count');
},
},
});
const app = new Vue({
el: '#app',
data() {
return {
total: 0,
};
},
methods: {
addTotalCount() {
this.total += 1;
},
},
});
</script>
</body>
</html>
컴포넌트에 js 삽입
FooterBar.js
export default {
template: `
<footer class="footer">
<ul class="list">
<li>
<a href="Personal information processing policy"
>개인정보처리방침</a
>
</li>
<li>
<a href="Terms of Service">이용약관</a>
</li>
<li>
<a href="Directions">오시는길</a>
</li>
<li>Created by the SSAFY team · © 2022</li>
</ul>
</footer>
`,
};
================================================================
HeaderNav.js
export default {
template: `
<header>
<nav class="container">
<div>
<a class="header_nav_home" href="index.html">홈으로</a>
</div>
<div class="header_nav_search">
<form action="#">
<input type="text" placeholder="검색어를 입력해주세요" />
<button class="button">검색</button>
</form>
</div>
<div class="header_nav_menuitem">
<a href="login.html" v-if="getUser">로그인</a>
<a href="#" @click="logout" v-else>로그아웃</a>
<a href="#">마이페이지</a>
</div>
</nav>
</header>
`,
methods: {
logout() {
localStorage.removeItem("loginUser");
alert("로그아웃 했습니다.");
},
},
computed: {
getUser() {
let user = JSON.parse(localStorage.getItem("loginUser"));
if (!user) {
return true;
} else {
return false;
}
},
},
};
================================================================
MainContent.js
export default {
template: `
<main class="index-main">
<div class="main-menu">
<ul>
<li><a href="./create.html">등록</a></li>
<li><a href="./list.html">목록</a></li>
</ul>
</div>
</main>
`,
};
================================================================
test11.html
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<title>Vue</title>
</head>
<body>
<div id="app">
<header-nav></header-nav>
<main-content></main-content>
<footer-bar></footer-bar>
</div>
<script type="module">
import HeaderNav from './component/HeaderNav.js';
import MainContent from './component/MainContent.js';
import FooterBar from './component/FooterBar.js';
const app = new Vue({
el: '#app',
components: {
HeaderNav,
MainContent,
FooterBar,
},
});
</script>
</body>
</html>