Vue.js 핵심 개념 정리: 입문자를 위한 실전 가이드

1. Vue 시작하기

<script src="./js/vue.js"></script>

<!-- 1. Vue 인스턴스 생성 시 반드시 옵션 객체를 전달해야 함 -->
<!-- 2. 컨테이너 내부 코드는 HTML 규칙을 따르며, Vue 전용 문법이 추가됨 -->
<!-- 3. 컨테이너 코드 = Vue 템플릿 -->
<!-- 4. Vue 인스턴스와 컨테이너는 1:1 관계 -->
<!-- 5. 실제 프로젝트에서는 단일 인스턴스 + 컴포넌트 구조 -->
<!-- 6. {{ xxx }} 는 JS 표현식이며, data 속성에 접근 가능 -->
<!-- 7. data 변경 시 해당 데이터를 사용하는 템플릿이 자동 갱신됨 -->

<div id="root">
    <h1>hello, {{name}}</h1>
</div>

<script>
    Vue.config.productionTip = false;
    new Vue({
        el: '#root',
        data: {
            name: '홍길동'
        }
    });
</script>

2. 템플릿 문법

<!-- 
    1. 보간법(Interpolation): 태그 본문 내용 해석
       - {{ xxx }} 형식, data 속성 접근 가능
    2. 디렉티브(Directive): 태그 속성, 이벤트 바인딩 등
       - v-bind:href='xxx' 또는 :href='xxx' 로 축약
-->
<div id="root">
    <h1>보간법 예시: {{name}}</h1>
    <a v-bind:href="site.url">{{site.name}} 방문하기 1</a>
    <a :href="site.url">{{site.name}} 방문하기 2</a>
</div>

<script>
    new Vue({
        el: '#root',
        data: {
            name: '홍길동',
            site: {
                name: 'Example',
                url: 'https://example.com'
            }
        }
    });
</script>

3. 데이터 바인딩

<!-- 
    1. 단방향 바인딩 (v-bind): data → 페이지
    2. 양방향 바인딩 (v-model): data ↔ 페이지
       - 주로 form 요소(input, select 등)에 사용
       - v-model:value 는 v-model 로 축약 가능 (value가 기본값)
-->
<div id="root">
    단방향: <input type="text" v-bind:value="username">
    양방향: <input type="text" v-model="username">
</div>
<script>
    new Vue({
        el: '#root',
        data: { username: '기본값' }
    });
</script>

4. el과 data의 두 가지 선언 방식

<!-- 
    el 선언 방식:
    1. new Vue 시 el 속성 직접 지정
    2. 인스턴스 생성 후 $mount() 호출

    data 선언 방식:
    1. 객체 형태
    2. 함수 형태 (컴포넌트에서 필수, Vue 인스턴스에서도 권장)

    주의: Vue가 관리하는 함수에서 화살표 함수 사용 시 this가 Vue 인스턴스를 가리키지 않음
-->
<div id="root">
    <h1>{{message}}</h1>
</div>
<script>
    // el 방식 1
    const app = new Vue({
        el: '#root',
        data: { message: '안녕하세요' }
    });

    // el 방식 2
    // app.$mount('#root');

    // data 방식 1 (객체) - 인스턴스에서만 가능
    // data: { message: '안녕하세요' }

    // data 방식 2 (함수) - 권장, 컴포넌트에서 필수
    // data: function() {
    //     console.log(this); // Vue 인스턴스
    //     return { message: '안녕하세요' };
    // }
</script>

5. MVVM 모델

/*
    M: Model - data 내 데이터
    V: View - 템플릿 코드
    VM: ViewModel - Vue 인스턴스

    특징:
    1. data의 모든 속성은 vm에 자동 등록
    2. vm의 속성과 Vue 프로토타입 속성은 템플릿에서 직접 사용 가능
*/

6. Object.defineProperty() 이해

<script>
    let ageValue = 18;
    let person = { name: '김철수', gender: '남성' };

    Object.defineProperty(person, 'age', {
        get() {
            console.log('age 속성 읽기 요청');
            return ageValue;
        },
        set(newValue) {
            console.log('age 속성 변경 요청:', newValue);
            ageValue = newValue;
        }
    });

    console.log(person.age); // getter 호출
    person.age = 25;         // setter 호출
</script>

7. 데이터 프록시 기초

