티스토리 뷰

차이가 뭔가요?

전에 부사수한테 Vue를 교육할 때 Component, Composable 기능들을 설명해준적이 있다.

기능적인 측면에서 설명을 하다가 부사수가

Composable은 왜 사용하나요?

라고 물었는데, 직관은 하고 있지만 설명이 참 어려웠다.

그래서 Component와 Composable 차이를 구분하고 Class랑 마지막에 비교하는 방식으로 정리해볼려고 한다

1. Composable vs Component 

핵심 차이

두개의 핵심 차이는 뭘까?

결국은 UI가 있냐 없냐가 핵심 차이다.

핵심 차이점만 알고 있으면 이제 왜 사용하느냐가 중요하고 결국 이건 두개의 공통적인 추상화 기능이다.

공통점

공통점은

State(상태)에 따라 동작이 정의가 된다

라는 것이다.

이게 가장 핵심이 되는 공통점이다.

추상화라는 건 항상 단계별로 진행이된다.

프로그래밍은 최초에 명령형으로 코드를 작성하여 프로세스 순대로 쭉 코드를 작성하게된다.

작성하다보면 항상 특정 데이터와 기능이 결합되어 있는 패턴을 발견하게 된다.

여러 프레임워크가 그렇듯 이러한 문제를 추상화하는 기법을 다 가지고 있다.

Java는 OOP(Object Orinted Programing) 중심으로 추상화를 하며,

Vue에서는 반응형 상태에 따라 추상화를 진행한다.

그럼 이제 예제를 통해 확인을 해보자

예제

Composable

import { ref, computed } from 'vue';

export function useCounter(initialValue = 0) {
  // 1. 반응형 상태 (데이터)
  const count = ref(initialValue);

  // 2. 상태 변경 로직 (기능 추상화)
  const increment = () => count.value++;
  const decrement = () => count.value--;

  // 3. 파생된 상태 (추가적인 추상화)
  const isEven = computed(() => count.value % 2 === 0);

  // UI 없이 로직과 상태만 내보냄
  return { count, increment, decrement, isEven };
}
  • count라는 반응형 상태로 기능들이 추상화가 되었다
  • increment, decrement 기능들이 존재
  • 파생 상태를 관리하기 위한 computed (계산) 속성이 존재

위 예제로 상태에 따른 기능 추상화를 확인할 수 있다.

Component

자식

CounterChild.vue

<template>
  <div class="child-card">
    <h3>[자식: v-model 적용]</h3>
    <p>현재 값: <strong>{{ modelValue }}</strong></p>

    <button @click="modelValue++">1 증가 (+)</button>
    <button @click="modelValue--">1 감소 (-)</button>
    
    <p class="tip">※ 여기서 변경하면 부모 데이터도 즉시 변경.</p>
  </div>
</template>

<script setup>
// v-model을 위한 특수 매크로 사용
// 부모의 v-model과 연결되는 반응형 상태(ref)
const modelValue = defineModel({ type: Number, default: 0 });
</script>

부모

<template>
  <div class="parent-container">
    <h2>[부모: 결과 수신처]</h2>
    <p>확정된 부모의 데이터: <span class="total">{{ totalCount }}</span></p>

    <CounterChild 
      v-model="count"
    />
  </div>
</template>

<script setup>
import { ref } from 'vue';
import CounterChild from './CounterChild.vue';

const count= ref(1); // 초기값 1
</script>
  • 자식 컴포넌트에서 model을 정의한다
  • 부모에서는 상태를 자식 컴포넌트에 주입
  • 자식 컴포넌트에서 UI 이벤트에 따라 상태가 바뀐다

이처럼 Component와 Composable은 상태를 중심으로 기능들을 추상화한다는 점을 볼 수 있다.

물론 예제코드가 설명을 위한 최소 예제라 Component와 Composable 에 보다 확장되는 기능들을 못보여준게 아쉽긴하지만 어쩃든 지금 구조에서는 최소 공통부분만 확인하는 정도로 정리하는게 좋을것 같다.

마지막으로 스스로 의문이 들었던점은

Class 와 Composable은 어떻게 다를까

였다.

항상 비슷하다고 느끼면서 정리를 해본적이 없는데 이번 기회로 정리를 해볼려고 한다.

Composable vs Class

핵심 차이

핵심적인 차이점은 상속이냐 조합이냐 이다.

Composable 함수는 보다 시피 상태에 따른 기능들을 정의하여 조합하는 형태로 확장한다면,

Class는 OOP 원칙에 따라 객체를 식별하고 해당 객체를 상속이라는 기능으로 확장해가는 방식을 취한다.

Composable 확장

import { ref } from 'vue';

// 기능 1: 검색 로직만 담당 (UI 없음, 반응형 상태 포함)
function useSearch(data) {
  const searchQuery = ref("");
  const filteredData = () => data.value.filter(i => i.includes(searchQuery.value));
  return { searchQuery, filteredData };
}

// 기능 2: 정렬 로직만 담당
function useSort(data) {
  const sortOrder = ref("asc");
  const sortedData = () => [...data.value].sort();
  return { sortOrder, sortedData };
}

// 실제 사용: 필요한 기능만 '조합'
function useInventory() {
  const items = ref(["Apple", "Banana", "Cherry"]);
  
  const { searchQuery, filteredData } = useSearch(items);
  const { sortOrder } = useSort(items);

  return { items, searchQuery, filteredData, sortOrder };
}

Class 확장

// 부모 클래스: 기본 데이터 관리
class BaseService {
  data = [];
  fetch() { console.log("데이터 로드"); }
}

// 상속을 통한 확장: 검색 기능 추가
class SearchService extends BaseService {
  search(query: string) {
    return this.data.filter(item => item.includes(query));
  }
}

// 또 상속: 검색 + 정렬 기능 추가
class SearchAndSortService extends SearchService {
  sort() {
    console.log("정렬 실행");
  }
}

const service = new SearchAndSortService();
service.fetch();  // 부모의 기능
service.search("A"); // 중간 부모의 기능

이처럼 조합이냐 상속이냐가 핵심 차이고, Web Front에서는 Composable 방식이 더 적합하다고 생각하는데, 객체가 명확하게 식별되는 경우는 Class를 이용하기도 했어서 배타적으로 선택하는 문제는 아니라고 생각한다.

마무리

Composable 을 왜 사용할까? 라는 의문에서 시작되어 정리를 해보았는데, 정리를 하다보니 직관으로 알던 지식이 조금 체계가 잡혔던 경험이 되었다.

이러한 추상화기법을 토대로

프로그램 규모가 커져도 무서워 하지말고 문제를 일정 수준으로 추상화하여

잘 관리되는 프로그램을 만들어야 한다는 것을 다시금 되새겨본다.

댓글