개발 기술/개발 이야기

[Vue] 컴포저블에서 props의 반응성 유지하기(feat. toRef, unref)

by GicoMomg (Lux) 2023. 10. 29.

1. props를 인자로 넘기면 반응성을 잃는다?

  • vue에서는 컴포저블을 사용하여 비즈니스 로직을 분리할 수 있다.
  • 또한, 컴포저블의 인자로 반응형 변수를 전달할 수도 있다.
  • 그러나, 반응형 변수를 컴포저블 인자로 전달하면 반응성을 잃는 경우가 발생한다.
  • 아래 코드는 사용자 데이터를 관리하는 useUser 컴포저블이다.
  • 이 컴포저블은 id값을 인자로 받아서, id값이 변경될 때 어떤 처리를 한다.
export function useUser(id) {
  watch(id, () => {
        // 어떤 처리
    });
}

  • computedref, reactive로 선언된 변수를 컴포저블의 인자로 전달하면, 인자는 반응성을 유지한다.
const userId = computed(() => ...)

useUser(userId);  // this!

  • 그런데! props 변수를 인자로 넘기면, 해당 인자는 반응성을 잃게 된다!
const props = defineProps({
  userId: { 
    type: Number,
        required: true
  }
})

useUser(props.userId);  // this!

props는 분명히 반응형 변수인데, props를 컴포저블의 인자로 전달하면 왜 반응성을 잃게 되는 걸까?

  • 그 이유는 props의 값 접근 방식 때문이다!
  • 이번 시간에는 props 변수의 특징과 함께, props의 반응성을 유지하는 두 가지 방법을 알아본다.



2. props의 반응성을 유지하는 법

1) props의 값 접근 방식이 반응성을 잃게 한다?

  • Vue에서 반응형 변수를 다루는 방법으로는 prop, ref, reactive, computed이 있다.
  • ref, reactive, computed.value 형태로 값에 접근할 수 있다.
  • 또한, .value 대신, 변수 자체를 호출하면 반응형 객체에 접근할 수 있다.
const data1 = ref('안녕');
const data2 = reactive({ id: 1 });
const data3 = computed(() => 'hello');

// 값 접근 방법 ✅
console.log(data1.value);     // 안녕
console.log(data2.value);     // { id: 1 }
console.log(data3.value);     // hello

// 반응형 객체 접근 ✅
console.log(data1);  
console.log(data2);  
console.log(data3);  

  • 아래 이미지는 반응형 객체의 예시이다.


  • 그에 비해 props는 props.XXX 형태로 값에 접근해야 한다.
  • 앞선 예시처럼, 변수 자체를 호출해도 반응형 객체에 접근할 수 없다.
const props = defineProps({
  data: { 
    type: Number,
        required: true
  }
})

// 값 접근 방법 ✅
console.log(props.data);   

// 반응형 객체 접근 ❌
console.log(props.data); 

  • props는 변수 자체를 호출하면, 반응형 객체가 아닌 값에 접근한다.
  • 이러한 이유로, 컴포저블에 props 변수를 인자로 전달하면, 인자가 반응성을 잃게 된다.

2) props의 반응성을 지키는 두 가지 방법

📌 그럼 props 변수를 컴포저블의 인자로 전달할 때, 반응성을 유지할 방법은 없을까?
두 가지 방법이 있다! 하나는 getter 함수를 사용하는 것이고, 다른 하나는 toRef()를 사용하는 것이다.

(1) props를 getter 함수로 선언하기

  • watch에서 props를 감시하고 싶을 때, props를 어떻게 전달해야할까?
  • 바로 "props 값을 반환하는 getter 함수" 형태로 전달하는 것이다!
  • props를 getter함수 방식으로 선언하면, 반응형 변수처럼 사용할 수 있다.
watch(
    () => props.data, // this!
    () => { ... }
)

  • 이 getter 함수 방식은 컴포저블에 props를 인자로 전달할 때 사용할 수 있다.
  • getter 함수 형태로 props값을 넘기면, 우리가 의도한 대로 값의 반응성을 유지할 수 있다.
const props = defineProps({
  userId: { 
    type: Number,
        required: true
  }
})

useUser(() => props.userId);  // this!

(2) toRef 사용하기

  • 두 번째는 toRef()를 사용하는 방법이다.
  • 공식문서에서 toRef() 설명을 보면, “컴포저블에 prop를 인자로 넘길 때 toRef()를 사용하면 반응성을 지킬 수 있다“고 되어 있다.


  • 용법은 간단한데, Vue 3.3 전후에 따라 사용 방법이 달리지는 것만 유의하자.
const props = defineProps({
  userId: { 
    type: Number,
        required: true
  }
})

useUser(toRef(props, 'userId'));     // 3.3 버전 이전일 때
useUser(toRef(() => props.userId));  // 3.3 버전 이상일 때

🙄 잠깐! 사용처에서 toRef 방식으로 props를 컴포저블 인자로 전달하지 않고, 컴포저블에서 항상 반응형 변수임을 보장할 수는 없을까요? unref()를 사용하면 가능하다!

  • unref()는 말 그대로, 반응형 변수에서 값을 뽑아내는 함수이다.
  • 만약 반응형 변수가 아니면 그 값 자체를 리턴해준다.
const props = defineProps({
  userId: { 
    type: Number,
        default: 12,
  }
})

const data1 = ref('안녕');

console.log(unref(props.userId)); // 12
console.log(unref(data1));        // 안녕

  • unref()의 특성을 이용하여, 인자를 항상 unref() 처리하고 이 값을 computed로 재선언하면 항상 반응형 변수임을 보장할 수 있다.
export function useUser(id) {
  const userIdRef = compunted(() => unref(id)); 

  watch(id, () => {
        // 어떤 처리
    });
}



3. 마치며…

  • 이번 시간에는 컴포저블에 props를 인자로 넘길 때, 반응성을 유지하는 방법을 알아보았다.
  • 첫 번째 방법은 props를 반환하는 getter 함수로 선언하는 것이고, 두 번째는 toRef를 사용하는 방법이다.
  • 만약 컴포저블에서 인자가 반응형이 되도록 보장하고 싶다면, unrefcomputed로 인자를 반응형 변수로 재선언하는 방법도 있다.
반응형

댓글