1. 들어가며…
- PrimeVue(vue UI 라이브러리)에는 textarea의 높이를 자동으로 조정하는
autoResize
옵션이 있다. - 이 옵션을 사용하면, 텍스트 길이에 따라 textarea의 높이를 조절 할 수 있다. 그런데 만약
v-show
를 사용해 textarea를 렌더링할 경우,autoResize
가 작동하지 않는 현상이 발생한다.
- 아래는 primevue 코드를 참고해, autoResize만을 구현한 코드이다.
TextArea 코드 자세히 보기
<template>
<textarea
ref="textarea"
class="textarea"
:value="props.value"
@input="onInput"
></textarea>
</template>
<script setup lang="ts">
import { onMounted, onUpdated, ref } from "vue";
const props = defineProps({
value: {
type: String,
required: true,
},
});
const emit = defineEmits(["updated"]);
const autoResize = ref(true);
const textarea = ref<HTMLTextAreaElement | null>(null);
const resize = () => {
if (!textarea.value) return;
textarea.value.style.height = "auto";
textarea.value.style.height = textarea.value.scrollHeight + "px";
const isOverflow =
parseFloat(textarea.value.style.height) >=
parseFloat(textarea.value.style.maxHeight);
if (isOverflow) {
textarea.value.style.overflowY = "scroll";
textarea.value.style.height = textarea.value.style.maxHeight;
} else {
textarea.value.style.overflow = "hidden";
}
};
const onInput = (event) => {
emit("updated", event.target.value);
onResize();
};
onMounted(() => {
onResize();
});
onUpdated(() => {
onResize();
});
const onResize = () => {
if (autoResize.value) {
resize();
}
};
</script>
<style>
.textarea {
resize: none;
}
</style>
- 우선 코드를 간단히 설명하자면,
resize()
는 textArea의 높이를 조정하는 함수이다. - 이 함수는
onMounted
,onUpdated
훅에서 실행된다.
const resize = () => {
if (!textarea.value) return;
// (A)
textarea.value.style.height = 'auto';
// (B)
textarea.value.style.height = textarea.value.scrollHeight + 'px';
// (C)
const isOverflow = parseFloat(textarea.value.style.height) >= parseFloat(textarea.value.style.maxHeight);
if (isOverflow) {
textarea.value.style.height = textarea.value.style.maxHeight;
}
// (D)
textarea.value.style.overflowY = isOverflow ? 'scroll' : 'hidden';
};
코드 번호 | 설명 |
---|---|
(A) | 먼저 textarea.value.style.height 를 'auto'로 설정하여 높이를 초기화한다. |
(B) | scrollHeight 값을 기반으로 현재 텍스트가 차지하는 높이만큼 textarea.value.style.height 를 설정한다. |
(C) | maxHeight 값과 비교하여 높이가 초과하는지 확인한다. 만약 maxHeight 를 초과할 경우 overflowY 스타일을 'scroll'로 설정하여 스크롤이 나타나도록 하고, 높이를 maxHeight 에 맞춘다. |
(D) | 높이가 maxHeight 에 도달하지 않으면 overflowY 를 'hidden'으로 설정하여 스크롤을 제거한다. |
onInput
은textarea
에 입력할 때마다 호출되며, 입력 값을 업데이트하고 자동으로 크기를 조정한다.
const onInput = (event: Event) => {
// (E)
emit("updated", event.target.value);
// (F)
if (autoResize.value) {
resize();
}
};
코드 번호 | 설명 |
---|---|
(E) | event.target.value 을 emit을 사용해 이벤트를 전송한다. |
F) | autoResize 가 활성화되어 있으면 resize 함수를 호출하여 텍스트 입력에 따라 textarea 의 높이를 동적으로 조정한다. |
이처럼 textArea의 autoResize는 컴포넌트가 렌더링되거나 업데이트될 때,
콘텐츠 길이에 따라 scrollHeight를 textArea의 높이로 지정한다.
- 하지만
<TextArea>
에v-show
를 사용하는 방식에 따라 렌더링 문제가 발생한다 - 우선
<TextArea>
에 직접v-show
를 추가하면 autoResize가 정상 작동한다.
<TextArea v-show="toggle" :value="value" @updated="onInput" />
const toggle = ref(false);
const value = ref("");
const onClick = () => {
toggle.value = !toggle.value;
if (toggle.value) {
value.value = "hellllloooooooooooooooowwwwww";
}
};
const onInput = (v) => {
value.value = v;
}
- 그러나
<div>
요소로<TextArea>
를 감싸고,<div>
에v-show
를 적용하면autoResize
기능이 작동하지 않는다
<div v-show="toggle">
<TextArea :value="value" @updated="onInput" />
</div>
const toggle = ref(false);
const value = ref("");
const onClick = () => {
toggle.value = !toggle.value;
if (toggle.value) {
value.value = "hellllloooooooooooooooowwwwww";
}
};
const onInput = (v) => {
value.value = v;
}
- 그렇다면 왜 부모 요소에
v-show
를 적용하면 문제가 생기는 걸까? - 이번 시간에는
v-show
의 특성을 이해하고 문제가 발생하는 원인을 분석해 보았다.
2. v-show 선언 위치에 따라 문제가 발생하는 이유
1) v-show 이해하기
(1) v-show의 작동 방식
v-show
는 요소를 보이게 하거나 숨기기 위해 CSS의display
속성을 변경한다. 그래서v-show
를 사용하면 요소가 화면에 나타나거나 사라지게 할 수 있다.v-show
는 요소를 단지 눈에 보이지 않게 숨기기 때문에, DOM에는 여전히 남아있다.- 반면
v-if
는 조건에 따라 요소를 웹 페이지에서 완전히 없애거나 다시 추가한다. 따라서 렌더링 속도와 요소의 상태 관리 방식이v-show
와 다르다. - 다음 코드는
v-show
의 실제 구현 코드이다.
훅 | 동작 |
---|---|
beforeMount | 요소가 화면에 추가되기 전 단계이다. 만약 v-show 값이 false 라면, 화면이 렌더링되기 전에 요소를 미리 숨긴다. |
mounted | 요소가 실제로 화면에 나타날 때이다. 만약 애니메이션이나 전환 효과가 있다면 이 시점에 실행된다. 그래서 에서 v-show 디렉티브를 사용해도 애니메이션이 작동한다. |
updated | v-show 에 바인딩된 값이 변경되면 실행된다. 예를 들어 v-show 에 바인딩된 변수값이 false 에서 true 로 변경되면, updated 훅이 호출되어 요소가 화면에 보이게 된다. |
beforeUnmount | 요소가 DOM에서 제거되기 전 단계로, display 를 업데이트한다. |
보시다시피
v-show
는 초기 렌더링 시display
값을 저장된다.그리고 디렉티브에 바인딩된 값이
true
면display
값을 초기에 저장했던 값으로 변경하고, 값이false
라면display
를none
으로 변경한다.여기서 우리가 주의 깊게 봐야할 곳은 “updated”훅 실행 코드이다.
v-show
의updated
훅은 디렉티브가 지정된 컴포넌트에서만 실행된다.따라서
<textarea>
의 상위 요소에v-show
를 사용하면 상위 요소는 업데이트 되지만, 하위 요소인<textarea>
에서는onUpdated
훅이 실행되지 않는다.이 때문에
resize()
함수가 호출되지 않아 텍스트가 변경되어도<textarea>
의 높이가 조정되지 않는 문제가 발생한다.
2) 원인과 해결방법
(1) 문제: 상위 요소에 v-show를 사용하면 textArea 높이가 조정되지 않음
<div v-show="toggle">
<TextArea :value="value" />
</div>
- 부모
<div>
에v-show
를 사용하면, 그 안에 있는<TextArea>
의 높이가 자동으로 조절되지 않는다. - 즉, 텍스트를 입력해도 텍스트 상자의 크기가 늘어나지 않는다.
(2) 원인: updated 훅이 호출되지 않기 때문
- TextArea 컴포넌트는
onUpdated
라이프사이클 훅에서resize()
함수를 호출해 높이를 조정한다.
onUpdated(() => {
if (autoResize.value) {
resize();
}
});
- 하지만 부모 요소에
v-show
를 사용하면 TextArea의onUpdated
가 호출되지 않는다. - 이는
v-show
가 지정된 컴포넌트에서만updated
훅이 실행되기 때문이다. - TextArea 컴포넌트는 css상 숨겨지거나 보이게 되므로
onUpdated
훅이 호출되지 않고,resize()
함수도 실행되지 않다. - 결과적으로 내용이 변경되어도 높이가 제대로 조정되지 않는다.
(3) 해결방법: ResizeObserver 사용하기
ResizeObserver
는 요소의 크기가 변할 때 이를 감지할 수 있다.- 만약 부모요소에
v-show
가 적용된 경우 자식 요소(textarea)가 css상 사라졌다 보여지는데,ResizeObserver
가 그 변화를 감지하고 TextArea의 높이를 조정한다. - 다음은
ResizeObserver
를 사용해서 수정한 코드이다.
import { onMounted, onUnmounted } from 'vue';
let resizeObserver;
// (A)
onMounted(() => {
if (textarea.value && autoResize.value) {
resize();
resizeObserver = new ResizeObserver(resize);
resizeObserver.observe(textarea.value); // (B)
}
});
// (C)
onUnmounted(() => {
if (resizeObserver) {
resizeObserver.disconnect();
}
});
코드 번호 | 설명 |
---|---|
(A) | - 컴포넌트가 렌더링 때 ResizeObserver 를 생성하여 textarea 요소를 관찰한다. - resize() 함수가 바로 호출되어 초기 높이를 설정한다. - 이후 ResizeObserver 를 설정하여 textarea 요소의 크기 변화를 감시한다. |
(B) | - ResizeObserver 가 textarea의 크기 변경을 감시한다. - textarea의 크기가 변경될 때마다 resize() 함수를 실행하여 높이를 조정한다. |
(C) | - 컴포넌트가 페이지에서 제거되기 전에 ResizeObserver 가 더 이상 textarea를 감시하지 않도록 수정한다. |
ResizeObserver
를 사용하면 textarea의 크기가 변경될 때마다 높이를 자동으로 조정할 수 있다.
아래 gif는 문제가 해결된 모습이다.
수정된 TextArea 코드 자세히 보기
<template>
<textarea
ref="textarea"
class="textarea"
:value="props.value"
@input="onInput"
></textarea>
</template>
<script setup lang="ts">
import { onMounted, onUpdated, onUnmounted, ref } from "vue";
const props = defineProps({
value: {
type: String,
required: true,
},
});
const emit = defineEmits(["updated"]);
const autoResize = ref(true);
const textarea = ref<HTMLTextAreaElement | null>(null);
let resizeObserver;
const resize = () => {
if (!textarea.value) return;
textarea.value.style.height = "auto";
textarea.value.style.height = textarea.value.scrollHeight + "px";
const isOverflow =
parseFloat(textarea.value.style.height) >=
parseFloat(textarea.value.style.maxHeight);
if (isOverflow) {
textarea.value.style.overflowY = "scroll";
textarea.value.style.height = textarea.value.style.maxHeight;
} else {
textarea.value.style.overflow = "hidden";
}
};
const onInput = (event) => {
onResize();
emit("updated", event.target.value);
};
onMounted(() => {
if (textarea.value && autoResize.value) {
resize();
resizeObserver = new ResizeObserver(resize);
resizeObserver.observe(textarea.value);
}
});
onUnmounted(() => {
if (resizeObserver) {
resizeObserver.disconnect();
}
});
const onResize = () => {
if (autoResize.value) {
resize();
}
};
</script>
<style>
.textarea {
resize: none;
}
</style>
3) v-show 사용시 발생할 수 있는 다른 문제들
(1) 이벤트 리스너 제거 문제
v-show
를 사용할 때, 컴포넌트가 DOM에서 제거되지 않고 계속 유지되기에, 요소에 등록된 이벤트 리스너도 계속 유지된다.- 이렇게 되면 사용자가 보지 않는 상태에서도 이벤트 리스너가 메모리에 남아 있어 불필요한 자원을 소비하게 된다.
- 만약
v-show
상태에서 자식 컴포넌트의 이벤트 리스너가 계속 작동하게 되면, 예상치 못한 동작이나 성능 저하를 야기할 수 있다. - 이런 경우에는 필요하지 않은 이벤트 리스너를 적절히 관리하거나,
v-if
를 사용하는 것이 더 효율적일 수 있다.
(2) 애니메이션 및 트랜지션 문제
v-show
를 사용할 때 CSS 애니메이션이나 트랜지션을 추가하면 예상대로 작동하지 않을 수 있다.- 특히 요소가
display: none
으로 숨겨진 상태에서는 애니메이션이 동작하지 않으며, 요소를 다시 보여줄 때 애니메이션 효과가 생략될 수 있다. - 트랜지션 효과가 필요한 경우,
v-if
를 사용하여 컴포넌트를 새로 렌더링하거나, Vue에서 제공하는<transition>
컴포넌트를 함께 사용하면 좀 더 매끄러운 전환 효과를 구현할 수 있다.
(3) 스타일 및 레이아웃 재계산 문제
v-show
를 사용하여 요소를 숨긴 후 다시 보여주게 되면, 요소의 스타일과 레이아웃을 다시 계산해야 하는 문제가 발생할 수 있다.- 특히 복잡한 레이아웃의 경우, 이러한 재계산이 성능에 영향을 줄 수 있다.
- 또한, 레이아웃을 변경하는 스크립트가 특정 시점에 요소의 크기를 기반으로 계산을 진행하고 있었다면,
display: none
상태에서는 요소의 크기가0
으로 간주되므로 예상치 못한 결과가 나올 수 있다.
3. 마치며…
v-show
디렉티브는 요소를 숨기거나 보여주는 데 유용하지만, 그 동작 원리를 이해하는 게 중요하다. v-show
를 사용하면 updated
훅이 호출된다고 생각할 수 있지만, 실제로는 v-show
가 직접 적용된 컴포넌트에서만 updated
훅이 발생하며, 그 하위 요소들은 CSS를 통해 단순히 보이거나 숨겨질 뿐 훅이 동작하지 않는다.
따라서 이러한 점을 인지하고 상황에 맞게 v-if
, ResizeObserver
을 적절히 활용하면 보다 나은 컴포넌트를 구현할 수 있을 것이다 🙂
'개발 기술 > 사소하지만 놓치기 쉬운 개발 지식' 카테고리의 다른 글
웹사이트를 최적화시키는 3가지 기법: 코드 압축, 경량화, 난독화 (4) | 2024.11.18 |
---|---|
모바일에서 연속 입력시 값이 무시되는 이유(feat. click, touch, pointer event) (4) | 2024.10.19 |
iOS 모바일의 보안과 이벤트 유실(feat. 사용자 활성화, 이벤트 루프) (20) | 2024.10.03 |
[CSS/JS] ellipsis 말줄임에서 발생할 수 있는 2가지 인터렉션 문제 (0) | 2024.09.19 |
[JS] event.target vs event.currentTarget의 차이 (with. vue slot) (0) | 2024.09.08 |
댓글