<script>
    let source = { x: 100 };
    let proxy = { y: 200 };

    Object.defineProperty(proxy, 'x', {
        get() { return source.x; },
        set(value) { source.x = value; }
    });
</script>

8. Vue의 데이터 프록시

/*
    Vue는 vm 객체를 통해 data 속성의 읽기/쓰기를 프록시함
    장점: data에 직접 접근하지 않고 vm을 통해 편리하게 조작 가능
    원리: Object.defineProperty()로 data의 모든 속성을 vm에 추가하고,
          각 속성에 getter/setter를 설정하여 data 객체를 간접 제어
*/

9. 이벤트 처리

9.1 기본 사용법

<div id="root">
    <h2>{{siteName}}에 오신 것을 환영합니다</h2>
    <button v-on:click="showInfo">정보 보기</button>
    <button @click="showDetail(1, $event)">상세 보기</button>
</div>
<script>
    new Vue({
        el: '#root',
        data() { return { siteName: 'MySite' }; },
        methods: {
            showInfo(event) {
                alert('안녕하세요!');
            },
            showDetail(id, event) {
                console.log('ID:', id, 'Event:', event);
                alert('상세 정보입니다.');
            }
        }
    });
</script>

9.2 이벤트 수식어

<!-- 
    .prevent  : 기본 동작 방지
    .stop     : 이벤트 버블링 중단
    .once     : 한 번만 실행
    .capture  : 캡처 모드 사용
    .self     : event.target이 자기 자신일 때만 실행
    .passive  : 기본 동작을 즉시 실행, 콜백 완료 대기 없음
    ※ 수식어는 체이닝 가능: @click.prevent.stop="handler"
-->
<div id="root">
    <h2>{{siteName}}에 오신 것을 환영합니다</h2>
    <a href="https://example.com" @click.prevent="showInfo">링크 이동 방지</a>
    <div class="outer" @click="outerClick">
        <button @click.stop="innerClick">버블링 방지</button>
    </div>
    <button @click.once="showInfo">한 번만 실행</button>
</div>

9.3 키보드 이벤트

<!-- 
    주요 키 별칭: enter, delete, esc, space, tab(keydown 전용),
                 up, down, left, right
    시스템 키: ctrl, alt, shift, meta (keyup 시 특수 동작)
    사용자 정의 키: Vue.config.keyCodes.키명 = 키코드
-->
<div id="root">
    <h2>{{siteName}}에 오신 것을 환영합니다</h2>
    <input type="text" @keyup.enter="handleEnter" placeholder="Enter 키 입력">
</div>
<script>
    new Vue({
        el: '#root',
        data() { return { siteName: 'MySite' }; },
        methods: {
            handleEnter(e) {
                console.log('입력값:', e.target.value);
            }
        }
    });
</script>

10. 계산된 속성 (Computed)

<!-- 
    계산된 속성: 기존 데이터로 새 속성을 생성
    원리: Object.defineProperty()의 getter/setter 활용
    get 실행 시점:
      1) 최초 접근 시
      2) 의존 데이터가 변경될 때
    장점: 메서드 대비 캐싱으로 효율적
    ※ 수정이 필요하면 setter 구현 필요
-->
<div id="root">
    이름: <input type="text" v-model="firstName"><br>
    성: <input type="text" v-model="lastName"><br>
    전체 이름: <span>{{fullName}}</span>
</div>
<script>
    const vm = new Vue({
        el: '#root',
        data: {
            firstName: '길동',
            lastName: '홍'
        },
        computed: {
            fullName: {
                get() {
                    return this.firstName + ' ' + this.lastName;
                },
                set(value) {
                    const parts = value.split(' ');
                    this.firstName = parts[0];
                    this.lastName = parts[1] || '';
                }
            }
        }
    });
</script>

10.1 계산된 속성 축약형

// 읽기 전용일 때 getter만 정의
computed: {
    fullName() {
        return this.firstName + ' ' + this.lastName;
    }
}

11. 감시 속성 (Watch)

<!-- 
    감시 속성: 데이터 변경을 감지하고 콜백 실행
    감시 대상은 반드시 data에 존재해야 함
    선언 방식:
      1) new Vue 시 watch 옵션 사용
      2) vm.$watch() 메서드 사용
