ppt 3개 페이지 수정

p77 지난 수업 복습

 

emit : 방출하다.

 

Exam10FallthroughAttr 폴더

 

index.vue 수정

<template>
    <div class="card">
        <div class="card-header">Exam10FallthroughAttr</div>
        <div class="card-body">
            <!--id는 UIComponentA.vue에 루트 엘리먼트 속성으로 들어가게 된다.-->
            <UIComponentA id="uiComponentA" class="bg-warning" style="color:blue" @click="handleClick" />

            <UIComponentB id="uiComponentB1" myclass="bg-dark" mystyle="color:white" @click="handleClick" />

            <UIComponentB id="uiComponentB2" myclass="bg-danger" mystyle="color:white" @click="handleClick" />
        </div>
    </div>
</template>

<script setup>
import UIComponentA from "./UIComponentA.vue";
import UIComponentB from "./UIComponentB.vue";

function handleClick() {
    console.log("Exam10FallthroughAttr.handleClick() 클릭됨");
}
</script>

<style scoped></style>

 

UIComponentA.vue 수정

<template>
    <!-- index.vue에서 UIComponentA 태그를 사용 후 속성까지 전달 -->
    <div class="card">
        <div class="card-header">UIComponentA</div>
        <div class="card-body">
            <div :class="prop.class" :style="prop.style" @click="emit('click')">Content</div>
        </div>
    </div>
</template>

<script setup>
const prop = defineProps(["class", "style"]);
const emit = defineEmits(["Click"]);

</script>

<style scoped></style>

 

이렇게도 가능하다.

index.vue

<template>
    <div class="card">
        <div class="card-header">Exam10FallthroughAttr</div>
        <div class="card-body">
            <!--id는 UIComponentA.vue에 루트 엘리먼트 속성으로 들어가게 된다.-->
            <UIComponentA id="uiComponentA" class="bg-warning" style="color:blue" @aaa="handleClick" />

            <UIComponentB id="uiComponentB1" myclass="bg-dark" mystyle="color:white" @click="handleClick" />

            <UIComponentB id="uiComponentB2" myclass="bg-danger" mystyle="color:white" @click="handleClick" />
        </div>
    </div>
</template>

<script setup>
import UIComponentA from "./UIComponentA.vue";
import UIComponentB from "./UIComponentB.vue";

function handleClick() {
    console.log("Exam10FallthroughAttr.handleClick() 클릭됨");
}
</script>

<style scoped></style>

 

UIComponentA.vue

<template>
    <!-- index.vue에서 UIComponentA 태그를 사용 후 속성까지 전달 -->
    <div class="card">
        <div class="card-header">UIComponentA</div>
        <div class="card-body">
            <!-- emit은 리스너 함수를 실행시켜준다. -->
            <div :class="prop.class" :style="prop.style" @click="emit('aaa')">Content</div>
        </div>
    </div>
</template>

<script setup>
const prop = defineProps(["class", "style"]);
const emit = defineEmits(["aaa"]);

</script>

<style scoped></style>

 

UIComponentA의 내용들을 클릭했을 때, 각각의 클릭 이벤트가 어디에서 일어나는가?

content ->

버튼 ->

 

전체를 호출하지 않으려면? 

import { defineOptions, useAttrs } from 'vue';

defineOptions({
    // fallthrough 속성을 비활성화
    inheritAttrs: false
});

const attrs = useAttrs();

 

 

-- 

Exam11Slot

fallthrough 속성을 적용한 예시 부분.

DialogTemplate의 modal 첫 태그의 id는 지정해주면 안된다.

 

index.vue의

<DialogA id="dialogA" />
<DialogB id="dialogB" />
 <DialogC id="dialogC" />

이 코드를 이용하여 id 값을 바꾸어가며 컴포넌트를 적용하게 된다.

 

오늘은 어제 배웠던 Fallthrough 속성을 이용하여 DialogB의 확인 버튼을 눌렀을 때 모달창이 닫히는 것과

DialogC의 로그인 버튼을 눌렀을 때 모달창이 닫히는 것을 해볼 것이다.

 

1. index.vue

<template>
    <div class="card">
        <div class="card-header">Exam11Slot</div>
        <div class="card-body">
            <button class="btn btn-info btn-sm me-3" @click="showDialogA">기본 다이얼로그</button>
            <button class="btn btn-info btn-sm me-3" @click="showDialogB">알림 다이얼로그</button>
            <button class="btn btn-info btn-sm" @click="showDialogC">로그인 다이얼로그</button>
        </div>

        <DialogA id="dialogA" />
        <!-- @close란 이벤트는 원래 없다.
            Fallthrough를 이용하여 사용 가능하게끔 한 것이다. -->
        <DialogB id="dialogB" @close="hideDialogB" />
        <DialogC id="dialogC" @close="hideDialogC" />

    </div>
