본문으로 건너뛰기
개발 머꼬
개발 노트Vue
hohyeon.dev17

자식에서 prop을 바꿨더니 props are readonly 경고가 뜬 이유

  • #Engineering Note
  • #Vue

문제 발생

자식 컴포넌트에서 prop을 바로 고쳤더니 콘솔에 경고가 떴습니다.

const props = defineProps(["modelValue", "size"]);
props.size = "large";   // ❌ 경고: props are readonly

배열 prop을 push했을 때는 경고 없이 동작했는데, 나중에 부모 쪽 화면이 이유 없이 바뀌는 문제가 생겼습니다.

원인 분석

Vue의 props는 단방향 데이터 흐름(One-Way Data Flow) 을 전제로 합니다. 부모가 바뀌면 자식으로 내려가지만 그 반대는 없습니다. 그래서 자식이 prop을 직접 바꾸면 Vue가 props는 읽기 전용이라고 경고합니다.

이유는 단순합니다. 자식이 prop을 고칠 수 있으면, 부모가 리렌더링될 때마다 그 값이 다시 덮어써집니다. 어느 쪽이 진짜 값인지 알 수 없게 됩니다.

객체·배열 prop이 경고 없이 바뀌는 건 예외가 아니라 막을 수 없어서입니다. 문서의 설명대로 자바스크립트에서 객체와 배열은 참조로 전달되므로, Vue가 모든 변경을 막는 것은 비용이 지나치게 큽니다. 다만 그런 변경은 자식이 부모 상태를 보이지 않게 바꾸는 것이라 데이터 흐름을 추적하기 어렵게 만듭니다. 문서는 부모와 자식이 아주 밀접하게 결합된 경우가 아니면 피하라고 권합니다.

해결 방안

공식 문서는 prop을 바꾸고 싶어지는 두 상황을 나누어 답을 제시합니다.

  1. 초기값으로만 쓰고 이후에는 자식이 관리하는 경우 → 지역 상태로 복사합니다.
const props = defineProps(["initialCounter"]);

// initialCounter는 초기값으로만 쓰이고,
// 이후 부모의 변경과는 연결되지 않습니다.
const counter = ref(props.initialCounter);
  1. prop을 가공해서 쓰는 경우 → computed로 만듭니다. 부모가 값을 바꾸면 자동으로 따라옵니다.
const props = defineProps(["size"]);
const normalizedSize = computed(() => props.size.trim().toLowerCase());
  1. 부모의 값을 실제로 바꿔야 하면 이벤트를 올립니다. 문서의 권고 그대로, 자식이 직접 고치는 대신 부모가 고치게 합니다.
<script setup>
const props = defineProps(["items"]);
const emit = defineEmits(["remove"]);
</script>

<template>
  <button @click="emit('remove', item.id)">삭제</button>
</template>
  1. 양방향이 정말 필요하면 v-model을 씁니다. 편법이 아니라 Vue가 정한 방식입니다. 3.4부터는 defineModel() 한 줄이면 됩니다.
const model = defineModel();
model.value = "large";   // 부모까지 반영됨
  1. 배열 prop을 자식이 정렬하지 않습니다. sort()reverse()는 원본을 바꾸는 메서드라 부모 상태를 조용히 흔듭니다. 사본을 만들어 씁니다.
const sorted = computed(() => [...props.items].sort(byName));

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.