ppt 66p

템플릿의 데이터를 바인딩

저번 수업 때는 '단방향' 바인딩

 

오늘 학습할 내용 V-디렉티브를 이용한 바인딩

p71

SPA에서는 a태그를 사용하면 안된다고 했다. -> RouterLink를 사용

 외부파일을 참조하여 가져올 때는 require함수 형태로 작성하여 가져온다.

쉽게 말해 앞에 콜론(:)이 붙는 경우 자바스크립트 코드처럼 사용한다.

 

실습해보자.

 

1. Ch03DataBinding 폴더에 Exam05AttrBinding.vue 생성

2. 라우터 폴더의 Ch03DataBinding.js 컴포넌트 추가

3. 컴포넌츠 폴더의 AppMenu.vue 리스트 하나 추가

 

4. Exam05AttrBinding.vue 코드 작성

<template>
    <div class="card">
        <div class="card-header">Exam05AttrBinding</div>
        <div class="card-body">
            <div class="mb-4">
                <h5>:속성 = "변수"</h5>
                <!-- <a href="https://www.vuejs.org" class="me-3">Vue 홈페이지</a> -->
                <a :href="vueHome" class="me-3">Vue 홈페이지</a>
                <!-- <RouterLink to="/">홈 페이지</RouterLink> -->
                <RouterLink :to="appHome">홈 페이지</RouterLink>
            </div>
            <hr>

            <div class="mb-4">
                <h5>:속성 = "연산식" | `매개변수화된 문자열`</h5>
                <!-- <img src="@/assets/photos/photo1.jpg" height="150" class="me-2"/> -->
                <img :src="require('@/assets/photos/' + imgName)" height="150" class="me-2" />
                <img :src="require(`@/assets/photos/${imgName}`)" height="150" class="me-2" />
            </div>
            <hr>

            <div class="mb-4">
                <h5>:class="변수 | 객체 | 배열"</h5>
                <div class="mb-3 fw-bold">아름다운 풍경</div> <!-- fw = font-weight -->
                <div class="mb-3" :class="className1">아름다운 풍경</div> <!-- mr-3과 className이 더해짐 -->
                <div class="mb-3" :class="[className1, className2]">아름다운 풍경</div> <!-- 배열식으로 사용 가능 -->
                <div class="mb-3" :class="{'fw-bold':true, 'text-info':true}">아름다운 풍경</div> <!-- 자바스크립트의 객체 표시 {키:값} // true, false 값은 우리가 변수를 넣어 제어 가능하다.-->
                <div class="mr-3" :class="{'fw-bold':isBold, 'text-danger':isRed}">아름다운 풍경</div>
            </div>
            <hr>

            <div class="mb-4">
                <h5>:style="변수 | 객체 | 배열"</h5>
                <div style="margin-bottom: 10px; font-weight: bold;">아름다운 풍경</div>
                <div :style="[style1, style2]">아름다운 풍경</div>
                <div style="margin-bottom: 10px" :style="{'font-weight': fontWeight, 'color':textColor}">아름다운 풍경</div>
            </div>
            <hr>

            <button class="btn btn-info btn-sm" @click="changeData">상태 변수값 변경</button>
        </div>
    </div>
</template>

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

let vueHome = ref("https://vuejs.org");
let appHome = ref("/");

let imgName = ref("photo1.jpg");

let className1 = ref("fw-bold");
let className2 = ref("text-danger");
let isBold = ref(false);
let isRed = ref(true);

let style1 = ref("margin-bottom:10px");
let style2 = ref("font-weight: bold; color:red");

// let style3 = ref("font-weight: bold;");
// let style4 = ref("color: green;");

let fontWeight = ref('bold');
let textColor = ref('green');

