Post

9장 Composition API

9장 Composition API

9장 Composition API

9.1 Composition API란?

Composition API는 Vue 3에서 제공하는 새로운 컴포넌트 구성 방식입니다.

9.2 setup 메서드를 이용한 초기화

1
2
3
4
5
6
7
8
9
10
11
<!-- MyComponent.vue -->
<script>
export default {
  setup() {
    return { message: 'Hello from setup!' }
  }
}
</script>
<template>
  <p></p>
</template>

setup은 컴포넌트 초기화 로직을 한 곳에서 관리합니다.

9.3 반응성을 가진 상태 데이터

9.3.1 ref

1
2
3
4
5
6
7
8
9
10
11
12
<script>
import { ref } from 'vue'
export default {
  setup() {
    const count = ref(0)
    return { count }
  }
}
</script>
<template>
  <p>9</p>
</template>

ref는 단일 반응형 값을 정의합니다.

9.3.2 reactive

1
2
3
4
5
6
7
8
9
10
11
12
<script>
import { reactive } from 'vue'
export default {
  setup() {
    const state = reactive({ name: 'Vue' })
    return { state }
  }
}
</script>
<template>
  <p></p>
</template>

reactive는 객체를 반응형으로 만듭니다.

9.4 computed

1
2
3
4
5
6
7
8
9
10
11
12
13
<script>
import { ref, computed } from 'vue'
export default {
  setup() {
    const count = ref(1)
    const doubled = computed(() => count.value * 2)
    return { count, doubled }
  }
}
</script>
<template>
  <p></p>
</template>

computed는 계산된 반응형 값을 생성합니다.

Vue.js에서 computedref는 둘 다 반응형 데이터를 다루는 데 사용되지만, 목적과 동작 방식에서 차이가 있습니다. 아래에서 이 둘의 차이를 자세히 설명하겠습니다.


1. ref란?

  • 정의: ref는 단일 값(원시 값이든 객체든)을 반응형으로 만들어주는 API입니다. Vue 3의 Composition API에서 주로 사용됩니다.
  • 특징:
    • ref로 생성된 값은 .value 속성을 통해 접근합니다(템플릿에서는 생략 가능).
    • 값이 변경되면 이를 감지하고 UI를 자동으로 업데이트합니다.
  • 용도: 주로 변수나 상태를 반응형으로 관리할 때 사용됩니다.

예제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
  <div>
    <p>9</p>
    <button @click="increment">증가</button>
  </div>
</template>

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

export default {
  setup() {
    const count = ref(0); // 반응형 변수

    const increment = () => {
      count.value++; // .value로 값 변경
    };

    return { count, increment };
  },
};
</script>
  • countref(0)으로 초기화되며, count.value를 통해 값을 변경합니다.
  • 템플릿에서는 count.value가 아닌 그냥 count로 사용 가능(Vue가 자동으로 처리).

2. computed란?

  • 정의: computed는 의존하는 반응형 데이터에 따라 계산된 값을 반환하는 함수입니다. 캐싱 기능을 제공해 성능을 최적화합니다.
  • 특징:
    • 의존성(예: refreactive 값)이 변경될 때만 재계산됩니다.
    • 읽기 전용 속성처럼 동작하며, 직접 값을 설정할 수 없습니다(Setter를 추가하면 예외).
    • 내부적으로 캐싱되어 불필요한 계산을 방지합니다.
  • 용도: 데이터 기반으로 파생된 값을 만들 때 사용됩니다.

예제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<template>
  <div>
    <p>원래 값: 9</p>
    <p>두 배 값: </p>
    <button @click="increment">증가</button>
  </div>
</template>

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

export default {
  setup() {
    const count = ref(0);

    const doubled = computed(() => count.value * 2); // 계산된 값

    const increment = () => {
      count.value++;
    };

    return { count, doubled, increment };
  },
};
</script>
  • doubledcount에 의존하며, count가 변경될 때만 재계산됩니다.
  • doubled.value로 접근 가능하지만, 템플릿에서는 doubled로 바로 사용됩니다.

3. 주요 차이점

