241126 에이콘 아카데미 수업에 기반하여 작성되었음을 알립니다.
뷰
뷰를 사용하다보면 리액트와 비슷해진다. 라우터 이런 부분들?
리액트를 배웠기 때문에 굳이 깊게 들어가지는 않는다.
리액트와 비교하여 어떤 부분이 다른 지 주의 깊게 보자.
뷰 수업의 목적도 리액트와 마찬가지로 RESTful 구현이다.
뷰 실습 (이벤트)
ex5event.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://unpkg.com/vue@3"></script>
</head>
<body>
<h1>이벤트 연습</h1>
<div id="app">
주사위 수 : {{number}}
<!-- v-on : 뷰에서 이벤트를 걸어주는 역할 -->
<button v-on:click="rollDiceEvent">주사위 던지기</button>
</div>
<script>
const app = Vue.createApp({
data() {
return {
number:1,
number2:0,
count:0,
};
},
methods:{
rollDiceEvent(){
let num = Math.floor(Math.random() * 6 + 1);
// 1 ~ 6 사이의 임의의 정수를 발생시키고
this.number = num;
// 뷰 인스턴스 number 필드에 값을 준다.
}
}
}).mount("#app");
</script>
</body>
</html>
숫자 변경 됨!
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://unpkg.com/vue@3"></script>
</head>
<body>
<h1>이벤트 연습</h1>
<div id="app">
... 생략
주사위 수 + 10 : {{number2}}
<!-- v-on:을 @으로 대체 가능하다. -->
<button @click="rollDiceEvent2(10)">주사위 던지기2</button>
</div>
<script>
const app = Vue.createApp({
data() {
return {
number:1,
number2:0,
count:0,
};
},
methods:{
... 생략
rollDiceEvent2(para){
let num = Math.floor(Math.random() * 6 + 1 + para);
this.number2 = num;
}
}
}).mount("#app");
</script>
</body>
</html>
숫자 증가 이벤트
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://unpkg.com/vue@3"></script>
</head>
<body>
<h1>이벤트 연습</h1>
<div id="app">
... 생략
숫자 * 2 증가 : {{count * 2}}
<br/>
숫자 js 조건문 : {{count % 2 === 0?"짝수" : "홀수"}} <!-- 삼항 연산자 사용 -->
<br/>
<!-- 함수를 써도 괜찮고 수식을 바로 써도 괜찮다.-->
<button @click="count += 1">클릭으로 count 증가</button>
<button @click="count++">클릭으로 count 증가</button>
</div>
<script>
const app = Vue.createApp({
data() {
return {
number:1,
number2:0,
count:0,
};
},
methods:{
... 생략
}
}).mount("#app");
</script>
</body>
</html>
뷰 실습 (이벤트 버스)
이벤트버스(EventBus)
부모 컴포넌트 -> 자식 컴포넌트 props를 전달하거나 자식 컴포넌트 -> 부모 컴포넌트로 events를 발생시켜 전달하는 것 외에도 형제 컴포넌트끼리 데이터를 통신할 수 있는 방법이 EventBus이다.
// 컴포넌트간의 데이터를 전달할 수 있는 방법이 이벤트 버스이다!
이벤트버스 사용 방법
EventBus는 전달자 역할만 하는 빈그릇일 뿐이고 사용방법은 자식이 부모에게 events를 사용하여 데이터를 전달한 방법과 동일하다.
데이터를 보내는 컴포넌트의 events에서 $emit을 사용하여 eventBus 객체에 값을 넣어주면 된다.
// 이벤트(데이터)를 보내는 컴포넌트 => EventBus.$emit('이벤트명', data);
// 이벤트(데이터)를 받는 컴포넌트 => EventBus.$on('이벤트명', 콜백함수);
// 해당 이벤트 이름을 가진 EventBus가 아닌 모든 EventBus 를 해제하고자 한다면 EventBus.$off();를 사용하면 된다.
출처 : 오늘의 CODE : Vue 이벤트 버스(EventBus)
💡 반응형 시스템
Vue의 가장 두드러지는 특징 중 하나는 눈에 띄지 않는 반응형 시스템이다.
뷰만 그렇지는 않다. 리액트도 마찬가지이다!
버미노트 : [Vue.JS] 반응형 시스템
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="https://unpkg.com/vue@3"></script>
</head>
<body>
... 생략
<hr />
<div id="app2">
message :
<child-component :propsdata="message"></child-component>
</div>
<script>
const app2 = Vue.createApp({
data(){
return {
message:"부모 컴포넌트에서 자식 컴포넌트로 전달할 메세지",
};
}
});
app2.component("child-component", {
props:["propsdata"], // 부모로부터 propsdata라는 propert를 받는다.
template:"<span>{{propsdata}}</span>"
})
app2.mount("#app2");
</script>
<hr />
<div id="app3">
<!-- 자식 컴포넌트에서 이벤트를 발생시키고, 부모 컴포넌트에서 감지하여 메소드 호출 -->
<child-component @show-log="printText"></child-component>
<h2>{{msg}}</h2>
</div>
<script>
const app3 = Vue.createApp({
data() {
return {
msg:"",
}
},
methods:{
printText(){
const logMessage = "이벤트 수신 성공";
console.log(logMessage);
this.msg = logMessage;
}
}
});
app3.component("child-component", { // 하위 컴포넌트
template:"<button @click='showLog'>하위 컴포넌트 버튼 클릭</button>",
methods:{
showLog(){
this.$emit('showLog'); // 보내는 컴포넌트에서는 .$emit()을 사용한다.
}
}
})
app3.mount("#app3");
</script>
</body>
</html>
💡propsdata 표기법 주의 사항
propsData(X), 카멜케이스 사용 불가 그외 props_data(O) 스네이크 케이스 props-data(O) 케밥케이스 사용 가능
카멜케이스 사용이 불가한 이유 :
"html 코드는 대소문자를 구분하지 않아 카멜표기법으로 표기해봐야 구분이 안된다."
하위 컴포넌트 버튼 클릭시 데이터 전송되는 모습을 확인
💡vue의 컴포넌트 간 통신 방식
- vue에서는 props와 $emit이 기본적인 통신 자원으로 활용된다.
- props는 단방향 데이터 흐름 (부모 -> 자식)을 구현한다.
- $emit은 이벤트 기반 흐름(자식 -> 부모)을 구현한다.
뷰 실습 (이벤트, 사칙연산)
ex6event.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="https://unpkg.com/vue@3"></script>
</head>
<body>
<div id="app">
<h2>이벤트 연습 (사칙연산)</h2>
<div>
<label for="value1">값1:</label>
<input type="number" id="value1" v-model.number="value1" />
</div>
<div>
<label for="value2">값2:</label>
<input type="number" id="value2" v-model.number="value2" />
</div>
<div>
<button @click="calculate('+')">+</button>
<button @click="calculate('-')">-</button>
<button @click="calculate('*')">*</button>
<button @click="calculate('/')">/</button>
</div>
<div>
<h3>결과 값 : {{result}}</h3>
</div>
</div>
<script>
const { createApp, ref } = Vue; // ref : 반응형 데이터 처리용
createApp({
setup() {
// 데이터는 vue의 반응성에 의해 UI와 동기화되며 값이 변경되면 화면이 리렌더링된다.
const value1 = ref(0); // 기본 데이터 타입을 감싸서 상태 변화를 추적할 수 있도록 한다.
const value2 = ref(0);
const result = ref(0);
const calculate = (oper) => {
switch (oper) {
case "+":
result.value = value1.value + value2.value;
break;
case "-":
result.value = value1.value - value2.value;
break;
case "*":
result.value = value1.value * value2.value;
break;
case "/":
if (value2.value === 0) {
alert("0으로 나눌 수 없어!");
result.value = "NaN";
} else {
result.value = value1.value / value2.value;
}
}
};
return {value1, value2, result, calculate};
}
}).mount("#app");
</script>
</body>
</html>
뷰 실습 (조건부 렌더링 : v-if, v-else-if, v-else, v-show)
ex7if.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>조건부 렌더링</title>
<script src="https://unpkg.com/vue@3"></script>
</head>
<body>
<div id="app">
<p v-if="season">겨울이 좋다!</p>
<p v-else>아니! 여름이 좋다!</p>
<button v-on:click="season =! season">계절 바꾸기</button>
</div>
<script>
const {createApp} = Vue;
createApp({
data() {
return {
season:true,
myVisible:false,
count:0
};
},
methods:{
}
}).mount("#app");
</script>
</body>
</html>
<!-- template를 활용한 조건부 그룹 -->
<template v-if="season">
<p>눈싸움 하기</p>
<p>눈사람 만들기</p>
</template>
<template v-else>
<p>더워</p>
<p>너무 더워</p>
</template>
<div v-show="season"> <!-- template와 함께 할 수 없다. -->
<strong>떡볶이 코트로 멋부리기</strong>
</div>
<br/>
* v-if와 v-show의 차이 *<br/>
<p v-if="season">v-if로 표현 - false인 경우 코드가 제거</p>
<p v-show="season">v-show로 표현 - false인 경우 display:none으로 처리</p>
false인 경우 console 확인
<!-- 체크박스의 상태에 따라 조건부 렌더링 -->
<label><input type="checkbox" v-model="myVisible">체크박스 표시 확인</label>
<p v-if="myVisible">check on</p>
<p v-else>check off</p>
<!-- count에 따라 조건부 렌더링 -->
<p v-if="count === 5">{{count}}번 클릭이면 버튼을 비활성화</p>
<p v-else-if="count >= 1">현재 클릭 횟수 : {{count}}</p>
<p v-else>버튼을 클릭하시오</p>
<button v-bind:disabled="count === 5" v-on:click="addFunc">숫자 늘리기</button>
<button :disabled="count === 5" @click="addFunc">숫자 늘리기</button>
<script>
const {createApp} = Vue;
createApp({
data() {
return {
season:true,
myVisible:false,
count:0
};
},
methods:{
addFunc(){
this.count++;
}
}
}).mount("#app");
</script>
뷰 실습 (for 반복문 렌더링)
ex8js.js
const {createApp} = Vue;
createApp({
data() {
return {
list:['커피','콜라','사이다','맥주'],
objArray:[
{name:"부산", taketime:'5시간'},
{name:"속초", taketime:'4시간'},
{name:"춘천", taketime:'3시간'},
],
myArr:['하나','둘','삼','넷','오'],
numArr:[1,2,3,4,5]
}
},
methods:{
}
}).mount("#app");
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>반복 렌더링</title>
<script src="https://unpkg.com/vue@3"></script>
</head>
<body>
<div id="app">
<h3>갈증날 때 마셔!</h3>
<ul>
<li v-for="a in list" :key="a">{{a}}</li>
</ul>
<ul>
<li v-for="(a, idx) in list" :key="`a-${idx}`">{{a}} ({{idx + 1}})</li>
</ul>
</div>
<script src="./ex8js.js"></script>
</body>
</html>
<h3>가고싶은 여행지!</h3>
<ol>
<li v-for="city in objArray" :key="city.name">{{city.name}}까지 소요시간 {{city.taketime}}</li>
</ol>
<h3>구구단 3단</h3>
<ul>
<li v-for="su in 9" :key="su">3 * {{su}} = {{3 * su}}</li>
</ul>
<h3>배열 자료 추가, 삭제</h3>
<ul>
<li v-for="i in myArr" :key="i">{{i}}</li>
</ul>
<button @click="addList">자료 추가(맨뒤)</button>
<button @click="addListIndex(3)">자료 삽입(특정)</button>
<button @click="changeList(2)">자료 수정</button>
<button @click="deleteList(1)">자료 삭제</button>
ex8js.js의 메소드
methods:{
addList(){
this.myArr.push("추가");
},
addListIndex(arg){
this.myArr.splice(arg, 0, '삽입')
},
changeList(arg){
this.myArr.splice(arg, 1, '수정');
},
deleteList(arg){
this.myArr.splice(arg, 1);
},
}
<h3>홀수만 출력</h3>
<!--
Vue에서는 성능상의 이유로 v-for와 v-if를 함께 사용 권장 암함
<span v-for="i in numArr" :key="i" v-if="i % 2 === 1">{{i}} </span>
-->
<template v-for="i in numArr" :key="i">
<span v-if="i % 2 === 1">{{i}} </span>
</template>
뷰 실습 (디렉티브 정리)
ex9.js
const {createApp} = Vue;
createApp({
data() {
return {
key1:'값1',
key2:'값2',
message:"안녕하세요",
htmlString:"<p style='color:pink;'>변덕스러운 날씨</p>",
su1:"0",
su2:"0",
txtMsg:"",
daumLogo:"https://t1.daumcdn.net/daumtop_deco/images/pctop/2023/logo_daum.png"
}
},
methods:{
myFunc(){
console.log(this.message);
},
myChange1(){
this.message = "안녕";
},
myChange2(){
this.message = "잘가";
},
}
}).mount("#app1")
ex9.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://unpkg.com/vue@3"></script>
</head>
<body>
<div id="app1">
{{key1}} {{key2}}
<br/>
<b>{{message}}</b>
<!-- 양방향 바인딩 -->
<input type="text" v-model="message">
<div v-html="htmlString"></div> <!-- raw HTML로 렌더링한다. -->
<div>{{htmlString}}</div>
<br/>
<button @click="myFunc">클릭</button>
<button @click="myChange1">변경1</button>
<button @click="myChange2">변경2</button>
</div>
<script src="ex9js1.js"></script>
</body>
</html>
<input type="text" size="5" v-model.number="su1"> +
<input type="text" size="5" v-model.number="su2"> =
<span>{{su1 + su2}}</span>
<!-- 조건부 렌더링 -->
<input type="search" v-model="txtMsg" placeholder="문자 입력">
<button :disabled = "txtMsg === ''">활성화 대기 버튼</button>
<!-- 이미지 출력 -->
<img :src="daumLogo" style="width: 300px; height: 300px;" />
🚨Uncaught SyntaxError: Identifier 'createApp' has already been declared (at ex9js2.js:1:1)
> js 파일이 하나일때는 문제가 없었지만 2개의 js파일에서 동일한 식별자인 createApp이 두번 나와 식별자 충돌이 난다. "이를 해결하기위해 IIFE라고 불리는 즉시실행함수를 사용해준다." 아래 코드를 보자
ex9js1.js
// JS는 같은 scope내에서 동의한 식별자로 여러번 const를 사용, 변수 선언 불가
// 이 문제를 해결하는 방법 중 하나로 즉시 실행 함수(iife)를 사용한다.
(function(){
const {createApp} = Vue;
createApp({
data() {
return {
key1:'값1',
key2:'값2',
message:"안녕하세요",
htmlString:"<p style='color:pink;'>변덕스러운 날씨</p>",
su1:"0",
su2:"0",
txtMsg:"",
daumLogo:"https://t1.daumcdn.net/daumtop_deco/images/pctop/2023/logo_daum.png"
}
},
methods:{
myFunc(){
console.log(this.message);
},
myChange1(){
this.message = "안녕";
},
myChange2(){
this.message = "잘가";
},
}
}).mount("#app1")
})();
ex9js2.js
(function(){
const {createApp} = Vue;
createApp({
data() {
return {
ypay:0,
busers:[
{bunho:'10', irum:'총무부', junhwa:'111-1111'},
{bunho:'20', irum:'영업부', junhwa:'222-2222'},
{bunho:'30', irum:'전산부', junhwa:'333-3333'},
{bunho:'40', irum:'관리부', junhwa:'444-4444'},
],
cssStyleTest:{
color:'gray',fontSize:'10px'
}
}
},
}).mount("#app2");
})();
console에 에러가 없어진 것을 확인할 수 있다.
배열 자료 출력 :
<select>
<option :value="buser.bunho" :key="buser.bunho" v-for="buser in busers">
{{buser.irum}} {{buser.junhwa}}
</option>
</select>
<table border="1">
<thead>
<tr>
<th>부서번호</th><th>부서이름</th><th>부서전화</th>
</tr>
</thead>
<tbody>
<tr v-for="b in busers" :key="b.bunho">
<td>{{b.bunho}}</td>
<td>{{b.irum}}</td>
<td>{{b.junhwa}}</td>
</tr>
</tbody>
</table>
<div>
연봉이 얼마야? <input type="number" v-model="ypay">
</div>
<div>
<span v-if="ypay < 5000">힘내!</span>
<span v-else-if="ypay < 8000">조금 더 힘내!</span>
<span v-else>부럽다!</span>
</div>
<span :style="cssStyleTest">컨텐츠 스타일을 바인딩</span>
<button @click="cssStyleTest.fontSize='30px'">글자 크기 확대</button>
뷰 실습 (computed와 methods 차이)
ex10exam.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://unpkg.com/vue@3"></script>
</head>
<body>
<div id="app">
<h2>1 강의실, 성적 관리</h2>
<!-- 폼 제출 이벤트 방지, v-on:submit.prevent -->
<form @submit.prevent="addScore">
<div>
<label for="name">이 름 : </label>
<input type="text" id="name" v-model="inputData.name" required/>
</div>
<br/>
시험 과목
<div>
<label for="subject1">자 바 : </label>
<input type="number" min="0" id="subject1" v-model="inputData.subject1" required/>
</div>
<div>
<label for="subject2">디 비 : </label>
<input type="number" min="0" id="subject2" v-model="inputData.subject2" required/>
</div>
<div>
<label for="subject3">뷰 뷰 : </label>
<input type="number" min="0" id="subject3" v-model="inputData.subject3" required/>
</div>
<br/>
<button type="submit">확인</button>
</form>
<h2>입력 결과</h2>
<ul>
<li v-for="entry in entries" :key="entry.name">
{{entry.name}}의 점수 : {{entry.subject1}}점, {{entry.subject2}}점, {{entry.subject3}}점
// 총점 : {{entry.total}}
</li>
</ul>
<h2>전체 계산 결과</h2>
<p>인원수 : {{entries.length}}명, 전체 총점 : {{totalSum}}</p>
</div>
<script src="ex10.js"></script>
</body>
</html>
ex10.js
const app = Vue.createApp({
data() {
return {
inputData:{
name:"",
subject1:0,
subject2:0,
subject3:0,
},
entries:[]
};
},
computed:{
// computed 영역은 종속된 데이터가 변경될 때마다 재 계산, 계산 결과 후 동일 요청에 대해 재계산을 안함, 성능 최적화
// 특정 데이터를 이용해 파생 데이터를 계산할 때 이용
totalSum(){ // 모든 총점의 합 계산
return this.entries.reduce((sum, entry) => sum + entry.total, 0);
}
},
methods:{
// methods 영역의 함수들은 매번 호출될 때마다 새로운 계산을 함 동일 요청이 들어온 경우 재계산 함!
// 결과를 cashing하지 않으므로 의도적인 새 값 계산을 하는 경우 사용
// 단순 동작 또는 이벤트 처리용으로 적합, 계산보다는 이벤트 처리에 관심이 집중되는 곳
addScore(){ // 세 과목의 총점을 계산
// 세 과목의 총점, (매번 달라짐, 의도적으로 새 값을 계산해줘야 함)
const total =
this.inputData.subject1 +
this.inputData.subject2 +
this.inputData.subject3;
this.entries.push({...this.inputData,total}); // 전개연산자 사용하여 총합을 저장
this.inputData = {name:'', subject1:0, subject2:0, subject3:0}; // 입력폼 초기화
}
}
});
app.mount("#app");
'Study > Acorn' 카테고리의 다른 글
241128 뷰 (ajax, ajax-router) (1) | 2024.11.28 |
---|---|
241127 뷰 (이벤트, 구글 차트, route, ajax) (0) | 2024.11.27 |
241125 뷰 (CDN, 라이프사이클, 컴포넌트, defer, 디렉티브) (1) | 2024.11.25 |
241122 리액트 (총정리 문제) (0) | 2024.11.22 |
241121 리액트 (리덕스 DB연동, RESTful) (0) | 2024.11.21 |