let toggle = ref(true);
function changeData() {
    if(toggle.value) {
        imgName.value = "photo2.jpg";
        className1.value = "text-warning";
        fontWeight.value = "";
        textColor.value = "#0000ff";
    } else {
        imgName.value = "photo1.jpg";
        className1.value = "text-danger";
        fontWeight.value = "bold";
        textColor.value = "#00ff00";
    }
    toggle.value = !toggle.value;
}

</script>

<style scoped></style>

 

AJAX를 사용하여 innerHTML. html의 조각을 얻어 적용을 한다면?

 

1. view폴더 - Ch03DataBinding폴더 - Exam06InnerHtmlBinding.vue 생성 (기초 코드만 작성)

2. router폴더 - Ch03DataBinding.js 파일에 경로와 컴포넌트 코드 추가

3. components폴더 - AppMenu.vue파일에 리스트 코드 추가

 

cf) src 밑의 자원들은 필요에 따라서 pulbic으로 이동하게 된다. require에 의해 변환되어 public/ 경로로 설정이 바뀌거나 한다.

 

Exam06InnerHtmlBinding.vue

<template>
    <div class="card">
        <div class="card-header">Exam06InnerHtmlBinding</div>
        <div class="card-body">
            <div v-html="innerHtml"></div>
            <!-- 테스트 용도 : <img src="/images/photos/photo1.jpg" height="150"/> -->
            <hr>
            <button @click="changeData" class="btn btn-info btn-sm">innerHTML 변경</button>
        </div>
    </div>
</template>

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

let innerHtml = ref("");

let toggle = ref(true);
function changeData() {
    if (toggle.value) {
        // @assets/ 경로를 못잡기 때문에 public/images/photos에서 가져오게끔 바꿈
        innerHtml.value = `
            <div>
                <h5>풍경 1</h5>
                <img src="/images/photos/photo1.jpg" height="150"/>
            </div>
        `;
    } else {
        innerHtml.value = `
            <div>
                <h5>풍경 2</h5>
                <img src="/images/photos/photo2.jpg" height="150"/>
            </div>
        `;
    }
    toggle.value = !toggle.value;
}

</script>

<style scoped></style>

 

 

cf) 리액트나 뷰에서는 태그에 id를 거의 사용하지 않는다.

 

1. view폴더 - Ch03DataBinding폴더 - Exam07IfShowBinding.vue 생성 (기초 코드만 작성)

2. router폴더 - Ch03DataBinding.js 파일에 경로와 컴포넌트 코드 추가

3. components폴더 - AppMenu.vue파일에 리스트 코드 추가

 

Exam07IfShowBinding.vue

<template>
    <div class="card">
        <div class="card-header">Exam07IfShowBinding</div>
        <div class="card-body">
            <div id="" v-if="isPhoto" class="mb-2">
                <img src="/images/photos/photo3.jpg" height="150">
            </div>

            <div v-show="!isPhoto">
                <img src="/images/photos/photo4.jpg" height="150">
            </div>
        </div>
    </div>
</template>

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

let isPhoto = ref(true);
</script>

<style scoped></style>

 

v-if는 false가 되면 아예 DOM에서 추가가 안됨

v-show는 false가 되어도 DOM에 추가는 되지만 화면에서는 안보인다.

 

태그를 계속적으로 추가. 반복할 태그에 지정. ( 배열일 경우 / 객체일 경우 )

대부분 배열일 경우로 사용하고 객체일 경우는 별로 없다..

 

:key는 반복하는 것에 대해 식별할 수 있는 값. ex) 게시물을 식별할 수 있는 값은? - 게시물 번호

v-for를 쓰면 항상 :key가 따라와야 한다.

 

우리가 이미지 파일을 저장할 때는 assets와 public에서 저장할 수 있다.

cf) assets 밑의 이미지 파일과 public 밑의 이미지 파일을 사용할 때의 차이점은?

assets에서 사용할 경우 빌드하는 과정에서 차이가 있다. @/assests을 사용하면서 경로를 잡아주게 되고

