thumbnail
3_ Composition API Best Practices - Vue.js Amsterdam 2020
vue_discussion
2022.07.08.

Composition API Best Practices - Vue.js Amsterdam 2020

ref() vs. reactive() Which should i use?

image01


여러 코드들을 참조했을 때 ref를 사용하는 경우가 훨씬 많았다. (A lot of ref())
반면 reactive를 사용하는 경우는 드물었다. (Some lonely reactive())

때때로, reactiv()는 작동하지 않는다. (적어도 인체공학적으로 말이다.)
그래서 당신은 ref를 더 원한다.

개발자는 일관성을 중요하게 생각한다.

computed 속성은 refs이다.
아래 예시 코드를 보겠다.

export function exampleWithComputed() {
    const state = reactive({
        a: 1,
        b: 2,
        x: 2,
    })
    
    const sum = computed(() => state.a + state.b)
    
    const squared = computed(() => sum.value ** state.x)
    
    return toRefs({
        ...state,
        sum,
        squared,
    })
}

export function exampleWithRefs() {
    const a = ref(1)
    const b = ref(2)
    const x = ref(3)
    
    const sum = computed(() => a.value + b.value)
    
    const squared = computed(() => sum.value ** x.value)
    
    return toRefs({
        a,
        b,
        x,
        sum,
        squared,
    })
}

DOM 참조에는 (템플릿) Refs가 필요하다

<script>
    export default {
        setup() {
            const inputEl = ref<HTMLInputElement>(null)
            
            onMounted(() => {
                inputEl.value.addEventListener(/*  */)
            })

            return {
                inputEl,
            }
        }
    }
</script>

<template>
    <div>
        <input type="text" ref="inputEl">
    </div>
</template>

개발자가 일관성을 중요시 여긴다면 모든 곳에서 작동하는 ref를 선호할 것이다.
그렇다면.. reactive()는 필요없는 구문인가?

당연히 그렇지않다.
당신이 사용하기 원한다면, 사용하면된다.
하지만 난 별로 추천하진 않는다. ref가 더 나은 출발점이라고 생각한다.
하지만 이는 완전히 개인 취향 문제이다.
하지만 명심해야될건 refs를 완전히 피할수는 없다는 것이다. 당신은 많은 상황에서 이 구문을 사용해야될 것이다.

새로운 모범사례

리팩토링

export function myCompositionFunction(arg1, arg2) {
    // The magic happens here
    
    return {
        refs,
        objects,
        functions,
    }
}

인자에서 refs를 처리한다.

export function useWithRef(someFlag: Ref<boolean>) {
    watch(ref, val => {
        // do something
    })
    
    return {}
}

const isActive = ref(true);
const result = useWithRef(isActive)
const result2 = useWithRef(true) // 타입스크립트를 활용하지 않으면 이러한 오류를 범할 수 있다.

export function useWithRef(someFlag: Ref<boolean>) {
    if (!isRef(someFlag)) warn('Needs a ref'); // 에러 검사
    
    watch(ref, val => {
        // do something
    })
    
    return {}
}

const result = useWithRef(ref(true)) // 그런데 그렇다고 이렇게 넘기기에도.. 반응형 값이 필요 없는 경우도 있을텐데 말이다.
                               // 실제로 인자로 넘겨야하는 값이 반응형이 필요 없을때도 문제지만, 코드적으로 좀 안이쁘다.

refstatic 값을 모두 받아들일 수 있을까?

const wrap = (value) => (isRef(value) ? value : ref(value));

export function useEvent(
    el: Ref<Element> | Element,
    name: string,
    listener: EventListener,
    options?: boolean | AddEventListenerOptions
) {
    const element = wrap(el as Element) // wrap은 ref로 감싸는 함수이다.
    
    onMounted(() => element.value!.addEventListener(name, listener, options))
    
    onUnmounted(() => element.value!.removeEventListener(name, listener))
}

Forgiving API VS. Strict API

LifeCycle Hooks VS. Watch

