woolta

vue composition api 살펴보기

wooltaUserImgb00032 | vue | 2020-03-10

개요

vue 의 3.x 버전 릴리즈가 코앞에 다가오고 있습니다. 이번에는 vue 3.x 버전의 큰 변화점중 한개인 composition-api 에 대해 알아도록 하겠습니다.

composition api란?

composition api 는 대규모 프로젝트에서도 코드의 재사용성을 증가와 로직에 대한 관심사를 모으기 위해 새롭게 추가된 API 입니다. composition api를 사용하게 되면 기존의 mixin 을 사용하며 번거롭게 느낀 namespace 충돌을 방지할 수 있고 코드 로직에 대한 관심사가 한군데에 집중되게 되어 가독성 면에서 큰 이점이 존재합니다. 기존의 react를 하시는 분은 hook의 개념을 생각하시면 이해하는데 한층 더 편할 수 있습니다.

기존 방식(2.x.x) 의 에로사항.

<template>
  <button @click="increment">
    Count is: {{ count }}, double is: {{ double }}
  </button>
</template>

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

위의 코드예시는 count 의 값을 보여주고 computed 속성을 통해 2배의 값을 보여주고 method 을 통해 count 를 증가시켜주고 있습니다. 물론 작은단위의 컴포넌트는 기존방식으로도 충분히 좋은 가독성을 보여주고 있습니다.

로직의 추가

<template>
    <div>
        <button @click="increment">
            Count is: {{ count }}, double is: {{ double }}
        </button>
        <input type="text" v-model="inputValue"/>
        reversed : {{ reversedInputValue }}
        <p>{{mousePositionMessage}}</p>
    </div>
</template>

<script>

export default {
    data() {
        return {
            count: 0,
            inputValue: '',
            x: 0,
            y: 0,
        }
    },
    computed: {
        double() {
            return this.count * 2
        },
        reversedInputValue() {
            return this.inputValue.split('').reverse().join('')
        },
        mousePositionMessage() {
            return `x: ${this.x} y: ${this.y}`;
        },
    },
    methods: {
        increment() {
            this.count++
        },
        mousePositionUpdate(e) {
            this.x = e.pageX
            this.y = e.pageY
        }
    },
    mounted() {
        window.addEventListener('mousemove', this.mousePositionUpdate);
    },
    destroyed() {
        window.removeEventListener('mousemove', this.mousePositionUpdate);
    }
}
</script>

위의 코드는 첫 예제에서 mouse 이동시의 좌표값 보여주는 로직과 input 값을 받는 로직이 추가되었습니다. 이제 데이터 상태의 관리는 count, inputValue, (x,y) 3개에 대해 한 컴포넌트에서 관리하게 됩니다. 이때 기존버전에서는 데이터의 기본설정, 함수, 생명주기, 계산속성(computed) 로만 분리가 되다보니 이러한 관심사가 커질수록 내가 원하는 데이터가 어디에서 처리되고 있는지 확인하기 어렵다는 불편함이 존재합니다.

composition api 를 이용한 개선

<template>
    <div>
        <button @click="increment">
            Count is: {{ count }}, double is: {{ double }}
        </button>
        <input type="text" v-model="inputValue"/>
        reversed : {{ reversedInputValue }}
    </div>
</template>

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

    export default {
        setup() {
            return {
                ...useCount(),
                ...useHandleInput(),
                ...useMousePosition()
            }
        }
    }

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

        return {count, double, increment};
    }

    function useHandleInput() {
        const inputValue = ref('');
        const reversedInputValue = computed(() => inputValue.value.split('').reverse().join(''));
        return {inputValue, reversedInputValue};
    }

    function useMousePosition() {
        const x = ref(0);
        const y = ref(0);

        const mousePositionUpdate = e => {
            x.value = e.pageX
            y.value = e.pageY
        }

        onMounted(() => {
            window.addEventListener('mousemove', mousePositionUpdate)
        })

        onUnmounted(() => {
            window.removeEventListener('mousemove', mousePositionUpdate)
        })

        return {x, y}
    }
</script>