</template>

<script setup>
import { onMounted } from "vue"; // 라이프사이클과 관련. 나중에 배운다.
import DialogA from "./DialogA.vue";
import DialogB from "./DialogB.vue";
import DialogC from "./DialogC.vue";
import { Modal } from "bootstrap";

let modalDialogA = null; // ?
let modalDialogB = null;
let modalDialogC = null;

// 컴포넌트가 생성되고, DOM에 부착될 때 자동으로 실행되는 콜백
onMounted(() => {
    modalDialogA = new Modal(document.querySelector("#dialogA"));
    modalDialogB = new Modal(document.querySelector("#dialogB"));
    modalDialogC = new Modal(document.querySelector("#dialogC"));
});

function showDialogA() {
    modalDialogA.show();
}

function showDialogB() {
    modalDialogB.show();
}

function showDialogC() {
    modalDialogC.show();
}

function hideDialogB() {
    modalDialogB.hide();
}

function hideDialogC() {
    modalDialogC.hide();
}
</script>

<style scoped></style>

 

2. DialogB.vue

<template>
    <DialogTemplate>
        <!-- DialogTemplate의 name들을 이용 -->
        <template v-slot:header>
            알림
        </template>

        <template v-slot:body>
            <P>DialogB의 내용입니다!</P>
        </template>

        <template v-slot:footer>
            <button class="btn btn-secondary btn-sm" @click="emit('close')">확인</button>
        </template>


    </DialogTemplate>
</template>

<script setup>
import DialogTemplate from "./DialogTemplate.vue";
// 다이얼로그 B 닫기 버튼 활성화 시키기
// 오늘 배운 '속성'을 이용
const emit = defineEmits(['close'])

</script>

<style scoped></style>

 

3. DialogC.vue

<template>
    <DialogTemplate>
        <!-- DialogTemplate의 name들을 이용 -->
        <template v-slot:header>
            로그인
        </template>

        <template v-slot:body>
            <div class="mb-3">
                <label for="inputEmail" class="form-label">이메일</label>
                <input type="text" class="form-control" id="inputEmail" v-model="inputData.email">
            </div>
            <div class="mb-3">
                <label for="inputPassword" class="form-label">비밀번호</label>
                <input type="password" class="form-control" id="inputPassword" v-model="inputData.password">
            </div>
        </template>

        <template v-slot:footer>
            <button class="btn btn-outline-warning btn-sm me-2" @click="handleLogin">로그인</button>
            <button class="btn btn-outline-warning btn-sm" data-bs-dismiss="modal">닫기</button>
        </template>


    </DialogTemplate>
</template>

<script setup>
import DialogTemplate from "./DialogTemplate.vue";
import { ref } from "vue";

const emit = defineEmits(['close'])

const inputData = ref({
    email: "",
    password: ""
});

function handleLogin() {
    // console.log(inputData.value);
    // console.log(JSON.stringify(inputData.value)); // 객체를 문자열로 바꿈
    console.log(JSON.parse(JSON.stringify(inputData.value))); // 문자열을 자바스크립트 객체로 바꿈
    emit('close');
}


</script>

<style scoped></style>

 

 

 

 

Ch04. Event Handling & Watch

빨간 부분이 이벤트 이름, 파란색은 수식어

65는 'A' 67은 'C'

prevent 정도는 알고 있어야 한다.

 

1. views폴더에 Ch04EventHandlingWatch폴더 생성

2. Exam01EventHandling.vue파일 생성

3. router폴더에 Ch04EventHandlingWatch.js 생성

const routes = [
    {
        path: "/Ch04EventHandlingWatch/Exam01EventHandling",
        component: () => import(/* webpackChunkName: "Ch04EventHandlingWatch" */ '@/views/Ch04EventHandlingWatch/Exam01EventHandling.vue')
    }
];

export default routes;

 

4. router폴더의 index.js에 import 구문 추가와 구조분해할당 방식 사용

import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import Ch02ComponentRouting from './Ch02ComponentRouting' // routes를 가져온다
import Ch03DataBinding from './Ch03DataBinding'
import Ch04EventHandlingWatch from "./Ch04EventHandlingWatch";


const routes = [
  {
    path: '/',
    name: 'home',
    component: HomeView
  },
  ...Ch02ComponentRouting, // 구조분해할당 방식
  ...Ch03DataBinding,
  ...Ch04EventHandlingWatch
]

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL), //히스토리 (뒤로가기 가능)
  routes
})

