5 vue 스타일 가이드

source: categories/study/vue-principle/vue-principle_5.md

5 vue 스타일 가이드

1. 뷰 스타일 가이드

  • 뷰 스타일 가이드
  • 컴포넌트 이름은 2 단어로.. 필수처럼 지켜야되는 규칙
  • 위 사이트에 나오는 우선순위 A, B는 다 지키는게 좋다.
    1. 컴포넌트는 2단어 이상으로 되어있어야한다. (필수)
    2. 컴포넌트 data는 함수여야한다. (필수)
    3. props는 반드시 타입을 정의해줘야한다.
    4. v-for를 사용할 때 :key를 붙여줘야한다.
      • :key가 필요한 이유
      • 반복하는 태그를 기억하기 위해
      • 반복하는 태그 외에 새로 반복되는 태그가 추가될 때, 반복되는 태그 모두를 재렌더링하는 것이 아니라 key로 기억해 새로 추가된 태그만 랜더링하도록 하는 장치.
    5. v-if, v-for 동시 사용하지 말아야한다.

등등..
이런 것들 반드시 지키는 것이 좋다.

2. 자식 컴포넌트와 props

  • 상위 컴포넌트에서 하위 컴포넌트로 데이터 전달 props
  • 하위 컴포넌트에서 전달받은 props를 수정하는 행위는 금지
  • readonly 개념이다. 값을 props로 받을 수는 있지만 바꿀 수는 없다.

3. 왜 상위 컴포넌트에서 하위 컴포넌트로 값을 props로 내려줘야될까?

  • LottoGenerator 라는 컴포넌트에서 당첨 숫자를 뽑는다.
  • 뽑힌 숫자를 각 LottoBall 컴포넌트에 쓸 것이다.
  • 로또 당첨 숫자를 만드는 곳은 LottoGenerator이고 LottoBall은 그 숫자를 화면에 표시하는 역할을 하는 컴포넌트
  • 데이터가 만들어진 위치랑 데이터를 표시하는 위치가 다르다.
Note

상위 컴포넌트에게서 받은 props를 하위 컴포넌트에서도 수정할 수 있다.
propsdata에 저장하고 그 data를 수정하면 되긴한다.
하지만 이는 그닥 추천하지 못한다.
왜냐면 상위 컴포넌트와 하위 컴포넌트 값이 서로 달라지는 거기 때문에 싱크가 안맞게된다.
이렇게 싱크가 깨지면 데이터 관리가 매우 힘들어진다.

  • 리액트: 올바른 방법은 데이터를 바꾸는 상위 컴포넌트의 메서드(함수)를 자식에게 props로 전달하는 것이다.

상위 컴포넌트가 가지고 있는 값을 바꾸면 자동으로 하위 컴포넌트의 값도 바뀌게된다.
그래서 부모 값을 바꿔주는게 가장 깔끔하다. 데이터의 불일치가 발생하지 않기 때문에.

4. 기존 data를 가공하여 새로운 값을 도출해낼 때는 보통 computed를 사용한다

  • data 가공하여 새로운 값을 도출해낼 때 computed 사용

5. mounted 때 실행한 이벤트 핸들러, setTimeout, setInterval 등등 beforeDestroy에서 다 실행취소

  • 메모리 누수(Leak) 발생하지 않도록 꼼꼼하게 다 체크!!!!

6. watch는 되도록 쓰지말자.

  • watch를 남용하면 프로그램이 고장날 수 있다.
  • watch는 비동기로 실행되기 때문에 뭐가 뭐를 바꾸는지 그 순서가 헷갈려진다.
  • 즉, watch를 남용하게 되면 순서 파악도 애매해지고 무한 반복될 가능성도 있고 그러면 프로그램이 망가진다.
  • 잘 생각해보면 watch를 안 쓰고도 할 수 있는 방법이 있을 것이다.
  • 즉, watch는 최후의 수단으로 사용해야된다.

  • computed, watch 모두 props나 data가 바뀔 때마다 실행되는데, computed는 하나의 값을 리턴하고 watch는 특정 동작을 수행한다.
  • watch는 웬만하면 쓰지말자.
  • 리액트, 앵귤러에서도 watch는 웬만하면 피한다.
  • 괜히 자꾸 뭔가를 서로 바꿔대가지고 프로그램 만들 때 헷갈려진다. 디버깅할 때 힘들어진다.
  • 그래서 웬만하면 자동으로 바뀌고 그런걸 좋아하지 않는다.
  • 그래서 대부분의 vue 강의하시는 분들이 watch 쓰지 말라고 할 것이다.
  • 특정 watch 가 무언갈 바꾸고 바뀐 무언가 때문에 또 watch가 실행되고.. 굉장히 이상해진다.

