티스토리 뷰

728x90

 

이번에는 v-model 키워드를 활용해서 form 요소에 양방향 바인딩(2 way binding)하는 방법을 배워 봅니다. 또한 사용자 입력값을 유효성 체크하는 로직도 추가합니다.

<!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>
            <div>
                <h2>Reviews</h2>
                <p v-if="!reviews.length">There are no reviews yet.</p>
                <ul>
                    <li v-for="review in reviews">
                        <p>{{ review.name }}</p>
                        <p>Rating: {{ review.rating }}</p>
                        <p>{{ review.review }}</p>
                    </li>
                </ul>
            </div>
            <product-review @review-submitted="addReview"></product-review>
        </div>

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

input, textarea, select, submit 요소를 가진 리뷰(product-review) 컴포넌트를 추가하고 v-model로 각 값을 바인딩시킵니다.

리액트의 경우 state를 컴포넌트 내부에 선언하고 setState 함수로 업데이트하는 방식인데 vue 경우 v-model로 연동하면 자동으로 바인딩돼서 더 간단한 느낌입니다.

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
            }
            this.$emit('review-submitted', productReview)
            this.name = null
            this.review = null
            this.rating = 5
        }
    }
})

var app = new Vue({
    el: '#app',
    data: {
        premium: false,
        cart: [],
        reviews: []
    },
    methods: {
        addToCart(id){
            this.cart.push(id)
        },
        addReview(productReview){
            this.reviews.push(productReview)
        }
    }
})

또한 이벤트 핸들링 시. prevent 값을 추가하면 이벤트의 기본 동작을 쉽게 막을 수 있습니다.

물론 연결될 onSubmit 함수 인자로 event 객체가 전달되기 때문에 메서드 내에서 event.preventDefault()를 수행시킬 수도 있습니다.

에러 메시지가 지속적으로 노출되는 버그가 있어서 전송 후 에러 데이터를 초기화하는 코드를 넣었습니다.

 

이름, 리뷰, 점수 중 하나라도 빈 값이면 체크하는 로직을 코드 상단으로 배치했습니다. 개인 취향이지만 예외 조건을 상단에서 처리하면 이후 코드가 수행되지 않아 불필요한 코드 실행을 방지할 수 있습니다.

728x90
댓글