export default router

 

5. components 폴더 아래의 AppMenu.vue에 새로운 아코디언 리스트 추가

<!-- 4 --------------------------------------------------------->
<div class="accordion-item">
    <h2 class="accordion-header">
        <button class="accordion-button fw-bold" type="button" data-bs-toggle="collapse"
            data-bs-target="#collapse4" aria-expanded="true" aria-controls="collapse4">
            Ch04. Event Handling & Watch
        </button>
    </h2>
    <div id="collapse4" class="accordion-collapse" data-bs-parent="#accordionExample">
        <div class="accordion-body">
            <ul class="nav flex-column">
                <li class="nav-item">
                    <RouterLink to="/Ch04EventHandlingWatch/Exam01EventHandling" class="nav-link">
                        Exam01EventHandling : 이벤트 처</RouterLink>
                </li>

            </ul>
        </div>
    </div>
</div>

 

6. Exam01EventHandling.vue 최종

<template>
    <div class="card">
        <div class="card-header">Exam01EventHandling</div>
        <div class="card-body">
            <div class="mb-3">
                <h6>[이벤트 처리]</h6>
                <!-- 함수 그 자체 -->
                <button class="btn btn-danger btn-sm me-3" @click="handleBtn1">버튼 1</button>
                <!-- 괄호()가 있으므로 함수를 호출 -->
                <button class="btn btn-info btn-sm" @click="handleBtn2('vue is good', $event)">버튼 2</button>
            </div>

            <div class="input-group mb-3">
                <span class="input-group-text">아이디</span>
                <!-- :value : 단방향. v-model을 쓰지않았기 때문에 양방향이 X -->
                <!-- 앞으로 모든 변수는 '상태'로 사용할 것이다. -->
                <input type="text" class="form-control" name="userID" :value="userID" @keyup="handleInput($event)" />
            </div>

            <div class="input-group mb-3">
                <span class="input-group-text">아이디</span>
                <!-- 양방향 바인딩을 사용해보자. (v-model 사용)-->
                <input type="text" class="form-control" name="userID" v-model="userID" />
            </div>

            <div>입력 내용 : {{ userID }}</div>
            <hr>

            <div>
                <h6>[이벤트 수식어 사용]</h6>
                <a href="javascript:handleLink()">링크로 자바스크립트 실행</a>
                <br>
                <a href="https://vuejs.org" @click="handleLink">링크로 자바스크립트 실행</a>
                <br>

                <!-- prevent를 이용하여 막는다. -->
                <!-- 만약 조건이 있다면 조건에 부합하지 않는 경우 
                    prevent를 붙이고, 조건을 만족한다면 넘어가게끔 만든다? -->
                <a href="https://vuejs.org" @click.prevent="handleLink">링크로 자바스크립트 실행</a>

                <!--  -->
                <!-- <form class="my-3" action="https://vuejs.org/guide/introduction.html" @submit.prevent="handleForm"> -->
                <!-- 위의 a태그의 이용용도와 마찬가지로 action을 취하는 것인지?? 확인해보자 -->
                <form class="my-3" action="" @submit.prevent="handleForm">
                    <div class="input-group mb-3">
                        <span class="input-group-text">이메일</span>
                        <input type="email" class="form-control" v-model="userEmail">
                    </div>
                    <!-- v-model을 사용하여 양방향 데이터 전송이 되었기 때문에
                        스크립트 부분에 ref로 상태 변수로 초기화 시키기만 하면 바로 아래 코드에
                        {{ 변수 }} 값으로 바로 나타낼 수 있다. -->
                    <div>입력내용: {{ userEmail }}</div>
                    <input type="submit" value="제출" class="btn btn-success btn-sm mt-2" />
                </form>


            </div>
        </div>
    </div>
</template>

<script setup>
import { ref } from 'vue';

function handleBtn1() {
    console.log("handleBtn1 실행");
}

function handleBtn2(arg, event) {
    console.log("handleBtn2 실행");
    console.log("arg:" + arg);
    console.log("event,target:", event.target); // 이벤트가 발생하는 타켓(엘리먼트)
}

let userID = ref(""); // 디폴트 값은 빈문자열 // 상태 데이터 생성

function handleInput(event) {
    console.log("event.target.name: " + event.target.name);
    console.log("event.target.value: " + event.target.value);
    userID.value = event.target.value;
}

function handleLink() {
    console.log("handleLink 실행");
}

let userEmail = ref("");

function handleForm() {
    console.log("userEmail.value: " + userEmail.value);
}

</script>

<style scoped></style>

 

# Watch : 감시한다!

userId값이 변경되면 자동적으로 화살표 함수가 실행되는 코드

