✅ 프로젝트 개요

1. PostForm.vue – 입력과 유효성 검사 (ref, computed, emit)

핵심 코드

const title = ref('')
const body = ref('')

const titleError = computed(() =>
  title.value.length > 30 ? '제목은 30자 이내여야 합니다' : ''
)

const emit = defineEmits(['add-post'])

function submit() {
  if (titleError.value) return
  emit('add-post', { title: title.value, body: body.value })
  title.value = ''
  body.value = ''
}

학습 포인트

개념 설명
ref() 반응형 입력 상태 (title, body)를 선언
computed() 제목 길이에 따라 에러 메시지를 실시간 계산
defineEmits() 부모 컴포넌트에 이벤트 전달 (@add-post)
Composition API setup 안에서 상태, 함수, 로직 분리 가능

2. PostList.vue – 리스트 렌더링 (v-for, props, emit)

<ul>
  <li v-for="post in posts" :key="post.id">
    <h3>{{ post.title }}</h3>
    <p>{{ post.body }}</p>
    <button @click="$emit('delete-post', post.id)">삭제</button>
  </li>
</ul>
defineProps<{ posts: Post[] }>()

학습 포인트

개념 설명
v-for 전달받은 posts 배열을 반복 렌더링
props 부모로부터 posts를 전달받음
emit @click 이벤트를 통해 post.id를 부모에게 전달
:key DOM 최적화를 위한 필수 식별자 지정

3. usePostStore.ts – 상태관리 (Pinia, actions, store 사용법)

export const usePostStore = defineStore('post', {
  state: () => ({
    posts: [] as Post[]
  }),
  actions: {
    addPost(post) {
      this.posts.unshift({ ...post, id: Date.now() })
    },
    deletePost(id) {
      this.posts = this.posts.filter(p => p.id !== id)
    }
  }
})

학습 포인트

개념 설명
Pinia 전역 상태 저장소 선언 및 사용
defineStore 상태(posts)와 메서드(addPost, deletePost) 정의
actions store 메서드는 this로 state 접근
unshift() 새 글을 목록 앞에 추가