:src="require('@/assets/ ... ')" 로 사용하거나 :src="require(`@/assets/ ... `)" 빽틱을 사용할 수도 있다. (코드로 인식)

assets 디렉토리

  • 빌드 처리: assets 디렉토리의 파일은 Vue CLI나 Vite 등의 번들러에 의해 처리됩니다. 파일은 해시값이 포함된 이름으로 변경되어, 캐싱 문제를 방지합니다.
  • 경로 접근: assets 디렉토리의 파일에 접근할 때는 주로 import 문이나 require 문을 사용합니다. JavaScript 또는 Vue 파일 내에서 모듈 방식으로 불러와야 합니다.

public 디렉토리

  • 빌드 처리: public 디렉토리의 파일은 빌드 과정에서 그대로 복사됩니다. 파일 이름이나 내용은 변경되지 않습니다.
  • 경로 접근: public 디렉토리의 파일은 정적 파일로 취급되며, 애플리케이션의 루트 경로(/)를 기준으로 접근할 수 있습니다. 즉, 절대 경로로 접근합니다.
  • 사용 예시:

# 정리

  • assets 디렉토리:  번들링 및 최적화를 위해 파일을 처리할 필요가 있을 때 사용합니다. 주로 이미지, 스타일 시트, JavaScript 모듈 등을 포함합니다.
  • --------------
  • public 디렉토리:  빌드 과정에서 파일을 변경하지 않고 그대로 유지하고 싶을 때 사용합니다. 주로 정적 리소스, favicon, 로봇 파일 등을 포함합니다.

 

1. view폴더 - Ch03DataBinding폴더 - Exam08RepeatBinding.vue 생성 (기초 코드만 작성)

2. router폴더 - Ch03DataBinding.js 파일에 경로와 컴포넌트 코드 추가

3. components폴더 - AppMenu.vue파일에 리스트 코드 추가

 

Exam08RepeatBinding.vue

<template>
    <div class="card">
        <div class="card-header">Exam08RepeatBinding</div>
        <div class="card-body">
            <div>
                <h6>[범위 반복]</h6>
                <span v-for="n in 3" :key="n" class="me-2">
                    <!-- 빽틱(`)이 들어가게 되면 코드이다. -->
                    <img :src="`/images/photos/photo${n}.jpg`" width="150" height="150" />
                </span>
            </div>
            <hr>

            <div class="mt-5">
                <h6>[배열항목 반복]</h6>
                <span v-for="(item, index) in photos" :key="index" class="me-2">
                    <!-- 빽틱(`)이 들어가게 되면 코드이다. -->
                    <img :src="`/images/photos/${item}`" width="150" height="150" />
                </span>
            </div>
            <hr>

            <div class="mt-5">
                <h6>[배열항목 반복]</h6>
                <table class="table table-bordered">
                    <thead>
                        <tr>
                            <th>No</th>
                            <th>Title</th>
                            <th>Writer</th>
                            <th>Date</th>
                        </tr>
                    </thead>
                    <tbody>
                        <!-- 태그 안 내용에 값을 나타내려면 표현식 {{  }} 을 사용 -->
                        <tr v-for="item in boards" :key="item.bno">
                            <td>{{ item.bno }}</td>
                            <td>{{ item.btitle }}</td>
                            <td>{{ item.bwriter }}</td>
                            <td>{{ item.bdate }}</td>
                        </tr>
                    </tbody>
                </table>
            </div>
            <hr>

            <div class="mt-5">
                <!-- 이렇게 사용하는 경우는 드물다.. -->
                <h6>[객체 속성 반복]</h6>
                <div v-for="(value, name, index) in board" :key="index">
                    <p>{{ index }}. {{ name }}: {{ value }}</p>
                </div>
            </div>

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

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

// 배열
const photos = ref(["photo1.jpg", "photo2.jpg", "photo3.jpg"]);