이것을 활용할 수 있는 방법들을 여러가지 생각해보자

console.gruop : 출력을 깔끔하게 그룹지어서 나타냄

 

1. Ch04EventHandlingWatch 폴더 아래에 Exam02Watch.vue 생성

2. router 폴더 아래 Ch04EventHandlingWatch.js에 라우츠 등록

3. componets 폴더 아래 AppMenu.vue에 리스트 추가

4. Exam02Watch.vue 작성

<template>
    <div class="card">
        <div class="card-header">Exam02Watch</div>
        <div class="card-body">
            <!-- 교수님 코드 -->
            <div class="row mb-2">
                <label class="col-sm-2 col-form-label">UserId</label>
                <div class="col-sm-10">
                    <!-- v-model만으로 양방향 데이터 바인딩 -->
                    <input type="text" class="form-control" v-model="userId">
                </div>
            </div>

            <div class="mb-2">
                <button class="btn btn-danger btn-sm" @click="handleProductChange">product 상태의 객체 변경</button>
            </div>

        </div>
    </div>
</template>

<script setup>
import { ref, watch } from 'vue';

// 상태 생성
let userId = ref("");

// 상태 감시
// watch(감시할 상태의 변수, (매개값1, 매개값2) => 화살표 함수)
watch(userId, (newUserId, oldUserId) => {
    console.group("userId 변경 감시");
    console.log("newUserId: " + newUserId);
    console.log("oldUserId: " + oldUserId);
    console.groupEnd();
});

// 상태 생성 // 객체
let product = ref({
    name: "",
    company: "",
    price: 0
});

// 객체 자체가 변경될 때를 감시
/*watch(product, (newProduct, oldProduct) => {
    console.group("product 변경 감시");
    console.log("newProduct: " + JSON.stringify(newProduct)); // 순수 자바스크립트 객체가 얻어진다.
    console.log("oldProduct: " + JSON.stringify(oldProduct));
    console.groupEnd();
});*/

// 옵션 주기  -- { deep: true } --
// 객체 자체 뿐만아니라 객체 안의 속성값이 변경될 시에도 감시를 하게 된다.
/*watch(product, (newProduct, oldProduct) => {
    console.group("product 객체 자체 뿐만 아니라 속성까지도 변경 감시");
    console.log("newProduct: " + JSON.stringify(newProduct)); // 순수 자바스크립트 객체가 얻어진다.
    console.log("oldProduct: " + JSON.stringify(oldProduct));
    console.groupEnd();
}, { deep: true });*/

// 특정 속성만 변경 감시
// 내가 지정한 속성에만 관련하여 감시
// ()=> {return product.value.price} 리턴문만 있는경우
/*watch(() => product.value.price, (newPrice, oldPrice) => {
    console.group("price 속성값만 변경 감시");
    console.log("newPrice: " + newPrice); // 객체가 아니기 때문에 변수값만 넣어준다.
    console.log("oldPrice: " + oldPrice);
    console.groupEnd();
});*/

// 복수개의 상태를 감시할 경우 (헷갈리기 때문에 비추천. 그냥 따로따로 감시해주자 -- 그냥 watch 두개 쓰자..)
watch([userId, product], ([newUserId, newProduct], [oldUserId, oldProduct]) => {
    console.group("복수개의 상태를 감시할 경우 --> userId와 product객체 감시");
    console.log("newUserId: " + newUserId);
    console.log("oldUserId: " + oldUserId);
    console.log("newProduct: " + JSON.stringify(newProduct)); // 순수 자바스크립트 객체가 얻어진다.
    console.log("oldProduct: " + JSON.stringify(oldProduct));
    console.groupEnd();
}); // 여기에도 { deep: true }를 넣어 사용할 수 있다..

//////////////////////////////////////////////////// watch는 여러개를 넣어도 괜찮다.

// product 상태 변경 함수
function handleProductChange() {
    // 객체 자체가 변경
    product.value = {
        name: "제네시스G80 풀옵",
        company: "현대",
        price: 80000000
    }

    // 속성값만 변경
    product.value.price = 90000000; // 값만 변경될 경우 watch는 감시를 하지 않는다.
    // 객체 자체의 값(전체?)이 변경되어야 watch가 실행된다.(감시를 한다.)
}

</script>

<style scoped></style>

 

cf) JSON.stringify(변수)를 하게되면 순수 자바스크립트 값(객체,변수)을 얻을 수 있다.

 

cf) 주식의 값이 변경되면 이러한 상태값을 이용하여 '감시'를 활용할 수 있을 것이다.

즉각적인 데이터의 변화를 활용해야 하는 경우에 사용. (우선순위 고려)

