Vue.js 를 배워보자

Vue CRUD UI 구성 시 watch와 computed 활용하기

_Blue_Sky_ 2025. 4. 1. 22:11
728x90

 
안녕하세요, 개발자 여러분! 이번에는 Vue.js의 Composition API를 사용해 CRUD(Create, Read, Update, Delete) UI를 구성하면서 watchcomputed를 실무적으로 활용하는 방법을 블로그 글 형식으로 풀어보겠습니다. Composition API는 기존 Options API보다 유연하고 모듈화된 코드를 작성할 수 있게 해주며, 특히 로직 재사용성이 뛰어납니다.

1. CRUD UI의 기본 설정
CRUD UI를 구성한다고 가정하면, 사용자 목록을 표시하고 검색, 추가, 수정, 삭제 기능을 제공하는 인터페이스를 구현할 수 있습니다. Composition API를 사용하면 데이터와 로직을 함수 단위로 깔끔하게 분리할 수 있습니다. 먼저 기본 구조를 보겠습니다.

 
 
2. computed로 데이터 필터링 구현
computed는 반응형 데이터를 기반으로 계산된 값을 제공하며, 캐싱을 통해 성능을 최적화합니다. 사용자 목록을 검색어로 필터링하는 예제를 Composition API로 작성해보겠습니다.
<template>
  <div>
    <input v-model="searchQuery" placeholder="사용자 검색" />
    <table>
      <thead>
        <tr>
          <th>ID</th>
          <th>이름</th>
          <th>이메일</th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="user in filteredUsers" :key="user.id">
          <td>{{ user.id }}</td>
          <td>{{ user.name }}</td>
          <td>{{ user.email }}</td>
        </tr>
      </tbody>
    </table>
  </div>
</template>

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

export default {
  setup() {
    const searchQuery = ref('');
    const users = ref([
      { id: 1, name: '김철수', email: 'cheolsu@example.com' },
      { id: 2, name: '이영희', email: 'younghee@example.com' },
      { id: 3, name: '박민수', email: 'minsu@example.com' },
    ]);

    const filteredUsers = computed(() => {
      return users.value.filter(user =>
        user.name.includes(searchQuery.value) || user.email.includes(searchQuery.value)
      );
    });

    return {
      searchQuery,
      filteredUsers,
    };
  },
};
</script>
설명: ref로 반응형 데이터를 정의하고, computedfilteredUsers를 계산합니다. searchQuery가 변경될 때마다 자동으로 필터링된 결과가 UI에 반영됩니다. Composition API에서는 this 없이 변수와 함수를 직접 다루므로 코드가 직관적입니다.

3. watch로 사용자 입력 감지 및 유효성 검사
watch는 특정 데이터의 변화를 감지해 로직을 실행합니다. 사용자 추가 폼에서 입력값의 유효성을 실시간으로 체크하는 예제를 보겠습니다.
<template>
  <div>
    <h2>사용자 추가</h2>
    <input v-model="newUser.name" placeholder="이름" />
    <input v-model="newUser.email" placeholder="이메일" />
    <button @click="addUser" :disabled="!isFormValid">추가</button>
  </div>
</template>

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

export default {
  setup() {
    const newUser = reactive({
      name: '',
      email: '',
    });
    const isFormValid = ref(false);

    const validateForm = () => {
      const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
      isFormValid.value =
        newUser.name.trim().length > 0 && emailRegex.test(newUser.email);
    };

    watch(
      () => [newUser.name, newUser.email],
      () => {
        validateForm();
      },
      { immediate: true }
    );

    const addUser = () => {
      if (isFormValid.value) {
        console.log('사용자 추가:', { ...newUser });
        newUser.name = '';
        newUser.email = '';
      }
    };

    return {
      newUser,
      isFormValid,
      addUser,
    };
  },
};
</script>
설명: reactive로 객체를 반응형으로 만들고, watchnewUsernameemail 변화를 감지합니다. 배열 형태로 여러 값을 한 번에 감시할 수 있는 점이 Composition API의 강력한 기능 중 하나입니다. 입력값이 변경될 때마다 validateForm이 호출되어 isFormValid를 업데이트합니다.

 
 
4. watchcomputed 조합으로 수정 폼 구현
실무에서는 watchcomputed를 함께 사용해 복잡한 상태를 관리하는 경우가 많습니다. 선택된 사용자를 수정하는 폼을 예로 들어보겠습니다.
<template>
  <div>
    <h2>사용자 수정</h2>
    <select v-model="selectedUserId">
      <option v-for="user in users" :value="user.id" :key="user.id">
        {{ user.name }}
      </option>
    </select>
    <div v-if="selectedUser">
      <input v-model="selectedUser.name" />
      <input v-model="selectedUser.email" />
      <button @click="saveChanges" :disabled="!hasChanges">저장</button>
    </div>
  </div>
</template>

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

export default {
  setup() {
    const selectedUserId = ref(null);
    const users = ref([
      { id: 1, name: '김철수', email: 'cheolsu@example.com' },
      { id: 2, name: '이영희', email: 'younghee@example.com' },
    ]);
    const originalUser = ref(null);

    const selectedUser = computed(() => {
      return users.value.find(user => user.id === selectedUserId.value);
    });

    const hasChanges = computed(() => {
      if (!selectedUser.value || !originalUser.value) return false;
      return (
        selectedUser.value.name !== originalUser.value.name ||
        selectedUser.value.email !== originalUser.value.email
      );
    });

    watch(selectedUserId, (newId) => {
      const user = users.value.find(u => u.id === newId);
      originalUser.value = user ? { ...user } : null;
    });

    const saveChanges = () => {
      if (hasChanges.value) {
        console.log('변경된 데이터 저장:', selectedUser.value);
        originalUser.value = { ...selectedUser.value }; // 원본 동기화
      }
    };

    return {
      selectedUserId,
      users,
      selectedUser,
      hasChanges,
      saveChanges,
    };
  },
};
</script>
설명: computedselectedUserhasChanges를 정의해 선택된 사용자와 변경 여부를 계산합니다. watchselectedUserId가 변경될 때 원본 데이터를 originalUser에 저장합니다. 이 조합은 수정 폼에서 데이터 비교와 저장 로직을 깔끔하게 처리합니다.

5. 실무 팁
  • 재사용성: Composition API를 활용하면 로직을 별도 함수로 분리해 여러 컴포넌트에서 재사용할 수 있습니다.
  • 디바운싱: watchdebounce를 적용하려면 watch(() => value.value, debounce(handler, 300))처럼 커스텀 로직을 추가하세요.
  • 상태 관리: 대규모 앱에서는 Pinia와 함께 사용해 watchcomputed의 부담을 줄이는 것도 추천합니다.

마무리
Composition API를 사용하면 watchcomputed를 더 유연하고 모듈화된 방식으로 활용할 수 있습니다. computed로 데이터를 가공하고, watch로 상태 변화를 감지해 CRUD UI를 효율적으로 구현해보세요. 코드가 깔끔해지고 유지보수도 쉬워질 거예요! 궁금한 점이 있다면 언제든 질문 주세요. Happy coding!
728x90