-->
<div id="root">
    <h2>오늘 날씨는 {{weatherInfo}}</h2>
    <button @click="toggleWeather">날씨 전환</button>
</div>
<script>
    const vm = new Vue({
        el: '#root',
        data: { isHot: true },
        computed: {
            weatherInfo() {
                return this.isHot ? '덥습니다' : '시원합니다';
            }
        },
        methods: {
            toggleWeather() {
                this.isHot = !this.isHot;
            }
        }
        // watch 옵션 방식
        // watch: {
        //     isHot: {
        //         immediate: true,
        //         handler(newVal, oldVal) {
        //             console.log('isHot 변경됨:', newVal, oldVal);
        //         }
        //     }
        // }
    });

    // $watch 메서드 방식
    vm.$watch('isHot', {
        immediate: true,
        handler(newVal, oldVal) {
            console.log('isHot 변경됨:', newVal, oldVal);
        }
    });
</script>

11.1 깊은 감시

<!-- 
    기본적으로 watch는 객체의 내부 변경을 감지하지 않음
    deep: true 옵션으로 객체의 모든 중첩 수준 감시 가능
    Vue 자체는 내부 변경을 감지하지만, watch는 기본값이 얕은 감시
-->
<div id="root">
    <h2>오늘 날씨는 {{weatherInfo}}</h2>
    <button @click="toggleWeather">날씨 전환</button>
    <h3>a 값: {{numbers.a}}</h3>
    <button @click="numbers.a++">a 증가</button>
</div>
<script>
    new Vue({
        el: '#root',
        data: {
            isHot: true,
            numbers: { a: 1, b: 2 }
        },
        watch: {
            isHot: {
                handler(newVal, oldVal) {
                    console.log('isHot 변경:', newVal, oldVal);
                }
            },
            numbers: {
                deep: true,
                handler() {
                    console.log('numbers 객체 변경됨');
                }
            }
        }
    });
</script>

11.2 감시 속성 축약형

// deep, immediate가 필요 없을 때
watch: {
    isHot(newVal, oldVal) {
        console.log('isHot 변경:', newVal, oldVal);
    }
}

// $watch 축약형
vm.$watch('isHot', function(newVal, oldVal) {
    console.log('isHot 변경:', newVal, oldVal);
});

12. Watch와 Computed 비교

/*
    - computed는 watch로 구현 가능하지만, watch는 computed로 불가능한 작업이 있음
    - watch는 비동기 작업에 적합
    
    원칙:
    1. Vue가 관리하는 함수는 일반 함수로 작성 (this가 Vue 인스턴스)
    2. Vue가 관리하지 않는 함수(타이머, Ajax, Promise 등)는 화살표 함수로 작성
       (this가 Vue 인스턴스를 유지하도록)
*/

13. 스타일 바인딩

<!-- 
    class 바인딩:
      - 문자열: 클래스명을 동적으로 결정
      - 객체: 여러 클래스를 조건부로 적용
      - 배열: 여러 개의 동적 클래스
    
    style 바인딩:
      - 객체: { fontSize: '40px' } 형태
      - 배열: 여러 스타일 객체를 결합
-->
<div id="root">
    <div class="basic" :class="currentClass" @click="changeClass">{{title}}</div>
    <div class="basic" :class="classArray">배열 스타일</div>
    <div class="basic" :class="classObject">객체 스타일</div>
    <div class="basic" :style="styleObject">인라인 스타일</div>
</div>
<script>
    new Vue({
        el: '#root',
        data: {
            title: '테스트',
            currentClass: 'normal',
            classArray: ['happy', 'sad', 'normal'],
            classObject: { active: false, highlight: true },
            styleObject: { fontSize: '40px', color: 'blue' }
        },
        methods: {
            changeClass() {
                const options = ['happy', 'sad', 'normal'];
                this.currentClass = options[Math.floor(Math.random() * 3)];
            }
        }
    });
</script>

14. 조건부 렌더링

<!-- 
    v-if: 조건부로 DOM 요소를 생성/제거 (전환 빈도 낮을 때 사용)
    v-show: CSS display로 숨김/표시 (전환 빈도 높을 때 사용)
    v-if는 v-else-if, v-else와 함께 사용 가능 (구조 유지 필수)
    template 태그로 여러 요소를 그룹화 가능 (v-if만 지원, v-show는 미지원)