const wrap = (value) => (isRef(value) ? value : ref(value));

export function useEvent(_el, name, listener, options) {
    const element = wrap(_el)
    
    onMounted(() => element.value.addEventListener(name, listener, options))
    
    onUnmounted(() =>  element.value.removeEventListener(name, listener))
}

mount시 ref가 비어있으면 어떻게될까?

나중에 ref가 바뀌면 어떻게될까?

이러한 문제는 watch가 해결해줄 것이다.

const wrap = (value) => (isRef(value) ? value : ref(value));

export function useEvent(_el, name, listener, options) {
    const element = wrap(_el)
    
    watch(element, (el, _, onCleanup) => {
        el && el.addEventListener(name, listener, options)
        
        onCleanup(() => el && el.removeEventListener(name, listener))
    })
}

위와 같이 하면 이벤트 리스너는 요소가 실제로 존재할 때만 추가된다.
ref 값이 바뀌면 리스너가 업데이트 된다.

return computed > return ref

createComponent({
    setup() {
        const isOnline = useOnline()
        
        reutnr {
            isOnline,
        }
    }
})

import { ref, computed, onUnmounted } from '@vue/composition-api';

export default function useOnline() {
    const isOnline = ref(true)
    
    isOnline.value = window.navigator ? window.navigator.onLine : true
    
    const onlineHandler = () => (isOnline.value = true)
    const offlineHandler = () => (isOnline.value = false)
    window.addEventListener('online', onlineHandler, false)
    window.addEventListener('offline', offlineHandler, false)
    
    onUnmounted(() => {
        window.removeEventListener('online', onlineHandler)
        window.removeEventListener('offline', offlineHandler)
    })
    
    // return isOnline // This ref is mutable! (이 참조는 변경 가능합니다!)
    // return computed(() => isOnline.value) // computed를 반환해 읽기권한만 부여한다.
    return readonly({ // 아니면 이렇게 읽기전용 오브젝트를 반환하던지..
        isOnline,
        a: 'A',
        b: 'B',
    })
}

NAME RETURNED PROPERTIES IN CONTEXT

export function useFullscreen(target: Ref<HTMLElement | null>) {
    const isFullscreen = ref(false)
    
    function exitFullscreen() {
        if (document.fullscreenElement) {
            document.exitFullscreen()
        }
        
        isFullscreen.value = false
    }
    
    async function enterFullscreen() {
        exitFullscreen();
        
        if (!target.value) return
        
        await target.value.requestFullscreen()
        isFullscreen.value = true
    }
    
    return {
        isFullscreen,
        enterFullscreen,
        exitFullscreen,
    }
}

createComponent({
    setup() {
        const el = ref<HTMLElement>(null)
        const fullscreen = useFullscreen(el)
        
        onMounted(() => fullscreen.enterFullscreen) // fullscreen 이라는 이름이 반복되는 것은 매우 불편한 일이다.
        
        return {
            el,
            fullscreen,
        }
    }
})

위 코드를 개선하면 아래와 같다.

export function useFullscreen(target: Ref<HTMLElement | null>) {
    const isActive = ref(false)
    
    function exit() {
        if (document.fullscreenElement) {
            document.exitFullscreen()
        }

        isActive.value = false
    }
    
    async function enter() {
        exit();
        
        if (!target.value) return
        
        await target.value.requestFullscreen()
        isActive.value = true
    }
    
    return {
        isActive,
        enter,
        exit,
    }
}

createComponent({
    setup() {
        const el = ref<HTMLElement>(null)
        // const fullscreen = useFullscreen(el)
        const { enter: enterFullscreen } = useFullscreen(el)

        // onMounted(() => fullscreen.enter) // 깔끔한 코드가 되었다.
        onMounted(enterFullscreen)
        
        return {
            el,
            fullscreen,
        }
    }
})
Thank You for Visiting My Blog, Have a Good Day 😆
© 2022 Developer hyungju-lee, Powered By Gatsby.