241126 뷰 (이벤트)

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>&nbsp;
        <button @click="calculate('-')">-</button>&nbsp;
        <button @click="calculate('*')">*</button>&nbsp;
        <button @click="calculate('/')">/</button>&nbsp;
      </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>&nbsp;
<button :disabled="count === 5" @click="addFunc">숫자 늘리기</button>&nbsp;

<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>&nbsp;
<button @click="addListIndex(3)">자료 삽입(특정)</button>&nbsp;
<button @click="changeList(2)">자료 수정</button>&nbsp;
<button @click="deleteList(1)">자료 삭제</button>&nbsp;

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}}&nbsp;&nbsp;</span>
-->
<template v-for="i in numArr" :key="i">
	<span v-if="i % 2 === 1">{{i}}&nbsp;&nbsp;</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>&nbsp;&nbsp;
        <!-- 양방향 바인딩 -->
        <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");