[Vue 04] Vue slot

예준천

목표

자식스코프에서 slot에 많은 사항을 접근하기.
1.
<slot>에 가변적으로 엘리먼트를 추가.
2.
자식컴포넌트에서는 각각의 엘리먼트의 validation함수를 수행 후 경고 또는 safe 표현
3.
<slot>에 있는 각각의 데이터를 수집가능해야함.

vue slot

vue slot 자식 스코프 접근

다행히도 vue에서는 자식 스코프의 변수에 접근할 수 있는 기능이 있다. 찾아보니 반응성도 유지되고 수정도 가능한 값이라고 한다.

1차목표 : 데이터 모으기

custom form을 만드려는 첫번째 이유인 데이터 모으기는 성공했다.
form 컴포넌트에 반응형 배열을 하나 만들고, v-slot으로 부모로 넘긴다음, 각각의 input에 v-model로 매핑해준다. 그러면 input에서 변한 값은 부모 스코프의 변수가 아니라 form 스코프에서 관리할 수 있다.
<script setup>
import child from './child';
</script>
<template>

<child v-slot="slotProps" >
    <input v-model="slotProps.data[0]"/>
    <input v-model="slotProps.data[1]"/>
    <input v-model="slotProps.data[2]"/>
    <input v-model="slotProps.data[3]"/>
</child>

</template>

<style scoped>
</style>
<script setup>
    import {ref} from 'vue';
    const data = ref([]);
</script>
<template>
    <slot :data="data">
    </slot>
</template>
<style scoped>
</style>

2차목표 : validation도 한군데서

사실 데이터를 한곳에 모은 이유가 validation을 한곳에서 하기위해서였다. 가장 먼저 생각이 난 방법은 form 컴포넌트에 props로 validation함수배열을 넘겨주고, 순서대로 하나씩 하는거였다.
<script setup>
import child from './child';
const idValid // ...
const pwValid // ...
const nameValid // ...
const birthValid // ...
</script>
<template>

<child v-slot="slotProps" :validationArray="[idValid,pwValid,nameValid,birthValid]">
    <input v-model="slotProps.data[0]"/>
    <input v-model="slotProps.data[1]"/>
    <input v-model="slotProps.data[2]"/>
    <input v-model="slotProps.data[3]"/>
</child>

</template>

<style scoped>
</style>
<script setup lang="ts">
    import {ref} from 'vue';
    const data = ref([]);
    const result = ref();
    const props = defineProps<{validationArr: Array<()=>boolean>}>();
    const validAll = ()=>{
        result.value = props.validationArr.some((validation,idx) => !validation(data[idx]));
    }
</script>
<template>
    <slot :data="data">
    </slot>
    <button @click="validAll">값 검증하기</button>
</template>
<style scoped>
</style>
장점 : 구현이 간단하다. (데이터가 form에 모여있어서 validation함수에서 사용하기 편하다.)
단점 : validation함수가 부모컴포넌트에 있어야한다. 순서를 실수하면 안된다.

각각의 input을 분리

아니면 각각의 input태그들을 내용과 validation로직이 함께 있는 컴포넌트를 만들고, 그걸 모아놓은 form태그에서 일괄적으로 validation을 실행시킬 수 있으면 어떨까? 이게 가능하려면 부모컴포넌트에서 자식 컴포넌트의 메서드를 실행시킬 수 있어야한다.
script setup방식에서는 expose를 이용해서 외부에 노출시킬 변수와 메서드를 선언하고, 해당 컴포넌트ref.value로 그 값에 접근 할 수 있다.
<script setup>
import child from './child';
</script>
<template>

<child v-slot="slotProps">
    <customInput v-model="slotProps.data[0].value" ref="slotProps.data[0].refAnchor"/>
</child>

</template>

<style scoped>
</style>
<script setup>
    import {ref} from 'vue';
    const data = ref( Array.from({ length: 60 }, () => ({value:ref('') , refAnchor: ref(null) }) ));
    const validate = () =>{
        data.forEach((input) =>{
            input.refAnchor.value.validation(input.value);
        });
    };
</script>
<template>
    <slot :data="data">
    </slot>
</template>
<style scoped>
</style>
<script setup lang="ts">

const props = defineProps<{value:string}>();
const emits = defineEmits<{(event:'update:value',value:string):void}>();

const validation = () => {
    return true;
}
defineExpose({validation});
</script>
<template>
    <input :value="props.value" @input="$event.target.value"/>
</template>
<style scoped>
</style>

이상

<script setup>
import child from './child';
const data  =ref();
</script>
<template>
<child v-model:data="data">
  <customInput />
    <customInput />
    <customInput />
    <customInput />
    <customInput />
</child>
</template>
<style scoped>
</style>
<script setup>
    import {ref} from 'vue';
    const data = ref( Array.from({ length: 60 }, () => ({value:'' , refAnchor: ref() }) ));

// 1. 현재 부모 컴포넌트에 마운트된 자식 vnode를 확인한다.
// 2. 자식 vnode들에 대해 값 가져오기 & validation 메소드 가져오기 
</script>
<template>
    <slot :data="data">

    </slot>
</template>
<style scoped>
</style>
<script setup lang="ts">
inteface passwordInputProps{
    data
}
inteface passwordInputEmits{
    (event:'update:value',value:string):void;
    (event:'isInvalid',isInvalid:boolean):void;
}
const props = defineProps<passwordInputProps>();
const emits = defineEmits<passwordInputEmits>();
</script>
<template>
    <input :value ="props."
</template>
<style scoped>
</style>
부모에서 customForm을 사용하는 방법을 조금 더 간소화 시켜보면 어떨까. 직접 ref연결하고 data 매핑하는 과정이 너무 귀찮다. 그냥 customForm 컴포넌트에서 자신에게 들어온 slot들의 정보를 불러와서, 각각의 customInput에 대해 script setup부에 정의된 ref랑 validation 메소드를 가져와서 실행시키면 정말 좋을 것 같다.
아쉽게도 불가능하다. 왜냐하면 useSlots()가 반환하는 건 slot에 있는 컴포넌트들의 vNode이다. VUE3 에서는 VNode로 컴포넌트 인스턴스에 접근할 수 없다. (사실 component 라는 속성에 들어있다는데, 나는 왜 안나오는지 모르겠다.)
VUE3 <script setup>방식에서는 현재 컴포넌트의 인스턴스를 얻으려면 getCurrentInstance()를 이용하고,(출처 : https://github.com/vuejs/core/discussions/9092)
다른 컴포넌트의 인스턴스를 얻기 위해서는 해당 컴포넌트에 ref를 달고, ref.value로 가져오면 된다. 만약 가져오고자 하는 componentInstance<script setup>방식으로 정의된 경우에는 기본적으로는 모든 데이터는 다른 컴포넌트에 대하여 비공개이기 때문에, 사용하고자 하는 변수나 메소드를 expose해줘야한다.
암튼 그렇더라.
Yejun Cheon
Subscribe to 'Yejun Cheon'
Subscribe to my site to be the first to receive notifications and emails about the latest updates, including new posts.
Join Slashpage and subscribe to 'Yejun Cheon'!
Subscribe
👍