주민등록번호를 입력할 경우에도 활용 가능?

 

 

 

Ch05. 컴포넌트간 데이터 전달

☆ 부모에서 자식으로 데이터를 전달할 때는 속성으로

☆ 자식에서 부모로 데이터를 전달할 때는 이벤트

이미 우리는 다 배웠다. (Fallthrough 내용)

 

cf) 전역 상태의 데이터를 저장하게 되면 이는 모든 컴포넌트에게 데이터를 전달할 수 있다. (뒤에서 배울 것이다)

 

우리가 전날에 했던 내용. 오늘 오전에도 복습해보았다.

규칙에 대해 다시 한번 위의 그림을 보고 숙지하자.

 

자식 컴포넌트에서 props.no / props.kind ... 등으로 사용 가능하다.

product는 객체. props.product.no 등으로 사용 가능하고, product를 구조분해하여 사용도 가능하다. (2개의 속성)

 

defineProps를 사용할 때, 배열로 사용하지 않고 '객체'로 사용하는 경우

콜론(:)을 붙이는 경우 큰 따옴표 " " 안의 내용은 '코드'로 취급하기 때문에

String 형식이나 Int 형식을 잘 맞추어 작성해야한다.

ex) propA="문자열"  ==  :propA=" '문자열' "

 

위의 그림에서 prop-d는 propD로 검증하여 사용할 수 있고, 옆에서 인자값을 넘기기 때문에 값을 반드시 입력해야한다.

깊게 생각하지말고 객체나 배열은 함수의 리턴값으로 그대로 넣어준다고 생각하자.

 

validator를 사용하여 유효성 검사 가능

 

 

# 실습해보자. 90~93p

1. views 폴더 아래에 Ch05DataDelivery 폴더 생성 - index.vue 생성

 

cf) 스니펫 사용해보자. (나는 vue3 snippet을 다운받아 명령 팔레트에서 수정했었다)

https://snippet-generator.app/?description=&tabtrigger=&snippet=&mode=vscode

 

snippet generator

Snippet generator for Visual Studio Code, Sublime Text and Atom. Enjoy :-)

snippet-generator.app

 

 

 

이제 vue-card 엔터를 치면 기본적인 코드 틀이 자동완성 된다.

 

2. router 폴더 아래 Ch05DataDelivery.js 파일 생성 후 코드 작성

 

3. router 폴더 아래 index.js에 구조분해할당 방식으로 코드 추가

 

4. components 폴더 아래 AppMenu.vue 아코디언과 리스트 추가

 

5. views 폴더 아래에 Ch05DataDelivery 폴더 생성 - ChildA.vue 생성

 

6. views 폴더 아래에 Ch05DataDelivery 폴더 생성 - ChildB.vue 생성

 

 

* index.vue

<template>
    <div class="card">
        <div class="card-header">Exam01Props</div>
        <div class="card-body">
            <!-- 각 값을 '문자열'로 전달할 것인지, '숫자'로 전달할 것인지 콜론(:)으로 구별 
                현재 부모 컴포넌트. -->
            <ChildA prop1="value1"
                    prop2="value2"
                    productNo="1"
                    product-Kind="bag"
                    :productPrice="1000"
                    :objectProp="{name: '홍길동', age:25}"
                    :arrayProp="['블랙', '레드']"
                    :funProp="() => {return '리턴값'}"/>
            <hr>
            <ChildB v-bind:productNo="product.no"
                    :productKind="product.kind"
                    :product="product"
                    v-bind="product"/>
            <!-- 부모쪽에서 상태를 변경하면 자식쪽에서 값이 변경이 되는지 확인하기 -->
            <button class="btn btn-info btn-sm mt-3" @click="changeData">상태 변경</button>
        </div>
    </div>
</template>

<script setup>
import ChildA from "./ChildA.vue"
import ChildB from "./ChildB.vue"

import { ref } from 'vue';

const product = ref({
    no: 1,
    kind: "bag"
});

function changeData() {
    product.value.no += 1;
    product.value.kind += "g";
    console.log("changeData 실행");
}

</script>

<style scoped></style>

 

 

* ChildA.vue

