Vue 문법 정리 2 / Vue 3 Composition API 종류 및 사용법

setup()

컴포넌트 인스턴스가 생성되기 전 호출되는 함수이며, Composition API의 진입점입니다.

setup 함수 사용 예시

<template>
  <p>
    {{ counter }}
  </p>
  <button @click="increment">click!</button>
</template>

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

  export default {
    setup(props, context) {
      const counter = ref(0); // 초기값 0 세팅
      const increment = () => {
        counter.value++;
      }
      return {
        counter,
        increment
      }
    },
    mounted() {
      console.log(this.count);
    }
  }
</script>

setup 함수 내에서 정의한 반응형 상태, 함수를 return 하면 template에서 사용할 수 있습니다.
mounted() 같은 Options API 함수 내에서도 this 키워드로 컴포넌트 인스턴스에 접근할 수 있습니다.


Reactivity

상태를 반응형으로 만들어 데이터 변경 시 UI가 자동 업데이트 되도록 돕는 반응형 API들입니다.

ref()

<template>
  <button v-on:click="addMessage">Click : {{ message }}</button>
</template>

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

export default {
  setup() {
    let message = ref('Hello Vue!');

    const addMessage = () => {
      message.value = message.value + '!';
    }

    return {
        message,
        addMessage
    }
  }
}
</script>

String, Number, boolean 등 단일 프리미티브 값을 반응형 객체로 생성하는 API입니다.
프리미티브 값을 레퍼런스 객체로 매핑해서 .value로 값에 접근할 수 있습니다.
Vue 3.3 이상에서 $ref()로 선언하면 .value 없이도 값에 바로 접근할 수 있습니다.

reactive()

<template>
  <button v-on:click="increment">Click : {{ state.count }}</button>
  <button v-on:click="increment">Deep Click : {{ state.deep.count }}</button>
</template>

<script>
import { reactive } from 'vue';

export default {
  setup() {
    const state = reactive({
      count: 0,
      deep: {
        count: 0,
      }
    });
    const increment = () => {
      state.count++;
      state.deep.count++;
    }
    return {
      state,
      increment,
    }
  }
}
</script>

객체, 배열 등 레퍼런스 타입을 깊은 반응형 객체로 생성하는 API입니다.
프리미티브 타입으로 만들면 반응형이 동작하지 않습니다.

<script>
import { reactive, ref } from 'vue';

export default {
  setup() {
    const count = ref(0);
    const state = reactive({
      count, // count: count 단축
    });

    // 동일 값 출력
    console.log(count.value);
    console.log(state.count);

    // undefined
    console.log(state.count.value);

    return {};
  }
}
</script>

위와 같이, ref로 선언한 데이터를 reactive 객체의 속성으로 주입하면 .value가 자동으로 언래핑됩니다.
ref로 선언한 데이터를 reactive 배열의 요소로 주입하면 .value를 붙여서 접근해야 합니다.

toRef()

<templeat>
  <div>
    <p>{{ author }}</p>
  </div>
</templeat>
<script>
import { reactive, toRef } from 'vue';

export default {
  setup() {
    const book = reactive({
      author: 'Vue Team',
      year: '2020',
      title: 'Vue 3 Guide',
      description: '책 설명',
      price: '무료',
    });

    // 반응형 유지하며 구조분해 할당
    const author = toRef(book, 'author');

    // 반응형 유지 안되는 구조분해 할당 예시
    // const author = book;

    return { author };
  }
}
</script>

reactive 객체의 단일 속성을 ref 형태로 반환해서 반응형을 유지하며 구조분해 할당하는 API입니다.
toRef 또는 toRefs를 사용하지 않고 ES6 비구조화 할당으로 추출 시 반응성을 잃게 됩니다.

toRefs()

<templeat>
  <div>
    <p>{{ author }}</p>
    <p>{{ title }}</p>
  </div>
</templeat>
<script>
import { reactive, toRefs } from 'vue';

export default {
  setup() {
    const book = reactive({
      author: 'Vue Team',
      year: '2020',
      title: 'Vue 3 Guide',
      description: '책 설명',
      price: '무료',
    });

    // 반응형 유지하며 구조분해 할당
    const { author, title } = toRefs(book);

    // 반응형 유지 안되는 구조분해 할당 예시
    // const { author, title } = book;

    return { author, title, book };
  }
}
</script>

reactive 객체의 모든 속성을 각각 ref 형태로 반환해서 반응형을 유지하며 구조분해 할당하는 API입니다.

readonly()

<script>
import { reactive, readonly } from 'vue';

const original = reactive({ count: 0 });

const copy = readonly(original);

// 원본 변경 시 복사본도 변경 (최신 상태 유지)
original.count++;

// 복사본 변경 시 콘솔 에러 및 실패
copy.count++;
</script>

반응형 객체를 읽기 전용으로 만들어서, 복사본을 통한 상태 변경을 방지하는 API입니다.

computed()

<template>
  <p>{{ reversedMessage }}</p>
</template>

<script>
import { ref, computed } from 'vue';