// 객체를 담은 배열
// 실제로 백엔드 쪽에서 이렇게 배열로 받고 컴포넌트에 활용해야 할 것.
const boards = ref([
    { bno: 1, btitle: "제목1", bwriter: "글쓴이1", bdate: "2021-08-07" },
    { bno: 2, btitle: "제목2", bwriter: "글쓴이2", bdate: "2021-08-08" },
    { bno: 3, btitle: "제목3", bwriter: "글쓴이3", bdate: "2021-08-09" }
]);

// 하나의 객체
const board = ref({
    bno: 4,
    btitle: "제목4",
    bcontent: "내용4",
    bwriter: "글쓴이4",
    bdate: "2021-08-10"
});

</script>

<style scoped></style>

 

양방향을 사용할 경우 v-model

v- 가 들어가면 뒤에는 '코드'

 

ex) 사용자가 입력시 바로 product.name 등 변수에 저장된다(프론트에서 백으로 데이터 즉시 전달 후 저장). 그래서 '양방향'이라고 부르는 것이다.

 

checkbox는 보통 name으로 그룹을 형성하고 서로 구별한다. 위의 그림에서는 v-model의 변수 값이 product.colors로 변수 값으로 name처럼 서로를 구별할 수 있게 형성되어 있다.

 

이전에 배웠던 v-on:click = @click처럼 위에서도 @submit으로 사용할 수 있다.

유효성 검사기능을 @submit.prevent에서 사용할 수 있다.

 

1. view폴더 - Ch03DataBinding폴더 - Exam09FormBinding.vue 생성 (기초 코드만 작성)

2. router폴더 - Ch03DataBinding.js 파일에 경로와 컴포넌트 코드 추가

3. components폴더 - AppMenu.vue파일에 리스트 코드 추가

 

양방향 데이터 바인딩

cf) ref는 상태를 만드는 것.

 

Exam09FormBinding.vue