<template>
    <div class="card">
        <div class="card-header">ChildA</div>
        <div class="card-body" :class="prop.prop1"> <!-- 속성의 값으로도 사용할 수 있다. -->
            <!-- 표현식 -->
            <p>prop1 = {{ prop.prop1 }} or {{ prop1 }}</p>
            <p>prop2 = {{ prop.prop2 }} or {{ prop2 }}</p>
            <p>productNo = {{ prop.productNo }} or {{ productNo }}</p>
            <p>product-Kind = {{ prop.productKind }} or {{ productKind }}</p>
            <p>productPrice = {{ prop.productPrice }} or {{ productPrice }}</p>
            <p>objectProp.name / objectProp.age = {{ prop.objectProp.name }} or {{ objectProp.age }}</p>
            <p>arrayProp = {{ prop.arrayProp[0] }} or {{ arrayProp[1] }}</p>
            <p>funProp = {{ prop.funProp() }} or {{ funProp() }}</p> <!-- 함수는 호출 형식으로 -->
        </div>
    </div>
</template>

<script setup>

const prop = defineProps([
    "prop1",
    "prop2",
    "productNo",
    "productKind", // product-Kind로 하면 안된다. 나중에 변수로 활용할 것이기 때문에.
    "productPrice",
    "objectProp",
    "arrayProp",
    "funProp"
]);

// 속성값 읽기
console.group("ChildA");
console.log("prop1: " + prop.prop1);
console.log("prop2: " + prop.prop2);
console.log("productNo: " + prop.productNo + " " + typeof (prop.productNo));
console.log("productKind: " + prop.productKind);
console.log("productPrice: " + prop.productPrice + " " + typeof (prop.productPrice));
console.log("objectProp: " + prop.objectProp + " " + typeof (prop.objectProp));
console.log("objectProp: " + prop.objectProp.name + " " + prop.objectProp.age);
console.log("arrayProp: " + prop.arrayProp + " " + typeof (prop.arrayProp));
console.log("arrayProp: " + prop.arrayProp[0] + " " + prop.arrayProp[1]);
console.log("funProp: " + prop.funProp + " " + typeof (prop.funProp));
console.log("funProp: " + prop.funProp());
console.groupEnd();
</script>

<style scoped></style>

 

* ChildB.vue

<template>
    <div class="card">
        <div class="card-header">ChildB</div>
        <div class="card-body">
            <!-- 부모쪽에서 changeData를 만들어 값이 바뀌는지 확인
                결과 : 부모의 상태가 변경이 되면 다시 자식쪽으로 데이터가
                        전달이 된다. -->
            <p>productNo = {{ productNo }}</p>
            <p>productKind = {{ productKind }}</p>
            <p>product = {{ product.no }} / {{ product.kind }}</p>
            <p>no = {{ no }}</p>
            <p>kind = {{ kind }}</p>
        </div>
    </div>
</template>

<script setup>
const prop = defineProps(["productNo", "productKind", "product", "no", "kind"]);

console.group("ChildB")
console.log("productNo: " + prop.productNo + " " + typeof (prop.productNo));
console.log("productKind: " + prop.productKind);
console.log("product: " + prop.product + " " + JSON.stringify(prop.product));
console.log("no: " + prop.no + " " + typeof (prop.no));
console.log("kind: " + prop.kind);
console.groupEnd();

</script>


<style scoped></style>

 

부모 쪽에서의 데이터를 자식 쪽으로 전달하는 방법(Fallthrough 속성 이용)과

부모 쪽의 데이터의 변경에 의해 자식 쪽에서 데이터가 변경이 되는지 실습으로 확인.

 

★ 부모 쪽에서의 값 변경이 일어나면 자식 쪽에서의 값도 변경 된다!!

<ChildB v-bind:productNo="product.no"
                    :productKind="product.kind"
                    :product="product"
                    v-bind="product"/>
            <!-- 부모쪽에서 상태를 변경하면 자식쪽에서 값이 변경이 되는지 확인하기 -->
            <button class="btn btn-info btn-sm mt-3" @click="changeData">상태 변경</button>
function changeData() {
    product.value.no += 1;
    product.value.kind += "g";
    console.log("changeData 실행");
}

 

7. views 폴더 아래에 Ch05DataDelivery 폴더 생성 - ChildC.vue 생성

부모로부터 미리 받아야될 속성을 먼저 정의하기.

 

const prop = defineProps({
    propA: String,
    propB: Number,
    propC: [String, Number], // 문자도 좋고 숫자도 좋다.
    propD:
        type: String,
        required: true //  ☆ 이렇게 지정해놓으면 부모쪽에선 반드시 작성해야 한다!!
    }
});
<ChildC propA="문자열"
                    :propB="5"
                    propC="뷰"/>

 

콘솔창에서 경고표시가 뜬다.

propE: {
        type: Number,
        default: 3
    }

부모쪽에서 아무값도 주지 않으면 디폴트 값인 숫자 3이 들어간다.

propF: {
        type: Object,
        default() {
            return {message: "Hello"}
        }
    }

객체 타입이라면 default() 함수 형태로 작성해야 한다!