| 특징 | ref | computed | |———————|————————————|————————————| | 목적 | 반응형 데이터 자체를 관리 | 의존성 기반으로 계산된 값을 제공 | | 값 접근 | .value로 접근 (템플릿 제외) | .value로 접근 (템플릿 제외) | | 변경 가능 여부 | 직접 수정 가능 (ref.value = x) | 기본적으로 읽기 전용 | | 캐싱 | 캐싱 없음, 값이 바로 반영 | 의존성 변경 시에만 재계산 (캐싱) | | 의존성 | 독립적인 값 | 다른 반응형 데이터에 의존 | | 사용 예 | 상태 변수 (예: count, name) | 파생 데이터 (예: fullName, total) |


4. 실제 사용 시나리오

ref를 사용하는 경우

  • 사용자 입력값, 상태 플래그 등 독립적인 데이터를 관리할 때.
  • 예: 입력 폼의 값, 토글 상태 등.
    1
    
    const name = ref(''); // 입력값 관리
    

computed를 사용하는 경우

  • 다른 데이터에 기반한 계산 결과가 필요할 때.
  • 예: 두 값의 합, 필터링된 리스트 등.
    1
    2
    3
    
    const firstName = ref('John');
    const lastName = ref('Doe');
    const fullName = computed(() => `${firstName.value} ${lastName.value}`);
    

5. Setter가 있는 computed

computed는 기본적으로 읽기 전용이지만, Setter를 추가해 양방향으로 동작하게 할 수 있습니다. 이 경우 ref와 비슷해 보일 수 있지만, 여전히 계산 로직에 의존합니다.

예제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<template>
  <div>
    <p></p>
    <button @click="displayName = 'Jane Doe'">변경</button>
  </div>
</template>

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

export default {
  setup() {
    const firstName = ref('John');
    const lastName = ref('Doe');

    const displayName = computed({
      get: () => `${firstName.value} ${lastName.value}`,
      set: (newValue) => {
        const [first, last] = newValue.split(' ');
        firstName.value = first;
        lastName.value = last;
      },
    });

    return { displayName };
  },
};
</script>
  • get: 계산된 값을 반환.
  • set: 값을 설정할 때 로직을 정의.

6. 결론

  • ref: “원본 데이터”를 반응형으로 관리할 때 사용. 값 자체를 다룹니다.
  • computed: “파생 데이터”를 만들 때 사용. 다른 데이터에 의존하며 계산된 결과를 제공합니다.
  • 선택 기준: 데이터가 독립적인지, 다른 값에 기반한 계산이 필요한지에 따라 선택하세요.

궁금한 점이 더 있으면 말씀해주세요!

Vue.js에서 refcomputed는 서로 다른 역할을 하므로, 각자가 할 수 있는 일과 할 수 없는 일이 분명히 존재합니다. 아래에서 ref가 할 수 있지만 computed가 못하는 경우와 그 반대(computed가 할 수 있지만 ref가 못하는 경우)를 간략히 예시로 설명하겠습니다.


1. ref가 할 수 있지만 computed가 못하는 경우

직접 값 수정

  • 상황: ref는 값을 직접 변경할 수 있지만, computed는 기본적으로 읽기 전용이라 직접 수정이 불가능합니다.
  • 예제:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
    <template>
      <div>
        <p>9</p>
        <button @click="count += 1">증가</button>
      </div>
    </template>
    
    <script>
    import { ref } from 'vue';
    
    export default {
      setup() {
        const count = ref(0); // ref는 값을 직접 수정 가능
        return { count };
      },
    };
    </script>
    
    • count.value += 1로 값을 변경할 수 있습니다.
    • 반면, computed로 만든 값은 직접 수정 불가:
      1
      2
      
      const doubled = computed(() => count.value * 2);
      doubled.value = 10; // 에러 발생! computed는 읽기 전용
      
  • 결론: 값을 직접 설정하거나 변경해야 할 때는 ref가 필요합니다.

2. computed가 할 수 있지만 ref가 못하는 경우