-->
<div id="root">
    <h2>현재 n 값: {{n}}</h2>
    <button @click="n++">n 증가</button>

    <!-- v-show 예제 -->
    <h2 v-show="n % 2 === 0">n은 짝수입니다</h2>

    <!-- v-if 체인 예제 -->
    <div v-if="n === 1">Angular</div>
    <div v-else-if="n === 2">React</div>
    <div v-else-if="n === 3">Vue</div>
    <div v-else>기타</div>

    <!-- template과 v-if 조합 -->
    <template v-if="n === 1">
        <p>첫 번째 항목</p>
        <p>두 번째 항목</p>
    </template>
</div>

15. 리스트 렌더링

<!-- 
    v-for: 배열, 객체, 문자열, 숫자 범위를 반복 렌더링
    문법: v-for="(item, index) in items" :key="uniqueId"
    key 속성은 Vue의 DOM 재사용 알고리즘에 중요
-->
<div id="root">
    <h2>사용자 목록</h2>
    <ul>
        <li v-for="user in userList" :key="user.id">
            {{user.name}} - {{user.age}}
        </li>
    </ul>

    <h2>차량 정보 (객체 반복)</h2>
    <ul>
        <li v-for="(val, key) in car" :key="key">
            {{key}}: {{val}}
        </li>
    </ul>
</div>
<script>
    new Vue({
        el: '#root',
        data: {
            userList: [
                { id: '001', name: '김철수', age: 28 },
                { id: '002', name: '이영희', age: 32 },
                { id: '003', name: '박민수', age: 25 }
            ],
            car: {
                brand: '현대',
                model: '소나타',
                year: '2023'
            }
        }
    });
</script>

15.1 key 속성의 중요성

/*
    key는 가상 DOM의 식별자 역할
    변경 감지 시 신규/기존 가상 DOM 비교에 사용

    비교 규칙:
    - 동일 key 발견 시: 내용이 같으면 기존 DOM 재사용, 다르면 새 DOM 생성
    - 새 key만 발견 시: 새 DOM 생성 후 추가

    index를 key로 사용할 때 문제:
    - 역순 추가/삭제 등 순서 변경 시 불필요한 DOM 갱신 발생
    - 입력 필드가 포함된 경우 잘못된 DOM 갱신 가능

    권장 사항:
    - 고유 식별자(id, 전화번호 등)를 key로 사용
    - 단순 표시용 리스트이고 순서 변경이 없으면 index도 허용
*/

16. 리스트 필터링

<div id="root">
    <h2>사용자 목록</h2>
    <input type="text" v-model="keyword" placeholder="이름 검색">
    <ul>
        <li v-for="user in filteredUsers" :key="user.id">
            {{user.name}} - {{user.age}} - {{user.gender}}
        </li>
    </ul>
</div>
<script>
    new Vue({
        el: '#root',
        data: {
            keyword: '',
            userList: [
                { id: '001', name: '김철수', age: 28, gender: '남' },
                { id: '002', name: '이영희', age: 32, gender: '여' },
                { id: '003', name: '박민수', age: 25, gender: '남' }
            ]
        },
        computed: {
            filteredUsers() {
                return this.userList.filter(user =>
                    user.name.includes(this.keyword)
                );
            }
        }
    });
</script>

17. Vue의 데이터 변경 감지 원리

<!-- 
    1. Vue는 data의 모든 계층을 감시
    2. 객체 감시: setter를 통해 이루어지며, new Vue 시 전달된 데이터만 감시
       - 이후 추가된 속성은 반응형이 아니므로 Vue.set() 또는 vm.$set() 사용
    3. 배열 감시: 배열 메서드를 래핑(push, pop, shift, unshift, splice, sort, reverse)
       - 인덱스 직접 변경은 감지되지 않음 → Vue.set() 또는 splice() 사용
    4. Vue.set()과 vm.$set()은 vm이나 data의 루트 속성에는 사용 불가
-->
<div id="root">
    <h1>학생 정보</h1>
    <button @click="student.age++">나이 증가</button>
    <button @click="addGender">성별 추가</button>
    <button @click="addHobby">취미 추가</button>
    <button @click="updateFirstHobby">첫 번째 취미 변경</button>

    <h3>{{student.name}} - {{student.age}} - {{student.gender}}</h3>
    <h3>취미</h3>
    <ul>
        <li v-for="(hobby, idx) in student.hobbies" :key="idx">{{hobby}}</li>
    </ul>