<template>
    <div class="card">
        <div class="card-header">Exam09FormBinding</div>
        <div class="card-body">
            <!-- <form> 프론트엔드에서 form이 눌렸다고 해서 다른 서버로(요청반응) 가면 안된다. -->
            <!-- <form v-on:submit.prevent="handleSubmit"> -->
            <form @submit.prevent="handleSubmit">
                <div class="input-group mb-2">
                    <span class="input-group-text">상품</span>
                    <input type="text" class="form-control" v-model="product.name">
                </div>

                <div class="input-group mb-2">
                    <span class="input-group-text">회사</span>
                    <input type="text" class="form-control" v-model="product.company">
                </div>

                <div class="input-group mb-2">
                    <span class="input-group-text">가격</span>
                    <input type="number" class="form-control" v-model="product.price">
                </div>

                <div class="input-group mb-2">
                    <span class="input-group-text">설명</span>
                    <input type="text" class="form-control" v-model="product.info">
                </div>

                <div class="input-group mb-2">
                    <span class="input-group-text">국가</span>
                    <select class="form-control" v-model="product.madein">
                        <option value="한국">한국</option>
                        <option value="미국">미국</option>
                        <option value="독일">독일</option>
                    </select>
                </div>

                <div class="input-group mb-2">
                    <span class="input-group-text">색상</span>
                    <div class="form-control">
                        <div class="form-check form-check-inline">
                            <!-- 체크박스는 하나의 그룹(name속성으로 이용하여)안에서 다중값을 선택할 수 있다.
                                product.colors의 값은 배열값을 가지게 된
                                 -->
                            <input id="checkboxBlack" class="form-check-input" type="checkbox" value="black"
                                v-model="product.colors">
                            <label class="form-check-label" for="chekboxBlack">블랙</label>
                        </div>
                        <div class="form-check form-check-inline">
                            <input id="checkboxRed" class="form-check-input" type="checkbox" value="red"
                                v-model="product.colors">
                            <label class="form-check-label" for="chekboxRed">레드</label>
                        </div>
                        <div class="form-check form-check-inline">
                            <input id="checkboxYellow" class="form-check-input" type="checkbox" value="yellow"
                                v-model="product.colors">
                            <label class="form-check-label" for="chekboxYellow">옐로우</label>
                        </div>
                    </div>
                </div>

                <div class="input-group mb-2">
                    <span class="input-group-text">판매1</span>
                    <div class="form-control">
                        <div class="form-check form-check-inline">
                            <!-- 체크박스가 하나인 경우 product.sale1은 배열이 아니라
                                 하나의 값이다. 하나밖에 없기 때문에 ture / false의 값을 가진다. -->
                            <input class="form-check-input" type="checkbox" id="chekboxSale1" value="true"
                                v-model="product.sale1">
                            <label class="form-check-label" for="chekboxSale1">판매1</label>
                        </div>
                    </div>
                </div>

                <div class="input-group mb-2">
                    <span class="input-group-text">판매2</span>
                    <div class="form-control">
                        <div class="form-check form-check-inline">
                            <!-- 위의 코드와 다른 점 true-value / false-value 
                                이 경우에 값을 yes로 주어야 체크가 됨-->
                            <input class="form-check-input" type="checkbox" id="chekboxSale2" v-model="product.sale2"
                                true-value="yes" false-value="no">
                            <label class="form-check-label" for="chekboxSale2">판매2</label>
                        </div>
                    </div>
                </div>

                <div class="input-group mb-2">
                    <span class="input-group-text">성별</span>
                    <div class="form-control">
                        <div class="form-check form-check-inline">
                            <!-- 이번엔 radio 버튼 -->
                            <input class="form-check-input" type="radio" id="chekboxSex1" value="man"
                                v-model="product.sex">
                            <label class="form-check-label" for="chekboxSex1">남성</label>
                        </div>
                        <div class="form-check form-check-inline">
                            <input class="form-check-input" type="radio" id="chekboxSex2" value="woman"
                                v-model="product.sex">
                            <label class="form-check-label" for="chekboxSex2">여성</label>
                        </div>
                    </div>
                </div>

                <div>
                    <input type="submit" value="등록" class="btn btn-danger btn-sm me-2" />
                    <!-- type="reset"은 처음 값으로 돌아가게 만듦 
                        Vue에서 리셋 버튼은 양식을 초기화하지 않음 -->
                    <!-- 체크 박스가 디폴트 값으로 바인딩 되지 않음 -->
                    <!-- <input type="reset" value="재작성" class="btn btn-info btn-sm" /> -->
                    <!-- 폼 태그 안의 버튼은 기본적으로 submit효과를 가지기 때문에
                        type="button"을 명시해주어야 한다. -->
                    <button type="button" class="btn btn-info btn-sm" @click="handleReset">재작성</button>
                </div>
            </form>
        </div>
    </div>
</template>

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

// 디폴트
let product = ref({
    name: "셔츠",
    company: "지오다노",
    price: "20000",
    info: "통풍이 잘되어 시원해요",
    madein: "미국",
    colors: ["black", "yellow"],
    //sale1: "black" // 체크가 되지 않는다.
    sale1: true,
    // sale2: true
    sale2: "yes",
    sex: "woman"
});

function handleSubmit() {
    console.log(product.value);
    console.log(JSON.stringify(product.value)); // 객체를 문자열로 바꿈
    console.log(JSON.parse(JSON.stringify(product.value))); // 문자열을 객로 바꿈

    // 여기에 유효성 검사 ...

    // 여기에 서버 통신 코드 추가 ...

}