export default {
  setup() {
    const message = ref('Hello Vue!');

    const reversedMessage = computed(() => 
      message.value.split('').reverse().join('') // 리턴 값 (한 줄이어서 중괄호, return 생략)
    );

    return { reversedMessage };
  }
}
</script>

템플릿 문법 내 계산식이 길어질 때 computed() 함수로 변경하면 좋습니다.
계산한 결과 값을 캐시에 저장 후 반환하므로, 반복 호출 시 일반 함수보다 효율적입니다.
의존하는 반응형 데이터가 변경되면 다시 계산해서 최신 값을 유지합니다.

<template>
  <p>{{ fullName }}</p>
</template>

<script>
import { ref, computed } from 'vue';
const firstName = ref('홍');
const lastName = ref('길동');

const fullName = computed({
  get() {
    return firstName.value + lastName.value;
  },
  set(value) {
    // 이름 배열을 구조분해 할당
    [ firstName.value, lastName.value ] = value.split(' ');
  },
});

// setter가 없으면 값 변경 시 에러
fullName.value = '송 하영';
</script>

computed 함수로 반환한 value는 읽기 전용이라서 값 변경 시 에러가 납니다.
위와 같이 getter/setter를 정의하면 쓰기 가능한 computed가 됩니다.

Watch

반응형 데이터 변경 시 콜백 함수를 실행하는 API입니다.
DOM 변경, 비동기 작업, 사이드 이펙트 등을 수행할 수 있습니다.

Watch 사용 예시

const data변수명 = ref('텍스트');

watch(data변수명, (newValue, oldValue) => {
  // 데이터 변경 시 특정 작업 수행
});
const data변수명1 = ref('텍스트');
const data변수명2 = ref(숫자);

watch([ data변수명1, data변수명2 ], (newValues, oldValues) => {
  // 배열 내 데이터 변경 시 특정 작업 수행
});
const obj = reactive({
  count: 0,
  name: '송하영',
  hobby: '주식',
});

watch(obj, (newValue, oldValue) => {
  // 객체 내 모든 속성 변경 시 특정 작업 수행
}, { deep: true });
const obj = reactive({
  count: 0,
});

watch(() => obj.count, (newValue, oldValue) => {
  // getter 함수로 받은 obj.count 변경 시에만 특정 작업 수행
});
const data변수명 = ref('텍스트');

watch(data변수명, (newValue, oldValue) => {
  // 처음 렌더링 시 즉시 실행, 데이터 변경 시 특정 작업 수행
}, { immediate: true });

WatchEffect

콜백함수에서 사용된 반응형 데이터들 변경 시 실행되는 API입니다.
Watch와 달리, 렌더링 시 최초 한번 즉시 실행됩니다.

const data변수명 = ref('텍스트');

watchEffect(() => {
  // WatchEffect 내에서 사용된 반응형 데이터 변경 시 특정 작업 수행
  console.log(data변수명.value);
});

Lifecycle Hooks

Vue 컴포넌트 인스턴스 생성 후 거치는 라이프사이클 단계에 따라 호출되는 함수들입니다.

컴포넌트 라이프사이클 단계

create 컴포넌트 인스턴스 생성 (초기화)
mount DOM에 컴포넌트 마운트(부착) 후 렌더링
update 반응형 상태 변경 시 컴포넌트 업데이트(재렌더링)
unmount 컴포넌트 소멸

Lifecycle Hooks 종류

Options API Composition API 호출 시점 및 설명
beforeCreate (Setup 함수로 대체) 컴포넌트 인스턴스 생성 전 호출

아직 this, DOM 요소를 사용할 수 없습니다.
created (Setup 함수로 대체) 컴포넌트 인스턴스 생성 후 호출

created 훅은 this 및 data 등에 접근할 수 있어 반응형 상태 사용이 가능합니다.
setup 함수 내에서는 아직 this, DOM 요소를 사용할 수 없습니다.
beforeMount onBeforeMount DOM에 컴포넌트가 마운트(부착) 되기 전 호출

DOM 요소 접근 시 null이 출력됩니다.
mounted onMounted 컴포넌트가 DOM에 마운트(부착) 된 후 호출

Options API에서는 this로 app 인스턴스에 접근할 수 있습니다.
이제 DOM 요소 접근이 가능합니다.
beforeUpdate onBeforeUpdate 반응형 상태 변경 시 DOM 업데이트(재렌더링) 전 호출
updated onUpdated DOM 업데이트(재렌더링) 완료 후 호출
beforeUnmount onBeforeUnmount 컴포넌트가 제거되기 전 호출
아직 DOM 요소 접근이 가능합니다.
unmounted onUnmounted 컴포넌트가 제거된 후 호출
DOM 요소 접근 시 null이 출력됩니다.
errorCaptured onErrorCaptured 자식 컴포넌트에서 에러 발생 시 호출
renderTracked onRenderTracked 반응형 데이터 의존성 추적 시 호출 (디버깅 용도)
renderTriggered onRenderTriggered 반응형 데이터 변경으로 컴포넌트 렌더링 트리거 시 호출 (디버깅 용도)
activated onActivated keep-alive 컴포넌트 활성화 시 호출
deactivated onDeactivated keep-alive 컴포넌트 비활성화 시 호출
serverPrefetch onServerPrefetch SSR(서버 사이드 렌더링) 시 호출

