티스토리 뷰

728x90

리뷰 입력과 리뷰 목록을 탭 메뉴로 분리하는 작업을 진행한다.

Vue 인스턴스를 생성하여 전역 이벤트 객체를 만들고 서로 연동하는 방식이 흥미롭다. 하지만 프로그램이 규모가 커진다면 디버깅하기 무척 어려울 것으로 추정된다. 우선 따라 해 보기로 했다.

 

 

Tabs - Intro to Vue.js | Vue Mastery

In this lesson, we’ll learn how to add tabs to our application and implement a simple solution for global event communication.

www.vuemastery.com

reviews 데이터를 product-tabs 컴포넌트에 props로 전달한다.

<!DOCTYPE html>
<html>
    <head>
        <title>App</title>
        <link rel="stylesheet" type="text/css" href="./style.css">
    </head>
    <body>
        <div class="nav-bar"></div>
        <div id="app">
            <div class="cart">
                <p>Cart({{ cart.length }})</p>
            </div>
            <product :premium="premium" @add-to-cart="addToCart"></product>
            <product-tabs :reviews="reviews"></product-tabs>
        </div>

        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
        <script src="main.js"></script>
    </body>
</html>

 

const eventBus = new Vue()

Vue.component("product", {
    props: {
        premium: {
            type: Boolean,
            required: true
        },
    },
    template: `
    <div class="product">
        <div class="product-image">
            <img v-bind:src="image"/>
        </div>
        <div class="product-info">
            <h1>{{ title }}</h1><span v-show="onSale">On Sale!</span>
            <p v-if="inStock">In Stock</p>
            <p v-else>Out of Stock</p>
            <p>Shipping: {{ shipping }}</p>

            <ul>
                <li v-for="detail in details">{{ detail }}</li>
            </ul>

            <div class="color-box"
                v-for="(variant, index) in variants" 
                :key="variant.variantId"
                :style="{backgroundColor: variant.variantColor}"
                @mouseover="updateProduct(index)"
                >
            </div>

            <button v-on:click="addToCart"
            :disabled="!inStock"
            :class="{ disabledButton: !inStock }"
            >Add to cart</button>
        </div>
    </div>
    `,
    data(){
        return {
            brand: 'Vue Mastery',
            product: 'Socks',
            selectedVarint: 0,
            onSale: false,
            details: ["80% cotton", "20% polyester", "Gender-neutral" ],
            variants: [
                {
                    variantId: 2234,
                    variantColor: "green",
                    variantImage: "./assets/vmSocks-green-onWhite.jpg",
                    variantQuantity: 10
                },
                {
                    variantId: 2235,
                    variantColor: "blue",
                    variantImage: "./assets/vmSocks-blue-onWhite.jpg",
                    variantQuantity: 0
                }
            ],
            cart: 0,
        }
    },
    methods: {
        addToCart(){
            this.$emit("add-to-cart", this.variants[this.selectedVarint].variantId)

        },
        updateProduct(index){
            this.selectedVarint = index
        },
    },
    computed: {
        title(){
            return `${this.brand} ${this.product}`
        },
        image(){
            return this.variants[this.selectedVarint].variantImage
        },
        inStock(){
            return this.variants[this.selectedVarint].variantQuantity
        },
        shipping(){
            if(this.premium){
                return "Free"
            } else {
                return 2.99
            }
        }
    }
})

Vue.component("product-review", {
    template: `
        <form class="review-form" @submit.prevent="onSubmit">
            <p v-if="errors.length">
                <b>Please correct the following errors(s):</b>
                <ul>
                    <li v-for="error in errors">{{ error }}</li>
                </ul>
            </p>
            <p>
                <label for="name">Name:</label>
                <input id="name" v-model="name" placeholder="name">
            </p>
            <p>
                <label for="review">Review:</label>
                <textarea id="review" v-model="review"></textarea>
            </p>
            <p>
                <label for="rating">Rating:</label>
                <select id="rating" v-model.number="rating">
                    <option>5</option>
                    <option>4</option>
                    <option>3</option>
                    <option>2</option>
                    <option>1</option>
                </select>
            </p>
            <p>
                <input type="submit" value="Submit">
            </p>
        </form>
    `,
    data() {
        return {
            name: null,
            review: null,
            rating: 5,
            errors: [],
        }
    },
    methods: {
        onSubmit(){
            if(!this.name ||  !this.review || ! this.rating){
                this.errors = []
                if(!this.name) this.errors.push("Name required")
                if(!this.review) this.errors.push("Review required")
                if(!this.rating) this.error.push("Rating required")
                return
            }

            const productReview = {
                name: this.name,
                review: this.review,
                rating: this.rating
            }
            eventBus.$emit('review-submitted', productReview)
            this.name = null
            this.review = null
            this.rating = 5
            this.errors = []
        }
    },
})

Vue.component("product-tabs", {
    props: {
        reviews: {
            type: Array,
            required: true
        }
    },
    template: `
        <div>
            <div>
                <span class="tab" 
                v-for="(tab, index) in tabs" 
                :key="index"
                @click="selectedTab = tab"
                :class="{ activeTab: selectedTab === tab }"
                >{{ tab }}</span>
            </div>

            <div v-show="selectedTab === 'Reviews'">
            <p v-if="!reviews.length">There are no reviews yet.</p>
            <ul v-else>
                <li v-for="review in reviews">
                    <p>{{ review.name }}</p>
                    <p>Rating: {{ review.rating }}</p>
                    <p>{{ review.review }}</p>
                </li>
            </ul>
            </div>
            <product-review 
            v-show="selectedTab === 'Make a Review'"></product-review>
        </div>
    `,
    data(){
        return {
            selectedTab: "Reviews",
            tabs: ["Reviews", "Make a Review"]
        }
    }
})

var app = new Vue({
    el: '#app',
    data: {
        premium: false,
        cart: [],
        reviews: []
    },
    methods: {
        addToCart(id){
            this.cart.push(id)
        },
    },
    mounted(){
        eventBus.$on('review-submitted', productReview => {
            this.reviews.push(productReview)
        })
    }
})

 

product-tabs 컴포넌트를 눈여겨 보면 props 유효성 값을 설정하고 탭이 클릭됐을 때 클릭된 탭 데이터를 selectedTab에 저장한다. 그리고 v-show로 값이 일치하면 노출한다.

 

그리고 eventBus를 생성하고 product-review 컴포넌트에서 submit 버튼이 눌렸을때 this.$emit 코드를 eventBus.$emit으로 변경한다.

Vue의 인스턴스를 생성하는 옵션에 mouted가 추가 되었다. 리액트와 동일하게 vue에도 라이프 사이클이 존재하는데 컴포넌트가 mounted 되는 시점에 해당 이벤트를 바인딩해주도록 되어 있다.

 

최종 결과물은 다음과 같다.

 

 

이로써 Buemastery의 Intro to Vue.js 코스를 모두 마쳤다.

 

728x90
댓글