부모쪽에선 :propF="{message:'Thanks'} 식으로 넘겨준다.

 

자식쪽의 코드

    propG: {
        type: Array,
        default() {
            return ["black", "pink"]
        }
    },
    propH: {
        type: Function,
        default() {
            return "Child값"
        }
    },
    propI: {
        type: Number,
        validator(value) {
            return value > 0 && value < 11
        }
    }

 

부모쪽의 코드

<ChildC propA="문자열"
                    :propB="5"
                    propC="뷰"
                    :propD="`프론트엔드`"
                    :propF="{message:'Thanks'}"
                    :propG="['white', 'brown']"
                    :propH="()=>'Parant값'"
                    :propI="12"/>

validator의 범위를 벗어났기 때문에 화면에 값은 나오지만 콘솔창에서 에러가 발생한다.

 

 

* ChildC.vue

<template>
    <div class="card">
        <div class="card-header">ChildC</div>
        <div class="card-body">
            <p>propA : {{ prop.propA }}</p>
            <p>propB : {{ propB }}</p>
            <p>propC : {{ propC }}</p>
            <p>propD : {{ propD }}</p>
            <p>propE : {{ propE }}</p>
            <p>propF : {{ propF.message }}</p>
            <p>propG : {{ propG[0] }} / {{ propG[1] }}</p>
            <p>propH : {{ propH() }}</p>
            <p>propI : {{ propI }}</p>
        </div>
    </div>
</template>

<script setup>
// 1. 부모로부터 미리 받아야될 속성을 먼저 정의하기.
const prop = defineProps({
    propA: String,
    propB: Number,
    propC: [String, Number], // 문자도 좋고 숫자도 좋다.
    propD: {
        type: String,
        required: true // ☆ 이렇게 지정해놓으면 부모쪽에선 반드시 작성해야 한다!!
    },
    propE: {
        type: Number,
        default: 3
    },
    propF: {
        type: Object,
        default() {
            return { message: "Hello" }
        }
    },
    propG: {
        type: Array,
        default() {
            return ["black", "pink"]
        }
    },
    propH: {
        type: Function,
        default() {
            return "Child값"
        }
    },
    propI: {
        type: Number,
        validator(value) {
            return value > 0 && value < 11
        }
    }
});

</script>


<style scoped></style>

 

 

** 다시 최종 index.vue

<template>
    <div class="card">
        <div class="card-header">Exam01Props</div>
        <div class="card-body">
            <!-- 각 값을 '문자열'로 전달할 것인지, '숫자'로 전달할 것인지 콜론(:)으로 구별 
                현재 부모 컴포넌트. -->
            <ChildA prop1="value1"
                    prop2="value2"
                    productNo="1"
                    product-Kind="bag"
                    :productPrice="1000"
                    :objectProp="{name: '홍길동', age:25}"
                    :arrayProp="['블랙', '레드']"
                    :funProp="() => {return '리턴값'}"/>
            <hr>
            <ChildB v-bind:productNo="product.no"
                    :productKind="product.kind"
                    :product="product"
                    v-bind="product"/>
            <!-- 부모쪽에서 상태를 변경하면 자식쪽에서 값이 변경이 되는지 확인하기 -->
            <button class="btn btn-info btn-sm mt-3" @click="changeData">상태 변경</button>
            <hr>
            <ChildC propA="문자열"
                    :propB="5"
                    propC="뷰"
                    :propD="`프론트엔드`"
                    :propF="{message:'Thanks'}"
                    :propG="['white', 'brown']"
                    :propH="()=>'Parant값'"
                    :propI="12"/>
        </div>
    </div>
</template>

<script setup>
import ChildA from "./ChildA.vue"
import ChildB from "./ChildB.vue"
import ChildC from "./ChildC.vue"

import { ref } from 'vue';

const product = ref({
    no: 1,
    kind: "bag"
});

function changeData() {
    product.value.no += 1;
    product.value.kind += "g";
    console.log("changeData 실행");
}

</script>

<style scoped></style>

 


 

이번엔 자식 -> 부모 데이터 전달을 알아보자.

템플릿에서 사용할 수 있는 2가지의 방법.

자바스크립트에서는 Emit( ... ) 방식으로만 사용해야 한다.

 

emit 값으로 들어간 데이터 값들은 부모 컴포넌트 핸들러의 매개변수의 갯수와 같다.

ex) defineEmits로 3개의 값을 넘기면 핸들러의 매개변수는 3개이다.

 

# 실습해보자.

1. Ch05DataDelivery 폴더 아래 Exam02EventEmit 폴더 생성 후 index.vue 생성 (인덱스의 기초적인 코드가 없으면 에러난다! ex) 템플릿, 스크립트, 스타일 태그)

