조아마시

쓸모 있는 상세페이지 만들기

웹개발/vuejs

[Vue] Vue3에서 사용자 훅 만들기

joamashi 2024. 7. 30. 00:33

Vue 3의 Composition API를 이용하면 재사용 가능한 로직을 훅(hook) 형태로 만들어 컴포넌트 간에 공유할 수 있습니다. 이는 코드 재사용성을 높이고, 컴포넌트를 더욱 모듈화하여 관리하기 쉽게 만듭니다.

왜 사용자 훅을 만들어야 할까요?

  • 코드 재사용성 증가: 여러 컴포넌트에서 공통적으로 사용되는 로직을 한 번만 작성하여 재사용할 수 있습니다.
  • 컴포넌트 복잡도 감소: 컴포넌트 내 로직을 작은 단위의 훅으로 분리하여 관리하기 쉬워집니다.
  • 테스트 용이성 향상: 각 훅을 독립적으로 테스트할 수 있어 컴포넌트 전체를 테스트하는 것보다 효율적입니다.
  • 더 나은 코드 가독성: 복잡한 로직을 작은 단위의 함수로 분리하여 코드를 더 명확하게 작성할 수 있습니다.

사용자 훅 만드는 방법

  1. 새로운 파일 생성: hooks 폴어를 만들고, 그 안에 훅 파일을 생성합니다. (예: useFetchData.js)
  2. 함수 정의: use로 시작하는 이름으로 함수를 정의합니다.
  3. 반응형 데이터 관리: ref, reactive, computed 등을 사용하여 반응형 데이터를 관리합니다.
  4. 로직 구현: 필요한 로직을 구현하고, 반환값으로 필요한 데이터를 제공합니다.

예시: 데이터 Fetching 훅

import { ref, onMounted } from 'vue';

export function useFetchData(url) {
  const data = ref(null);
  const error = ref(null);
  const loading = ref(false);

  onMounted(async () => {
    loading.value = true;
    try {
      const response = await fetch(url);
      data.value = await response.json();
    } catch (error) {
      console.error(error);
      error.value = error;
    } finally {
      loading.value = false;
    }
  });

  return { data, error, loading };
}

사용자 훅 사용 방법

<template>
  <div v-if="loading">Loading...</div>
  <div v-else-if="error">Error: {{ error }}</div>
  <div v-else>
    </div>
</template>

<script setup>
import { useFetchData } from '@/hooks/useFetchData';

const { data, error, loading } = useFetchData('https://api.example.com/data');
</script>
 
 

추가적인 팁

  • 훅의 목적을 명확히 하기: 훅 하나에 하나의 기능만 담는 것이 좋습니다.
  • 타입 선언: TypeScript를 사용한다면, 훅의 인자와 반환값에 타입을 명확히 선언하여 코드의 안정성을 높일 수 있습니다.
  • 커스텀 훅 라이브러리 활용: VueUse와 같은 커스텀 훅 라이브러리를 활용하면 이미 구현된 유용한 훅들을 사용할 수 있습니다.

 

Vue 3 사용자 훅: 다양한 예시 및 활용법

1. 양식 유효성 검사 훅

import { ref, computed } from 'vue';

export function useFormValidation(rules) {
  const formData = ref({});
  const errors = ref({});

  const validate = () => {
    // rules에 따라 formData를 검증하고 errors에 오류 메시지 저장
  };

  const handleChange = (event) => {
    formData.value[event.target.name] = event.target.value;
    validate();
  };

  return { formData, errors, handleChange };
}

2. 데모 모드 훅

import { ref } from 'vue';

export function useDemoMode() {
  const isDemoMode = ref(false);

  const toggleDemoMode = () => {
    isDemoMode.value = !isDemoMode.value;
  };

  return { isDemoMode, toggleDemoMode };
}

3. 지연 로딩 이미지 훅

import { ref, onMounted } from 'vue';

export function useLazyImage(src) {
  const isLoaded = ref(false);
  const imgRef = ref(null);

  onMounted(() => {
    const observer = new IntersectionObserver((entries) => {
      if (entries[0].isIntersecting) {
        isLoaded.value = true;
        observer.disconnect();
      }
    });
    observer.observe(imgRef.value);
  });

  return { isLoaded, imgRef };
}

4. 모달 관리 훅

import { ref } from 'vue';

export function useModal() {
  const isModalOpen = ref(false);

  const openModal = () => {
    isModalOpen.value = true;
  };

  const closeModal = () => {
    isModalOpen.value = false;
  };

  return { isModalOpen, openModal, closeModal }
}

5. 디바이스 감지 훅

import { ref, onMounted } from 'vue';

export function useDeviceDetect() {
  const isMobile = ref(false);

  onMounted(() => {
    isMobile.value = window.innerWidth <= 768;
  });

  return { isMobile };
}
 

6. 데이터 페칭 및 캐싱:

import { ref, onMounted } from 'vue';

export function useFetchDataWithCache(url) {
  const data = ref(null);
  const isLoading = ref(false);
  const error = ref(null);

  const fetchData = async () => {
    // ... 데이터 페칭 로직
  };

  onMounted(async () => {
    // 캐시에서 데이터 가져오기
    if (cachedData) {
      data.value = cachedData;
    } else {
      isLoading.value = true;
      try {
        const response = await fetchData();
        data.value = response;
        // 캐시에 저장
      } catch (error) {
        error.value = error;
      } finally {
        isLoading.value = false;
      }
    }
  });

  return { data, isLoading, error };
}

7. 드래그 앤 드롭:

import { ref } from 'vue';

export function useDraggable(element) {
  const isDragging = ref(false);
  const offsetX = ref(0);
  const offsetY = ref(0);

  const handleDragStart = (event) => {
    // ... 드래그 시작 이벤트 처리
  };

  const handleDragEnd = (event) => {
    // ... 드래그 종료 이벤트 처리
  };

  const handleDrag = (event) => {
    // ... 드래그 중 이벤트 처리
  };

  return { isDragging, offsetX, offsetY, handleDragStart, handleDragEnd, handleDrag };
}

8. 무한 스크롤

import { ref, onMounted } from 'vue';

export function useInfiniteScroll(fetchMoreData) {
  const isLoading = ref(false);
  const hasMore = ref(true);

  const handleScroll = () => {
    // ... 스크롤 이벤트 처리
  };

  onMounted(() => {
    // ... 스크롤 이벤트 리스너 등록
  });

  return { isLoading, hasMore, handleScroll };
}
728x90