junglast
Published on

vue3에서 props의 reactivity - props에 항상 toRefs를 사용해야 할까?

vue3에서 props로 넘어온 값을 다룰 때, 해당 값을 reactivity(반응성)을 유지하고자 하는 의도로 아래와 같이 습관적으로 toRefs, toRef, 혹은 computed를 사용했습니다.

const { id, tags, content } = toRefs(props)
const id = toRef(props, "id")
const id = computed(() => props.id)

하지만, toRefsreactive 등을 이용하지 않고도 reactive하게 동작하는 경우가 있었습니다. 따라서, vue3가 props를 처리하는 방법에 대해 알아보고 toRefs가 반드시 필요한 경우에 대해 정리해보았습니다.

다음과 같은 형태의 props를 받는 컴포넌트가 있다고 가정해 봅시다. 그리고 props를 사용하는 몇 가지 케이스를 살펴보겠습니다.

const props = defineProps<{
  id: string
  tags: string[]
  content: {
    title: string
    body: string
  }
}>()

props를 사용하는 예시

1. props의 값을 다른 변수에 재할당하는 경우

다음과 같이 props의 값을 다른 변수에 할당하는 경우가 있을 수 있습니다.

const articleId = props.id

이 경우 articleId는 reactive하게 동작하지 않습니다. 즉, props의 id의 값이 변경되어도 articleId 값은 변경되지 않습니다. 따라서, 이 경우 아래 2번과 같이 props.id 형태로 그대로 사용하거나, toRef를 사용하는 방법, 혹은 computed를 사용하여 reactivity를 유지하는 방법이 있습니다.

const articleId = toRef(props, "id")
const articleId = computed(() => props.id)

물론 아래와 같이 구조 분해 할당을 이용하는 경우에도 동일하게 reactivity가 유지되지 않습니다.

const { id } = props

위 표현은 const id = props.id와 동일하기 때문입니다.

2. props의 값을 재할당하지 않고 그대로 사용하는 경우

<div>id: {{ props.id }}</div>

이 경우 articleId는 reactive하게 동작합니다. 즉, props의 id의 값이 변경될 때마다 div에 렌더링 되는 값도 달라집니다.

3. props의 값을 다른 변수에 재할당하는데, 이 값이 객체인 경우

위에서 설정한 props 중, 객체인 content를 1번과 같이 다른 변수에 할당하는 경우가 있을 수 있습니다.

const articleContent = props.content

이 경우 articleContent는 reactive하게 동작합니다. 즉, props의 content의 값이 변경될 때마다 articleContent 값도 변경됩니다. 게다가, articleContent.title이나 articleContent.body와 같이 내부의 값도 별도 변수에 또다시 할당하지 않는 이상 reactive하게 동작합니다.

1번과 동일한 방식으로 값을 사용하고 있지만, 결과는 다릅니다.

문제의 원인: vue3가 props를 처리하는 방법

왜 위와 같은 차이가 발생하는 것일까요? 이는 vue3가 reactivity를 처리하는 방식과 관련이 있습니다. vue3는 내부적으로 자바스크립트 Proxy를 이용해 모든 reactivity를 처리합니다. props 역시 Proxy로 처리되기에, props 내부의 값에 변경이 있을 때마다 vue의 런타임이 이를 알 수 있습니다.

하지만 여기서 한 가지 특이한 동작은, vue는 재귀적으로 객체 내부의 모든 객체를 Proxy로 처리한다는 점입니다. 즉, Proxy 객체 내부의 값 중 또다른 객체가 있다면 (위 props의 예시 중 props.content) 이 또한 Proxy로 처리됩니다.

이 동작 방식에 따라 위 1~3번을 다시 살펴보면 다음과 같습니다.

1. props의 값을 다른 변수에 재할당하는 경우

const articleId = props.id

위 상황에서, props는 Proxy이지만 props.id는 원시 값인 string입니다. 따라서 articleId에는 단순 string 값이 복사될 뿐입니다. 따라서 props에 변화가 일어나도 articleId는 이를 알 수 없습니다.

2. props의 값을 재할당하지 않고 그대로 사용하는 경우

따라서, props.id와 같이 props 객체를 직접 사용하여 내부의 값에 접근하는 경우에는 reactivity를 유지할 수 있습니다. vue가 props가 변경되었음을 알 수 있기 때문입니다.

3. props의 값을 다른 변수에 재할당하는데, 이 값이 객체인 경우

vue3에서 reactivity를 처리하는 방식 때문에, props의 값 중 하나인 content 역시 재귀적으로 Proxy로 처리됩니다. 따라서, 아래와 같이 변수를 설정할 때에는 articleContent 역시 props.content와 같은 Proxy 객체를 가리킵니다. 따라서 props.content의 값이 변경되면 당연히 articleContent도 이를 알 수 있습니다.

const articleContent = props.content

따라서 articleContent.title이나 articleContent.body와 같이 사용해도, reactivity를 유지할 수 있습니다.

물론 1번 사례와 같이 const title = articleContent.title과 같이 내부의 값을 또다시 다른 변수에 할당하는 경우에는 reactivity를 유지할 수 없을 것입니다.

요약

  • props나 다른 reactive한 객체를 다룰 때, 이 객체가 가지고 있는 원시 값을 다른 변수에 할당한다면 reactivity를 잃게 되므로, 다른 방식(toRef, computed 등)을 사용해야 합니다.
  • 하지만, 한 reactive한 객체 내부의 객체를 다른 변수에 할당한다면, 이 객체 또한 Proxy로 처리되어 reactivity를 유지할 수 있습니다.

Reference