// main.js
import Vue from 'vue';
import LottoGenerator from './LottoGenerator';

new Vue(LottoGenerator).$mount('#root');

<!-- LottoGenerator.vue -->
<template>
    <div>
        <div>당첨 숫자</div>
        <div id="result">
            <lotto-ball v-for="ball in winBalls" :key="ball" :number="ball"></lotto-ball>
        </div>
        <div>보너스</div>
        <lotto-ball v-if="bonus"></lotto-ball>
        <button v-if="redo" type="button" @click="onClickRedo">한번 더!</button>
    </div>
</template>

<script>
import LottoBall from './LottoBall';
    
function getWinNumbers() {
    const candidate = Array(45).fill().map((v, i) => i + 1); // [1, 2, 3, 4, 5, ..., 45]
    const shuffle = [];
    while (candidate.length > 0) {
        shuffle.push(candidate.splice(Math.floor(Math.random() * candidate.length), 1)[0]);
    }
    // shuffle에 1~45가 무작위 순서로 들어가게된다.
    const bonusNumber = shuffle[shuffle.length - 1]; // shuffle 배열의 맨 마지막 요소
    const winNumbers = shuffle.slice(0, 6).sort((p, c) => p - c); // shuffle 배열의 0 ~ 6 요소를 winNumbers에 오름차순으로 넣음
    return [...winNumbers, bonusNumber];
}
    
const timeouts = [];

export default {
    name: 'LottoGenerator',
    components: {
        LottoBall, // 파스칼 케이스(맨 첫글자도 대문자) -> 이를 자동으로 뷰가 케밥케이스로 바꿔줌
    },
    data() {
        return {
            winNumbers: getWinNumbers(), // 처음 7개 숫자를 모두 뽑아서 winNumbers에 저장
            winBalls: [], // 시각적인 재미를 위해 1초마다 winNumbers에 있는 숫자를 하나씩 winBalls에 넣음
            bonus: null,
            redo: false,
        }
    },
    watch: {
        // 특정 data를 감시
        // 아래와 같은 식으로 작성할거면 배열, 객체말고 원시값(primitive value)을 감시하는걸로 작성하는게 좋을듯
        // 어 근데 deep: true라는 옵션값 있잖아?
        // 그런데 제일 좋은건 최대한 watch는 사용하지말자
        // watch가 자꾸 지속되면 문제가 발생한다. watch를 잘못쓰면 무한반복되는 문제가 발생한다.
        // watch를 남용하면 프로그램이 고장날 수 있다.
        // 그리고 watch는 비동기로 실행되기 때문에 뭐가 뭐를 바꾸는지 그 순서가 헷갈려진다.
        // 남용하게 되면 순서 파악도 애매해지고 무한 반복될 가능성도 있고 그러면 프로그램이 망가진다.
        // winBalls(value, oldValue) {
        //     console.log(value, oldValue); // oldValue는 바뀌기 이전 값, 그런데 객체나 배열같은 참조형 데이터라면 value랑 oldValue가 같은 값이 찍힐 것이다.
        //     if (value.length === 0) {
        //         this.showBalls();
        //     }
        // },
    },
    mounted() {
        this.showBalls();
    },
    beforeDestroy() {
        timeouts.forEach((t) =>  {
            clearTimeout(t);
        })
    },
    methods: {
        onClickRedo() {
            this.winNumbers = getWinNumbers();
            this.winBalls = [];
            this.bonus = null;
            this.redo = false;
            this.showBalls();
        },
        showBalls() {
            // winNumbers 7개
            // let i = 0; i < 6; i++ // 0, 1, 2, 3, 4, 5
            for (let i = 0; i < this.winNumbers.length - 1; i++) {
                timeouts[i] = setTimeout(() => {
                    this.winBalls.push(this.winNumbers[i]);
                }, (i + 1) * 1000);
            }
            timeouts[6] = setTimeout(() => {
                this.bonus = this.winNumbers[6];
                this.redo = true;
            }, 7000);
        }
    }
}
</script>

<style>
    
</style>


<!-- LottoBall.vue -->
<template>
    <div class="ball" :style="styleObject">{{ number }}</div>
</template>

<script>
export default {
    name: 'LottoBall',
    props: {
        number: Number,
    },
    computed: {
        styleObject() {
            let background;
            if (this.number < 10) {
                background = 'red';
            } else if (this.number <= 20) {
                background = 'orange';
            } else if (this.number <= 30) {
                background = 'yellow';
            } else if (this.number <= 40) {
                background = 'blue';
            } else {
                background = 'green';
            }
            return {
                background,
            }
        }
    },
    watch: {
        
    },
}
</script>

<style scoped>
    .ball {
        display: inline-block;
        border: 1px solid black;
        border-radius: 20px;
    }
</style>