본문으로 바로가기

[Vue] Composition API

category Helloworld!/Vue 2022. 3. 21. 14:57

Vue3 에서 달라진 점

https://v3.ko.vuejs.org/guide/migration/introduction.html#주목할-만한-새로운-기능들

 

시작하기 | Vue.js

시작하기 INFO Vue.js의 새로운 기능에 대한 정보가 필요하신가요? 그렇다면 필수가이드를 확인하세요. 이번 가이드는 Vue 2 경험이 있으면서, Vue 3 변경사항 및 새로운 기능을 배우고 싶은 사용자를

v3.ko.vuejs.org

https://composition-api.nuxtjs.org/

 

Nuxt Composition API

Vue 3 Composition API in Nuxt

composition-api.nuxtjs.org

 

Composition API 탄생 배경

1. Options API

  • data, methods, computed 에 각각 로직이 분산되어 추적이 어렵다.
  • 하나의 데이터가 어떻게 변화하고 사용되는지 그룹핑하기 어렵다.
    • mixin 을 사용하면 되지만 다중 mixin 을 상속하게 되면 오버라이딩 문제나 component 관리가 어려워진다.
    • 즉, 유지보수가 어렵다 어려워 :(

2. Composition API 의 setup 함수에 데이터가 그룹핑 되어 보다 용이하게 데이터의 흐름을 파악하고 로직의 재사용성과 가독성을 높여준다.

 

Composition API 예시

  • 기존 방식
<template>
    <div>
        <h1>Count: {{ count }}</h1>
        <h1>Double: {{ double }}</h1>
        <button @click="increase">increase</button>
        <button @click="decrease">decrease</button>
    </div>
</template>

<script>
export default {
    data () {
        return {
            count: 0,
        }
    },
    computed: {
        double () {
            return this.count * 2;
        }
    },
    methods: {
        increase () {
            ++this.count;
        },
        decrease () {
            --this.count;
        }
    }
}
</script>

딱히 별 기능 없이 간단한 카운트 기능을 하는 Component 에 비슷한 기능을 가진 로직들이 data, methods, computed 로 각각 분리되어 있다.

  • Component API 방식
<template>
    <div>
        <h1>Count: {{ count }}</h1>
        <h1>Double: {{ double }}</h1>
        <button @click="increase">increase</button>
        <button @click="decrease">decrease</button>
    </div>
</template>

<script>
import { reactive, computed } from '@vue/composition-api';

const useCount = () => {
    const count = ref(0);
    const double = computed(() => count.value * 2);

    const increase = () => ++count.value;
    const decrease = () => --count.value;

    return { count, double, increase, decrease }
}

export default {
    setup () {
        const { count, double, increase, decrease } = useCount();

        return {
            count,
            double,
            increase,
            decrease
        }
    }
}
</script>

관련된 것들을 한 부분에서 해결하고 있기 때문에 추후 Component 를 나눌 때도 편해지고 가독성도 좋아진다.

 

Setup

  • 새로 추가된 라이프 사이클 훅으로써 Composition API 를 사용하기 위한 초기화 지점이다.
  • 기존의 beforCreate 훅 이전에 setup이 호출되며, Composition API 를 사용하는 경우 beaforCreate, created hook을 setup 훅으로 대체하여 사용된다.
  • 따라서 다음과 같이 기존 라이프 사이클에도 변화가 생긴다. (기존의 라이프 사이클을 사용할 땐 앞에 on을 붙여서 사용해야 된다.)

import { onMounted, onUpdated, onUnmounted } from  '@vue/composition-api'

const MyComponent = {
  setup() {
    onMounted(() => {
      console.log('mounted!')
    })
    onUpdated(() => {
      console.log('updated!')
    })
    onUnmounted(() => {
      console.log('unmounted!')
    })
  }
}

 

Data 선언

  • ref : 원시값(ex. 1, 'abc', false), 객체 선언 시 사용하며 모두 반응형을 가진다.
  • reactive : 원시값에 대해서는 반응형을 가지지 않고 객체나 배열에서만 깊은 감지를 수행한다.
<template>
  <div>{{ count }} {{ object.foo }}</div>
</template>

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

export default {
  setup() {
    const count = ref(0);
    const object = reactive({ foo: 'bar' });
    console.log(count.value); // 0

    return {
      count,
      object
    }
  }
}
</script>

예제에서 보다 시피 ref 로 선언한 값은 반드시 .value 를 통해 접근해야 실제 값을 받아올 수 있다. 그러나 template 에서는 접근하려 할 경우는 생략 가능하다.

  • refreactive 둘다 객체 선언이 가능하면 ref 만 사용하면 되는게 아닐까?
    • ref 는 원본 값을 value 라는 속성에 담아두고 변경을 감시하는 객체이고, reactive 는 원본 객체 자체에 변경을 감지하는 옵저버를 추가하여 그대로 반환하기 때문에 각 특성에 따라 사용하면 된다.
  • ref 로만 구성했을 때 예시
setup(props, context) {
  const inputText = ref('');
  const isFocused = ref(false);

  const onChange = (): void => {
    context.emit('on-change', inputText.value);
  }
  const onFocus = (): void => {
    isFocused.value = true; // value에 할당
  }

  const onBlur = (): void => {
    isFocused.value = false; // value에 할당
  }

  onBeforeMount(() => {
    inputText.value = props.value; // value에 할당
  })

  return {
    inputText,
    isFocused,
    onChange,
    onFocus,
    onBlur
  }
}
  • reactive 로 구성했을 때 예시 ( + toRefs)
setup(props, context) {
  const state = reactive({
    inputText: '',
    isFocused: false
  });

  const onChange = (): void => {
    context.emit('on-change', state.inputText);
  }
  const onFocus = (): void => {
    state.isFocused = true; // state의 property를 통해 값을 할당한다.
  }

  const onBlur = (): void => {
    state.isFocused = false; // state의 property에 값을 할당한다.
  }

  onBeforeMount(() => {
    state.inputText = props.value; // state의 property에 값을 할당한다.
  })

  return {
    ...toRefs(state), // toRefs를 사용하여 ref로 변환한다.
    onChange,
    onFocus,
    onBlur
  }
}

이처럼 위와 같이 reactive 로 객체 값을 생성한 뒤 각 속성을 분해해서 재할당 할 경우 반응형으로 동작하지 않기 때문에 반환 시 toRefs 를 사용해 문제를 해결할 수 있다.

  • ifRef
...
const printValue = obj => {
  console.log(isRef(obj) ? obj.vlaue : obj); //ref 이면 .value 붙여서 출력, 아닐 경우 obj만으로 출력
}
...

 

Component 선언

  • computed(fn) 처럼 감싸서 값 선언을 해준다.
<template>
  <div>{{ count }} {{ object.foo }}</div>
</template>

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

export default {
  setup() {
    const count = ref(0);
    const plusOne = computed(() => count.value + 1);

    console.log(plusOne.value); // 1

    return {
      count,
      object
    }
  }
}
</script>
  • get, set 또는 추가 설정 가능하다.
<template>
  <div>{{ count }} {{ object.foo }}</div>
</template>

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

export default {
  setup() {
    const count = ref(0);
    const plusOne = computed({
      get: () => count.value + 1,
      set: val => { count.value = val - 1 }
    });

    console.log(plusOne.value); // 1

    return {
      count,
      object
    }
  }
}
</script>

 

Props

  • 상위에서 어떤 props 를 받을 것인지 알려준 후, setup 에서 props.xxx 로 접근한다.
// Parent Component
<template>
  <dlv class="home">
    <PostList :posts="posts" />
  </div>
</template>
<script>
  import PostList from '../components/PostList.vue'
  import { ref } from 'vue';

  export default {
    name: 'Home',
    components: { PostList },

    setup() {
      const posts = ref([
        { title: '1번 타이틀', body: '1번 제목', id: 1 },
        { title: '2번 타이틀', body: '2번 제목', id: 2 },
      ]);

      return { posts }
    }
  }
</script>

// Child Component
<template>
  <div>
    {{ post.title }}
    {{ post.body }}
  </div>
</template>
<script>
export default {
  props: ["posts"], // 사용할 props를 배열내에 정의합니다.
  setup(props) {
    console.log(props.posts); // 받은 prop 사용가능
  }
};
</script>
  • 이로써 죄다 this 에 때려박는 바인딩에서 탈피할 수 있다 (props 는 props! this 는 this!)

 

그 외