</div>
<script>
    new Vue({
        el: '#root',
        data: {
            student: {
                name: 'Tom',
                age: 20,
                hobbies: ['독서', '음악', '운동']
            }
        },
        methods: {
            addGender() {
                this.$set(this.student, 'gender', '남성');
            },
            addHobby() {
                this.student.hobbies.push('코딩');
            },
            updateFirstHobby() {
                Vue.set(this.student.hobbies, 0, '여행');
            }
        }
    });
</script>

18. 폼 데이터 수집

<!-- 
    input[type="text"]: v-model은 value 수집
    input[type="radio"]: v-model은 value 수집, value 속성 필수
    input[type="checkbox"]:
      - value 미설정: checked (boolean)
      - value 설정 & v-model 초기값이 배열: value 배열
      - value 설정 & v-model 초기값이 비배열: checked (boolean)
    
    v-model 수식어:
      .lazy: change 이벤트 후 동기화
      .number: 숫자로 변환
      .trim: 앞뒤 공백 제거
-->
<div id="root">
    <form @submit.prevent="submitForm">
        계정: <input type="text" v-model.trim="form.account"><br>
        비밀번호: <input type="password" v-model="form.password"><br>
        나이: <input type="number" v-model.number="form.age"><br>
        성별:
        남 <input type="radio" value="male" v-model="form.gender">
        여 <input type="radio" value="female" v-model="form.gender"><br>
        취미:
        독서 <input type="checkbox" value="reading" v-model="form.hobbies">
        게임 <input type="checkbox" value="gaming" v-model="form.hobbies">
        운동 <input type="checkbox" value="sports" v-model="form.hobbies"><br>
        도시:
        <select v-model="form.city">
            <option value="">선택하세요</option>
            <option value="seoul">서울</option>
            <option value="busan">부산</option>
        </select><br>
        기타: <textarea v-model.lazy="form.notes"></textarea><br>
        <input type="checkbox" v-model="form.agree"> 동의합니다
        <button type="submit">제출</button>
    </form>
</div>
<script>
    new Vue({
        el: '#root',
        data: {
            form: {
                account: '',
                password: '',
                age: null,
                gender: '',
                hobbies: [],
                city: '',
                notes: '',
                agree: false
            }
        },
        methods: {
            submitForm() {
                console.log(JSON.stringify(this.form));
                alert('제출 완료!');
            }
        }
    });
</script>

19. 내장 디렉티브

<!-- 
    v-text: 텍스트 내용 렌더링 (기존 내용 대체)
    v-html: HTML 구조 렌더링 (XSS 위험 주의!)
    v-cloak: Vue 인스턴스 생성 전까지 숨김 (네트워크 지연 시 {{}} 노출 방지)
    v-once: 최초 렌더링 후 캐싱 (데이터 변경 무시)
    v-pre: 컴파일 생략 (순수 HTML로 처리)
-->
<div id="root">
    <div v-text="message"></div>
    <div v-html="htmlContent"></div>
    <div v-once>초기값: {{counter}}</div>
    <div v-pre>{{ 이 부분은 컴파일되지 않음 }}</div>
</div>
<script>
    new Vue({
        el: '#root',
        data: {
            message: '텍스트 예시',
            htmlContent: '<strong>굵은 텍스트</strong>',
            counter: 100
        }
    });
</script>

20. 사용자 정의 디렉티브

<!-- 
    선언 방식:
      - 지역: directives 옵션 사용
      - 전역: Vue.directive() 사용
    
    콜백:
      - bind: 디렉티브와 요소가 바인딩될 때
      - inserted: 요소가 DOM에 삽입될 때
      - update: 템플릿이 재컴파일될 때
    
    참고: 디렉티브명은 kebab-case 사용, 사용 시 v- 접두사 추가
-->
<div id="root">
    <h2>현재 n 값: <span v-text="n"></span></h2>
    <h2>10배 값: <span v-big="n"></span></h2>
    <button @click="n++">증가</button>
    <hr>
    <input type="text" v-focus:value="n">
