출시·고도화 중
Vue.js · Nuxt 안내서 · 3/6
이 장에서는 Vue 코드를 읽고 쓰는 데 필요한 핵심 개념을 다룹니다. 단일 파일 컴포넌트, 반응성 API, 템플릿 디렉티브, props와 emits, 그리고 Nuxt의 파일 기반 라우팅과 자동 가져오기입니다.
.vue 파일은 <script setup>, <template>, <style> 세 블록으로 이루어집니다. <script setup>은 Composition API를 간결하게 쓰는 문법으로, 그 안에서 선언한 변수·함수·가져온 컴포넌트는 템플릿에서 곧바로 쓸 수 있습니다. scoped를 붙인 스타일은 이 컴포넌트에만 적용됩니다.
ref는 어떤 값이든 반응형으로 감싸며, 스크립트에서는 .value로 읽고 씁니다. 템플릿에서는 .value 없이 씁니다. reactive는 객체 자체를 반응형으로 만듭니다. computed는 다른 상태에서 계산된 값을 캐시하고, watch는 값이 바뀔 때 부수 효과를 실행합니다.
<script setup lang="ts">
import { ref, reactive, computed, watch } from "vue";
const query = ref("");
const filter = reactive({ onlyDone: false });
const todos = ref([
{ id: 1, text: "Vue 배우기", done: true },
{ id: 2, text: "Nuxt 써 보기", done: false },
]);
const visible = computed(() =>
todos.value
.filter((t) => !filter.onlyDone || t.done)
.filter((t) => t.text.includes(query.value)),
);
watch(query, (next, prev) => {
console.log(`검색어: ${prev} → ${next}`);
});
</script>
<template>
<input v-model="query" placeholder="검색" />
<label><input type="checkbox" v-model="filter.onlyDone" /> 완료만</label>
<ul>
<li v-for="todo in visible" :key="todo.id">{{ todo.text }}</li>
</ul>
<p v-if="visible.length === 0">결과가 없습니다.</p>
</template>computed는 순수한 계산에만 쓰고, API 호출이나 로그처럼 바깥에 영향을 주는 일은 watch나 watchEffect에 맡깁니다.
| 디렉티브 | 줄임 | 하는 일 |
|---|---|---|
v-bind:href | :href | 속성에 값을 연결 |
v-on:click | @click | 이벤트 처리 |
v-model | 입력과 상태를 양방향 연결 | |
v-if / v-else | 조건에 따라 렌더링 | |
v-show | CSS로 보이기·숨기기만 전환 | |
v-for | 목록 반복, :key 필수 |
부모는 props로 데이터를 내려 주고, 자식은 이벤트(emits)로 변경을 알립니다. TypeScript에서는 defineProps와 defineEmits에 타입을 바로 적습니다.
<!-- components/TodoItem.vue -->
<script setup lang="ts">
const props = defineProps<{ text: string; done?: boolean }>();
const emit = defineEmits<{ toggle: []; remove: [reason: string] }>();
</script>
<template>
<li :class="{ done: props.done }">
<input type="checkbox" :checked="props.done" @change="emit('toggle')" />
{{ props.text }}
<button @click="emit('remove', 'user')">삭제</button>
</li>
</template>부모에서는 <TodoItem :text="todo.text" :done="todo.done" @toggle="todo.done = !todo.done" />처럼 씁니다. props는 읽기 전용이므로 자식이 직접 바꾸지 않고 이벤트로 요청합니다. 양방향 바인딩이 필요하면 defineModel()을 쓰면 부모의 v-model과 바로 연결됩니다. 자식 사이에 내용을 끼워 넣을 때는 <slot />을 씁니다.
Nuxt에서는 pages/ 폴더의 파일 경로가 URL이 됩니다. 대괄호 이름은 동적 매개변수입니다.
pages/index.vue → /
pages/about.vue → /about
pages/posts/[id].vue → /posts/:id<!-- pages/posts/[id].vue -->
<script setup lang="ts">
const route = useRoute();
definePageMeta({ layout: "default" });
</script>
<template>
<h1>글 {{ route.params.id }}</h1>
<NuxtLink to="/">목록으로</NuxtLink>
</template>app.vue에 <NuxtPage />를 두면 현재 URL에 맞는 페이지가 그 자리에 렌더링되고, <NuxtLink>는 페이지를 새로 불러오지 않고 이동합니다.
위 예제에 import 문이 없다는 점에 주목하세요. Nuxt는 ref, computed 같은 Vue API, useRoute·useFetch 같은 Nuxt 컴포저블, components/의 컴포넌트, composables/와 utils/의 함수를 자동으로 가져옵니다. components/base/Button.vue는 폴더 이름이 앞에 붙어 <BaseButton>이 됩니다. 명시적으로 쓰고 싶다면 import { ref } from "#imports"처럼 가져올 수도 있습니다.
<script setup>에서 선언한 것은 템플릿에서 바로 쓰입니다.ref는 .value로, reactive는 객체 그대로 다루며, 파생 값은 computed, 부수 효과는 watch에 둡니다.pages/ 파일 경로로 라우트를 만들고, API·컴포넌트·composable을 자동으로 가져옵니다.
댓글 0개
로그인 · 로그인하면 댓글을 남길 수 있습니다.
첫 댓글을 남겨 보세요.