2. Ch05DataDelivery.js에 라우츠 등록

3. components 폴더 아래의 AppMenu.vue 에 리스트 추가

4. Ch05DataDelivery 폴더 아래 Exam02EventEmit 폴더 생성 후 Child.vue 생성

 

* index.vue

<template>
    <div class="card">
        <div class="card-header">Exam02EventEmit</div>
        <div class="card-body">
            <!-- 자식 쪽에서 이벤트가 발생했을 때 부모도 이벤트로 받는다! -->
            <Child @child-event-1="handleChildEvent1" 
            @child-event-2="handleChildEvent2"
            @increment-event="handleIncrementEvent" 
            @decrement-event="handleDecrementEvent"
            :counter="counter" />
        </div>
    </div>
</template>

<script setup>
import Child from "./Child.vue";

function handleChildEvent1(data) {
    console.log("handleChildEvent1 실행");
    console.log("data: ", data);
}

function handleChildEvent2(arg1, arg2) {
    console.log("handleChildEvent2 실행");
    console.log("agr1: ", arg1);
    console.log("agr1: ", arg2);
}

// ----
import { ref } from 'vue';

const counter = ref(0);

function handleIncrementEvent(amount) {
    counter.value += amount;
}

function handleDecrementEvent(amount) {
    counter.value -= amount;
}
</script>

<style scoped></style>

 

* Child.vue

<template>
    <div class="card">
        <div class="card-header">Child</div>
        <div class="card-body">
            <div>
                <h6>[자식 -> 부모]</h6>
                <button class="btn btn-danger btn-sm me-3" @click="$emit('child-event-1', 'value1')">child-event-1 이벤트
                    발생($emit)</button>
                <button class="btn btn-danger btn-sm me-3" @click="emit('child-event-1', data1)">child-event-1 이벤트
                    발생(emit)</button>
                <!-- 이번엔 emit을 사용하지 않고 작성 -->
                <button class="btn btn-info btn-sm" @click="handleChildEvent2">child-event-2 이벤트
                    발생(emit)</button>
            </div>
            <hr>

            <div>
                <!-- 부모에서도 값의 기록을 남겨야 할 때 사용..?
                    어떤 때일까? -->
                <!-- 실제 프로젝트에선 이렇게만 사용할 수 있을까..?
                    부모 -> 자식 -> 자식 -> 자식 ... 골치 아프다...
                    정확한 명세서를 작성해야 될 듯하다. -->
                <!-- 자식 쪽에서 이벤트를 발생시켜 값을 부모쪽으로 보내고,
                    부모 쪽에서 값의 상태를 변화시켜 다시 자식 쪽으로 값을 전달 -->
                <h6>[자식 -> 부모 -> 자식]</h6>
                <button class="btn btn-warning btn-sm me-3" @click=handleIncrementEvent>증가(increment-event)</button>
                <!-- 이번엔 emit을 사용하지 않고 작성 -->
                <button class="btn btn-info btn-sm" @click="handleDecrementEvent">감소(decrement-event)</button>
                <p class="mt-2">Counter : {{ counter }}</p>
            </div>
        </div>
    </div>
</template>

<script setup>
import { ref } from 'vue';

let data1 = ref("value1");
let data2 = ref(100);

// 'child-event-1'가 이벤트인 것을 알려준다.
const emit = defineEmits(["child-event-1", "child-event-2", 'increment-event', 'decrement-event']);

function handleChildEvent2() {
    emit("child-event-2", data1, data2); // 값을 2개를 주어봄.

}

// ----

function handleIncrementEvent() {
    emit("increment-event", 1);
}

function handleDecrementEvent() {
    emit("decrement-event", 1);
}

const prop = defineProps(["counter"]);

</script>

<style scoped></style>

 

# 각각의 컴포넌트 A -> B -> C -> D가 있다고 가정하자.

여기서 바로 A -> D로 갈 수가 없다.

따라서 각 컴포넌트들을 거쳐 수도없이 많은 중첩을 일으킨다. (이런 식으로 많은 컴포넌트 사이에서는 사용하지 말자.)

관리를 못하게된다.

 

아직 배우지 않았지만, '전역적인 상태값 변환'을 사용해야 한다.

 

이 방식의 단점을 극복한 것을 다음 시간에 배울 것이다. ppt 94p

최종적으론 Ch07. Vuex 전역 상태 관리를 배우고 그것을 사용할 것이다.

 


다음 주중으로 프론트, 백엔드 수업이 모두 끝날 것이다.

다음 주까지 프로젝트 회의와 결정을 하고 바로 요구분석까지!!

+ Recent posts