의존성 기반 캐싱과 자동 계산

  • 상황: computed는 의존하는 값이 변경될 때만 자동으로 재계산되고 캐싱되지만, ref는 수동으로 값을 업데이트해야 합니다.
  • 예제:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    <template>
      <div>
        <p>이름: </p>
        <p>전체 이름: </p>
        <button @click="name = 'Jane'">이름 변경</button>
      </div>
    </template>
    
    <script>
    import { ref, computed } from 'vue';
    
    export default {
      setup() {
        const name = ref('John');
        const fullName = computed(() => `${name.value} Doe`); // 자동 재계산
        return { name, fullName };
      },
    };
    </script>
    
    • name이 변경되면 fullName은 자동으로 업데이트됩니다. 캐싱 덕분에 name이 변하지 않으면 재계산되지 않습니다.
    • 반면, ref로 동일한 로직을 구현하려면 수동으로 값을 갱신해야 합니다:
      1
      2
      3
      4
      5
      6
      7
      
      const name = ref('John');
      const fullName = ref('John Doe'); // ref로 정의
      const updateFullName = () => {
        fullName.value = `${name.value} Doe`; // 수동 업데이트 필요
      };
      name.value = 'Jane';
      updateFullName(); // 호출하지 않으면 fullName은 갱신 안 됨
      
  • 결론: 다른 값에 의존하는 계산을 자동화하고 캐싱하려면 computed가 적합합니다.

요약

  • ref가 유리한 경우: 값을 직접 설정하거나 독립적인 상태를 관리할 때.
  • computed가 유리한 경우: 다른 반응형 데이터에 기반한 파생 값을 효율적으로 계산할 때.

이 차이 때문에 두 도구는 보완적으로 사용됩니다! 추가 질문 있으면 말씀해주세요.

9.5 watch와 watchEffect

9.5.1 watch

1
2
3
4
5
6
7
8
9
10
<script>
import { ref, watch } from 'vue'
export default {
  setup() {
    const count = ref(0)
    watch(count, (newVal) => console.log(newVal))
    return { count }
  }
}
</script>

watch는 특정 값의 변화를 감시합니다.

9.5.2 watchEffect

1
2
3
4
5
6
7
8
9
10
<script>
import { ref, watchEffect } from 'vue'
export default {
  setup() {
    const count = ref(0)
    watchEffect(() => console.log(count.value))
    return { count }
  }
}
</script>

watchEffect는 의존성 변화를 자동으로 감지해 실행합니다.

9.5.3 감시자 설정 해제

1
2
3
4
5
6
7
8
9
10
11
<script>
import { ref, watch } from 'vue'
export default {
  setup() {
    const count = ref(0)
    const stop = watch(count, () => console.log('Watching'))
    stop() // 감시 중지
    return { count }
  }
}
</script>

감시자를 해제하면 불필요한 감시를 멈출 수 있습니다.

9.6 생명주기 훅(Life Cycle Hook)

1
2
3
4
5
6
7
8
<script>
import { onMounted } from 'vue'
export default {
  setup() {
    onMounted(() => console.log('Mounted!'))
  }
}
</script>

생명주기 훅은 setup 내에서 컴포넌트 상태를 관리합니다.

9.7 TodoList App 리팩토링

1
2
3
4
5
6
7
8
9
10
11
12
13
<script>
import { ref } from 'vue'
export default {
  setup() {
    const todos = ref([])
    const addTodo = (task) => todos.value.push(task)
    return { todos, addTodo }
  }
}
</script>
<template>
  <ul><li v-for="todo in todos" :key="todo"></li></ul>
</template>

Composition API로 TodoList를 더 모듈화하고 간결하게 만듭니다.

9.8 〈script setup〉 사용하기

9.8.1 〈script setup〉이 기존과 다른 점

1
2
3
4
5
6
<script setup>
const message = 'Hello from script setup!'
</script>
<template>
  <p></p>
</template>

<script setup>은 더 간결한 문법으로 setup을 대체합니다.

9.8.2 TodoList 앱에 〈script setup〉 적용하기

1
2
3
4
5
6
7
8
<script setup>
import { ref } from 'vue'
const todos = ref([])
const addTodo = (task) => todos.value.push(task)
</script>
<template>
  <ul><li v-for="todo in todos" :key="todo"></li></ul>
</template>

<script setup>으로 TodoList를 더 간단히 작성할 수 있습니다.

9.9 마무리

Composition API는 코드 재사용성과 가독성을 높여 Vue 개발을 혁신합니다.

This post is licensed under CC BY 4.0 by the author.