</div>
<script>
    // 전역 디렉티브
    Vue.directive('focus', {
        bind(el, binding) {
            el.value = binding.value;
        },
        inserted(el) {
            el.focus();
        },
        update(el, binding) {
            el.value = binding.value;
        }
    });

    new Vue({
        el: '#root',
        data: { n: 1 },
        directives: {
            // 함수형 축약
            big(el, binding) {
                el.innerText = binding.value * 10;
            },
            // 객체형
            focus: {
                bind(el, binding) {
                    el.value = binding.value;
                },
                inserted(el) {
                    el.focus();
                },
                update(el, binding) {
                    el.value = binding.value;
                }
            }
        }
    });
</script>

21. 생명 주기 (Lifecycle)

<!-- 
    생명 주기 훅: Vue가 특정 시점에 호출하는 함수
    훅 이름은 고정, 내용은 개발자 정의
    this는 Vue 인스턴스 또는 컴포넌트 인스턴스
-->
<div id="root">
    <h2 :style="{ opacity: opacityLevel }">Vue 학습 중...</h2>
</div>
<script>
    new Vue({
        el: '#root',
        data: { opacityLevel: 1 },
        mounted() {
            this.timer = setInterval(() => {
                this.opacityLevel -= 0.01;
                if (this.opacityLevel <= 0) this.opacityLevel = 1;
            }, 16);
        },
        beforeDestroy() {
            clearInterval(this.timer);
        }
    });
</script>

21.1 주요 생명 주기 훅 활용

<!-- 
    mounted: Ajax 요청, 타이머 시작, 이벤트 바인딩 등 초기화 작업
    beforeDestroy: 타이머 제거, 이벤트 해제 등 정리 작업
    
    vm.$destroy() 호출 시:
    - Vue DevTools에서 정보 사라짐
    - 사용자 정의 이벤트는 사라지지만, 네이티브 DOM 이벤트는 유지
    - destroy 후 데이터 변경해도 템플릿 갱신되지 않음
-->
<div id="root">
    <h2 :style="{ opacity: opacityLevel }">Vue 학습 중...</h2>
    <button @click="stopAnimation">애니메이션 중지</button>
</div>
<script>
    new Vue({
        el: '#root',
        data: { opacityLevel: 1 },
        methods: {
            stopAnimation() {
                this.$destroy();
            }
        },
        mounted() {
            this.timer = setInterval(() => {
                this.opacityLevel -= 0.01;
                if (this.opacityLevel <= 0) this.opacityLevel = 1;
            }, 16);
        },
        beforeDestroy() {
            clearInterval(this.timer);
        }
    });
</script>

22. 컴포넌트 정의

// 컴포넌트: 애플리케이션의 기능 단위를 코드와 리소스로 묶은 집합

23. 비단일 파일 컴포넌트

<!-- 
    Vue.extend(options)로 컴포넌트 생성
    new Vue(options)와 유사하나 차이점:
      - el 속성 불필요 (vm이 관리)
      - data는 반드시 함수 (재사용 시 데이터 격리)
    
    등록 방식:
      - 지역: components 옵션
      - 전역: Vue.component('name', component)
    
    사용: <component-name></component-name>
-->
<div id="root">
    <my-header></my-header>
    <user-profile></user-profile>
</div>

<div id="root2">
    <my-header></my-header>
</div>

<script>
    // 컴포넌트 생성
    const headerComponent = Vue.extend({
        template: `<div><h2>헤더 컴포넌트: {{title}}</h2></div>`,
        data() { return { title: '메인 헤더' }; }
    });

    const profileComponent = Vue.extend({
        template: `<div><h3>사용자: {{name}}, 나이: {{age}}</h3></div>`,
        data() { return { name: '김철수', age: 30 }; }
    });

    // 전역 등록
    Vue.component('my-header', headerComponent);

    new Vue({
        el: '#root',
        components: { 'user-profile': profileComponent }
    });

    new Vue({ el: '#root2' });
</script>

23.1 주의사항

/*
    컴포넌트명:
    - 단일 단어: school / School (권장: PascalCase)
    - 여러 단어: my-school / MySchool (kebab-case 권장)
    - HTML 예약어 회피 (h2, button 등)

    컴포넌트 태그:
    - <school></school> (닫는 태그 필수, 단일 태그는 비권장)
    
    축약형: const comp = Vue.extend(options) → const comp = options
*/

23.2 VueComponent 생성자