composition api 를 사용해 input, count, mousePosition 각 값들에 대한 관심사를 각각 useCount, useHandleInput, useMousePosition 로 묶어서 처리하게 되어 count 관련 변경점이나 확인사항이 필요할 시 useCount만 확인하면 되어 더욱 간결하고 직관적인 처리가 가능하도록 되었습니다. 이러한 관심사는 하단 이미지 처럼 각 관심사별로 큰 덩어리로 분리될 수 있습니다.

https://image.woolta.com/3fe112a5a0d7f157.png 그럼 이제부터 composition aPI 의 사용방법에 대해 간략하게 알아보도록 하겠습니다.

composition api 설치하기

vue의 메이저 버전이 3.x 이라면 vue만 설치해도 가능하지만 아직 릴리즈 되지 않았기 때문에 2.x 버전에서 composition api 사용을 위해 플러그인 방식으로 사용하도록 하겠습니다.

패키지 설치

우선 다음과 같이 package를 설치해 주도록 합니다.

npm install @vue/composition-api

이후 아래 코드와 같이 plugin 설정을 세팅해 주면 세팅 완료 입니다.!

import Vue from 'vue';
import VueCompositionApi from '@vue/composition-api';

Vue.use(VueCompositionApi);

setup 함수

setup 함수는 beforeCreate 생명주기 전에 호출되는 composition aPi 사용을 위한 진입점 역활을 담당합니다. setup 함수는 lifeCycle 에서 beforeCreate와 create의 역활도 대체 합니다. 해당 함수내에서 기존의 data, computed, method, lifeCycle 에 대한 설정이 한번에 가능 합니다.

<template>
  <div>{{ count }}</div>
</template>

<script>
import { ref } from 'vue'

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

    return {
      count,
    }
  }
}
</script>

위의 코드처럼 setup 에서 return 한 값을 사용하게 됩니다.

data 선언

기존의 data 영역을 통한 변수선언은 setup 내부에서는 2가지로 선언 가능합니다.

  • ref : 기본형 값 (ex. : 1,'abc',false )
  • reactive : 객체 (2.x 의 Vue.observable() 와 동일)
<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>

이런식으로 2가지 방법으로 데이터를 선언할 수 있습니다. 이때 ref로 선언한 값은 반드시 .value 를 통해 접근해야 실제 값을 받아올 수 있습니다. 그러나 위의 예제처럼 바로 template에서 사용할 경우는 생략이 가능합니다.

computed 선언

setup 함수 내에서 computed 를 사용하고 싶다면 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
    }
  }
}
</script>

물론 따로 set, get 도 추가 설정이 가능합니다.

<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 }
	})

    return {
      count
    }
  }
}
</script>

lifeCycle

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!')
    })
  }
}

제공하는 생명주기는 다음과 같습니다.

https://image.woolta.com/3fe1d609dc2ada4a.png

관심사 별로 나누어서 관리하기

위의 composition api 예시에서 사용한 count, object 에 대한 예시를 다시 보도록 하겠습니다.

<template>
  <div>{{ count }} {{ object.foo }}</div>
</template>

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

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

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

현재는 setup 함수 내에 전부 선언 되었는데 이를 각 함수에 대해 나누어 처리하기 위해 분리해보도록 하겠습니다.

<template>
    <div>{{ count }} {{ object.foo }}</div>
</template>

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

    export default {
        setup() {
            const {count} = useCount();
            const {object} = useObject();
            
            return {
                count,
                object
            }
        }
    }

    function useCount(){
        const count = ref(0);
        console.log(count.value); // 0
        
        return {
            count
        }
    }

    function useObject(){
        const object = reactive({ foo: 'bar' });
        
        return {
            object
        }
    }
    
</script>

이런식으로 사용하면 각 값에 대해 완벽하게 나누어서 처리가 가능하고 무엇보다 다른 컴포넌트에서도 무리없이 재활용이 가능한 장점까지 존재합니다.! 또한 각 함수의 모든 값을 사용한다면 다음과 같이 사용해도 됩니다.

setup() {
    return {
        ...useCount(),
        ...useObject
    }
}

참고

Copyright © 2018 woolta.com

gommpo111@gmail.com