function handleReset() {
    console.log("handleReset 실행")
    // product = ref({
    //     name: "셔츠",
    //     company: "지오다노",
    //     price: "20000",
    //     info: "통풍이 잘되어 시원해요",
    //     madein: "미국"
    // });

    // 상태를 바꾸면 console에 나오는 값은 변한 값이 나오지만 사용자 UI에서는 재작성 버튼을 눌렀을 때 바뀐 값이 나오지 않는다. 이전 주소를 참조하고 있기 때문이다.
    // product의 상태를 바꾸는 것이 아닌 value를 바꾸어보자. (객체 통째로 value를 바꿈)
    product.value = {
        name: "셔츠",
        company: "지오다노",
        price: "20000",
        info: "통풍이 잘되어 시원해요",
        madein: "미국"
    };

    // 객체 안의 하나의 인자 값을 바꿈
    product.value.name = "셔츠";
    product.value.company = "지오다노";
}
</script>

<style scoped></style>

 

 

*바인딩은 아니지만 비슷한 속성을 가진다.

템플릿의 첫번째 엘리먼트의 속성으로 모두 들어가게 된다. (기본적으로 이런 것을 Fallthrough 속성이라 한다)

 

 

1. view폴더 - Ch03DataBinding폴더 - Exam10FallthroughAttr폴더 생성 - index.vue파일 생성(기초 코드만 작성)

2. router폴더 - Ch03DataBinding.js 파일에 경로와 컴포넌트 코드 추가

3. components폴더 - AppMenu.vue파일에 리스트 코드 추가

4. view폴더 - Ch03DataBinding폴더 - Exam10FallthroughAttr폴더 - UIComponentA.vue 파일 생성

5. index.vue의 card-body부분에 <UIComponentA /> 추가.

<template>
    <div class="card">
        <div class="card-header">Exam10FallThroughAttr</div>
        <div class="card-body">
            <UIComponentA />
        </div>
    </div>
</template>

<script setup>
// import UIComponentA from '@/components/Ch02ComponentRouting/UIComponentA.vue';
import UIComponentA from './UIComponentA.vue';

</script>

<style scoped></style>

 

 

이렇게 Template의 첫번제 엘리멘트 속성에 들어가게 된다.

 

 

UIComponentA 카드 부분을 클릭 시 console.log를 이용하여 확인

 

defineProps를 이용하여 "class"와 "style"의 속성을 다른곳에 사용하려 변수를 선언.

class와 style의 값이 태그에서 빠진 것을 볼 수 있다.

@click="handleClick"도 events 변수로 값을 빼옴..

 

class와 style이 예약어이기 때문에 적용에 실패... ★ 예약어는 피하자!

다시 시도

.여기서 click도 예약어이기 때문에 콘솔에 출력이 되지 않는다.

--> onClick으로 변경해주자.

 

※ 여기에서 id만 Fallthrough ???

 

 

이 방법을 주로 사용한다.

 

# UIComponentB.vue 작성하여 다른 방법으로 사용해보기

위 코드의 방식으로 사용하려면 Fallthrough 속성을 꺼야 한다. 때문에 false로 두는 것이다.

<template>
    <div class="card" :id="$attrs.id">
        <div class="card-header">UIComponentB</div>
        <div class="card-body">
            <!-- 템플릿에서만 $attrs. 를 붙인다. -->
            <div :class="$attrs.myclass" :style="$attrs.mystyle" @click="$attrs.onClick">
                Contents
            </div>
            <hr>
            <button class="btn btn-info btn-sm me-3" @click="handleBtn1">버튼 1 (속성값 이용)</button>
            <button class="btn btn-info btn-sm" @click="handleBtn2">버튼 2 (리스너 호출)</button>
        </div>
    </div>
</template>

<script setup>
import { defineOptions, useAttrs } from 'vue';

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

const attrs = useAttrs();

function handleBtn1() {
    // '속성'으로 전달된 값 얻기
    console.log(attrs.id);
    console.log(attrs.myclass);
    console.log(attrs.mystyle);
}

function handleBtn2() {
    // '속성'으로 전달된 핸들러 함수(리스너)를 호출
    attrs.onClick();
}