/*
    1. Vue.extend()는 VueComponent 생성자 반환
    2. <school></school> 작성 시 Vue가 내부적으로 new VueComponent() 실행
    3. Vue.extend() 호출마다 새로운 VueComponent 생성자 생성
    4. this 바인딩:
       - 컴포넌트 내 data, methods, watch, computed → VueComponent 인스턴스
       - new Vue() 내 → Vue 인스턴스
    
    VueComponent 인스턴스 = vc (컴포넌트 인스턴스)
    Vue 인스턴스 = vm
*/

23.3 중요 상속 관계

/*
    VueComponent.prototype.__proto__ === Vue.prototype
    → vc가 Vue.prototype의 속성/메서드에 접근 가능
*/

24. Render 함수

/*
    Vue 버전 차이:
    - vue.js: 코어 + 템플릿 컴파일러 포함 (완전판)
    - vue.runtime.js: 코어만 포함 (런타임판)
    
    런타임 버전은 템플릿 컴파일러가 없으므로, template 대신 render 함수 사용
    
    render(createElement) {
        return createElement('h1', '안녕하세요!');
    }
    
    축약형: render: h => h('h1', '안녕하세요!')
*/

25. vue.config.js 설정

/*
    vue inspect > output.js: 기본 설정 확인
    vue.config.js: 프로젝트 설정 커스터마이징
    상세: https://cli.vuejs.org/zh
*/

26. ref 속성

/*
    ref: 요소/컴포넌트 참조 (id의 Vue 버전)
    - HTML 요소: 실제 DOM 요소 반환
    - 컴포넌트: 컴포넌트 인스턴스(vc) 반환
    
    접근: this.$refs.xxx
    동적 ref: :ref="variable" → this.$refs[variable]
*/
<button ref="myBtn" @click="focusInput">포커스</button>
<input ref="myInput">

methods: {
    focusInput() {
        this.$refs.myInput.focus();
    }
}

27. props 옵션

<!-- 
    props: 부모 컴포넌트가 자식에게 데이터 전달
    전달: <Child :name="value" :age="number"></Child>
    
    선언 방식:
    1. 배열: props: ['name', 'age']
    2. 객체(타입 제한): props: { name: String, age: Number }
    3. 객체(상세 설정):
       props: {
           name: { type: String, required: true },
           age: { type: Number, default: 20 },
           email: { type: String, default: '' }
       }
    
    중요: props는 읽기 전용
    수정이 필요한 경우 data에 복사 후 변경
-->
<Student name="이영희" sex="여성" :age="25"></Student>

// 컴포넌트 내 props 선언
props: {
    name: {
        type: String,
        required: true
    },
    age: {
        type: Number,
        default: 20
    },
    sex: {
        type: String,
        default: '남성'
    }
}

28. mixin (혼합)

/*
    mixin: 여러 컴포넌트의 공통 옵션을 추출하여 재사용
    
    정의:
    {
        data() { ... },
        methods: { ... },
        created() { ... }
    }
    
    사용:
    - 전역: Vue.mixin(mixinObject)
    - 지역: mixins: [mixinObject]
*/

// mixin.js
export const commonMixin = {
    methods: {
        greet() {
            console.log('안녕하세요, ' + this.name + '님!');
        }
    },
    created() {
        console.log('컴포넌트 생성됨!');
    }
};

// 컴포넌트에서 사용
import { commonMixin } from './mixins';
export default {
    mixins: [commonMixin],
    data() {
        return { name: '홍길동' };
    }
};

29. 플러그인

/*
    플러그인: Vue에 기능을 추가하는 도구
    install 메서드를 가진 객체
    
    정의:
    const myPlugin = {
        install(Vue, options) {
            // 전역 필터
            Vue.filter('capitalize', value => value.toUpperCase());
            
            // 전역 디렉티브
            Vue.directive('highlight', { bind(el) { el.style.color = 'red'; } });
            
            // 전역 mixin
            Vue.mixin({ created() { console.log('플러그인 mixin'); } });
            
            // 인스턴스 메서드
            Vue.prototype.$myMethod = function() { console.log('커스텀 메서드'); };
        }
    };
    
    사용: Vue.use(myPlugin);
*/

태그: Vue.js JavaScript Computed Watch MVVM

6월 24일 00:12에 게시됨