데이터 사전 로딩용입니다.

Vue 컴포넌트 인스턴스 생명 주기에서 실행되는 함수들입니다.

Options API Lifecycle Hooks 사용 예시

<script>
export default {
  data() {
    return {
      data변수명: '텍스트',
    };
  },
  setup() {
    console.log('setup 호출 : this = undefined');
    return {};
  },
  beforeCreate() {
    console.log('beforeCreate 호출 : ' + this.data변수명 + ' = undefined');
  },
  created() {
    console.log('created 호출 : ' + this.data변수명 + ' = 접근 가능');
  },
}
</script>

setup() → beforeCreate() → created() 순으로 라이프사이클 훅이 실행됩니다.

Composition API Lifecycle Hooks 사용 예시

<script>
export default {
  setup() {
    console.log('setup 호출');

    onBeforeMount(() => {
      console.log('onBeforeMount 호출');
    });

    onMounted(() => {
      console.log('모든 자식 컴포넌트 onMounted 후');
      console.log('onMounted 호출');
    });

    onUnmounted(() => {
      console.log('onUnmounted 호출');
    });

    return {};
  },
}
</script>

setup() → onBeforeMount() → onMounted() 순으로 라이프사이클 훅이 실행됩니다.


Template Refs

template DOM 요소에 직접 접근할 때 사용합니다.
ref()와 함께 사용되며, setup() 함수 안에서 정의합니다.

자식 컴포넌트 DOM 요소를 ref로 참조하는 것은 컴포넌트 간 의존성을 높여서 권장되지 않습니다.
자식 컴포넌트에서 부모 컴포넌트에 접근할 수 있는 $parent 내장 객체도 권장되지 않습니다.
부모/자식 컴포넌트 상호작용은 props, emit으로 구현하는 것이 좋습니다.

Template Refs 사용 예시

<template>
  // DOM 요소를 ref 변수에 추가
  <input ref="inputRef" />

  <button @click="focusInput">Focus</button>

  // input DOM 출력
  <p>{{ inputRef }}</p>

  // input DOM 값 출력
  // 마운트 전까지 inputRef는 null이기 때문에 v-if 없으면 에러
  <p v-if="inputRef">{{ inputRef.value }} 또는 {{ $refs.inputRef.value }}</p>
</template>

<script>
import { ref, onMounted } from 'vue';

export default {
  setup() {
    // 템플릿 ref 변수 선언
    const inputRef = ref(null);

    const focusInput = () => {
      // DOM 요소에 직접 접근
      inputRef.value.focus();
    };

    onMounted(() => {
      // 컴포넌트 마운트 후, DOM 요소 및 값 출력
      console.log(inputRef.value);
      console.log(inputRef.value.value);
    });

    return { inputRef, focusInput };
  },
};
</script>

setup 함수 내에서 input 요소에 접근하는 Template ref 사용 예시입니다.
DOM ref 속성 값과 return 반응형 상태 객체 키를 동일하게 선언해야 합니다.

Template Refs 배열 바인딩 예시

<template>
  <ul>
    // v-for문으로 출력하며 DOM 요소를 ref 배열에 추가
    <li v-for="fruit in fruits" :key="fruit" ref="itemRefs">{{ fruit }}</li>
  </ul>
</template>

<script>
import { ref, onMounted } from 'vue';

export default {
  setup() {
    onMounted(() => {
      // ref 배열 내 DOM 요소들 출력
      itemRefs.value.forEach(item => console.log(item));

      // ref 배열 내 DOM 요소들 값 출력
      itemRefs.value.forEach(item => console.log(item.textContent));
    });
    
    const fruits = ref(['사과', '딸기', '포도']);
    const itemRefs = ref([]);

    return { fruits, itemRefs };
  },
};
</script>

위와 같이, v-for문과 ref 속성을 이용해서 DOM 요소들을 ref 배열에 추가할 수도 있습니다.

Template Refs 함수 바인딩 예시

<template>
  <ul>
    // 현재 DOM 엘리먼트를 받아서 내부 값을 ref 배열에 추가
    <li v-for="(fruit, i) in fruits" :key="fruit" :ref="(el) => el && (itemRefs.value[i] = el.textContent)">{{ fruit }}</li>
  </ul>
</template>

<script>
import { ref, onMounted } from 'vue';

export default {
  setup() {
    onMounted(() => {
      // ref 배열 내 값 출력
      itemRefs.value.forEach(item => console.log(item));
    });
    
    const fruits = ref(['사과', '딸기', '포도']);
    const itemRefs = ref([]);

    return { fruits, itemRefs };
  },
};
</script>

위와 같이, v-for문으로 출력한 요소 내부 값을 ref 배열에 추가할 수도 있습니다.