
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?
여러 코드들을 참조했을 때 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)) // 그런데 그렇다고 이렇게 넘기기에도.. 반응형 값이 필요 없는 경우도 있을텐데 말이다.
// 실제로 인자로 넘겨야하는 값이 반응형이 필요 없을때도 문제지만, 코드적으로 좀 안이쁘다.
ref및static값을 모두 받아들일 수 있을까?
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,
}
}
})