</script>

<style scoped></style>

 

# 앞으로 접두사에 'use'가 들어가는 것은 'hook'을 이용한다고 생각하면 된다.

 

# 앞으로 <UIComponentB/> 만 사용하는 경우가 대부분이겠지만, 경우에 따라서는 

<UIComponentB id="uiComponentB" myclass="bg-dark" mystyle="color:white" @click="handleClick" /> 이렇게 사용할줄도 알아야 한다!!

--> 게시판 댓글 / 리뷰 등록에 사용하면 좋을 것 같다!

 

index.vue

<template>
    <div class="card">
        <div class="card-header">Exam10FallthroughAttr</div>
        <div class="card-body">
            <!--id는 UIComponentA.vue에 루트 엘리먼트 속성으로 들어가게 된다.-->
            <UIComponentA id="uiComponentA" myclass="bg-warning" mystyle="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 UIComponentA 클릭됨");
}
</script>

<style scoped></style>

이런 식으로 속성을 다르게 주고  UIComponent를 재사용할 수 있다. 

 

탈부착 가능한 슬롯을 의미한다.

cf) 슬롯은 꼭 name이 있어야 한다.

 

1. view폴더 - Ch03DataBinding폴더 - Exam11Slot폴더 생성 - index.vue, DialogTemplate.vue, DialogA .vue파일 생성

DialogTemplate는 DialogA 를 사용하고 index는 DialogTemplate을 사용한다.

-> 기초적인 코드 작성

index.vue

<template>
    <div class="card">
        <div class="card-header">Exam11Slot</div>
        <div class="card-body">
            <button class="btn btn-info btn-sm" @click="showDialogA">기본 다이얼로그</button>
        </div>

        <DialogA id="dialogA" />

    </div>
</template>

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

let modalDialogA = null;

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

function showDialogA() {
    modalDialogA.show();
}
</script>

<style scoped></style>

 

DialogTemplate.vue (모달 사용)

<template>
    <div class="modal" tabindex="-1">
        <div class="modal-dialog modal-dialog-centered">
            <div class="modal-content">
                <div class="modal-header">
                    <h5 class="modal-title">
                        <!-- slot -->
                        <slot name="header">제목</slot>
                    </h5>
                    <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
                </div>
                <div class="modal-body">
                    <!-- slot -->
                    <slot name="body">
                        <p>다이얼로그 내용</p>
                    </slot>
                </div>
                <div class="modal-footer">
                    <slot name="footer">
                        <!-- slot -->
                        <button type="button" class="btn btn-info btn-sm" data-bs-dismiss="modal">확인</button>
                        <button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">닫기</button>
                    </slot>
                </div>
            </div>
        </div>
    </div>
</template>

<script setup>
</script>

<style scoped></style>

 

DialogA.vue

<template>
    <DialogTemplate />
</template>

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

<style scoped></style>

 

# 모든 다이알로그는 헤더, 바디, 풋터가 있다. 구조는 똑같지만 각각의 위치는 내용이 다를 수 있다.

다이얼로그템플레이트는 '틀'이고, 코드에서 Slot 부분은 교체될 수 있다.

 

2. router폴더 - Ch03DataBinding.js 파일에 경로와 컴포넌트 코드 추가

3. components폴더 - AppMenu.vue파일에 리스트 코드 추가

4. DialogB.vue 생성

- 여기에선 Slot을 교체할 것이다.

<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">확인</button>
        </template>


    </DialogTemplate>
</template>

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

<style scoped></style>

 

 

5. 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">로그인</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 inputData = ref({
    email: "",
    password: ""
});
</script>

<style scoped></style>

 

# Slot을 사용한다면 중복된 코드를 줄일 수 있다.

 

# 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" />
        <DialogB id="dialogB" />
        <DialogC id="dialogC" />

    </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();
}
</script>

<style scoped></style>

 

+ Recent posts