<template>
<dialog open>
<slot></slot>
</dialog>
</template>
<style>
dialog {
...
}
</style>
<template>
<h2>Manage Goals</h2>
<input type="text" ref="goal">
<button @click="setGoal">Set Goal</button>
<error-alert v-if="inputIsInValid">
<h2>Input is Invalid</h2>
<button @click="confirmError">OK</button>
</error-alert>
</template>
<script>
import ErrorAlert from "@/components/ErrorAlert.vue";
export default {
components: {ErrorAlert},
data(){
return{
inputIsInValid:false
}
},
methods:{
setGoal(){
const enteredValue = this.$refs.goal.value
if(enteredValue === ""){
this.inputIsInValid=true
}
},
confirmError(){
this.inputIsInValid=false
}
}
}
</script>
가장 윗줄부터 input은 v-model을 써도 되지만 그것이 주가 아니기에 편한 ref를 사용하여 처리하였다.
button들의 @click 메서드들은 data prop의 input is invalid를 true, false 바꿔주는 것으로 만들었다.
커스텀 컴포넌트는 v-if 속성을 사용하여 유효성에 의존성을 걸어두었다.
자 여기까기 구현을 간단하게 하였는데 무엇이 문제일까? 바로 웹 표준이다.
보면 h2, input, button, dialog 태그가 모두 같은 위치에 있는데 이는 시맨틱 관점과 HTML 관점에서 다이얼로그 요소를 이 위치에 놓는 것은 다이얼로그가 전체 페이지에 오버레이된다는 문제가 있다.
또 이 다이얼로그를 여기에 놓는 것은 HTML 측면에서 이상적이지 않다. 작동은 하지만 접근성을 생각해보면 이상하다.
또한 스타일링 문제가 발생할 수도 있다.
따라서 적합한 위치는 DOM Tree 의 root node에 위치하는 것이 적합하다고 볼 수 있다. 그러다면 어떻게 할까?
...
<teleport to="body">
<error-alert v-if="inputIsInValid">
<h2>Input is Invalid</h2>
<button @click="confirmError">OK</button>
</error-alert>
</teleport>
...
텔레포트는 to라는 한 가지 속성을 필요로 한다. 여기에 CSS 선택자를 넣어서 전체 페이지에서 HTML 요소를 선택한다.
하지만 논리상 여전히 해당 컴포넌트에 속하며 사용된 컴포넌트에서 methods와 상호작용할 수 있지만 DOM 구조 내 다른 곳에서 렌더링 된다.
react에서 보던 그아이가 맞고 Vue2 까지만 해도 react와 동일하게 1개의 element만 반환했어야했다.
그런데 Vue3로 넘어오고서 지금은 원하는 만큼 많은 상위 레벨의 요소를 가질 수 있다.
시맨틱 또는 스타일링 목적을 제외하면 하나만 유지할 필요가 없다.
이 기능을 Fragments라고 한다.
이제는 Fragment를 통해 복수의 상위 레벨 요소를 가질 수 있다.