-
-
Notifications
You must be signed in to change notification settings - Fork 506
Description
from #146
Goal
When we are writing complex templates we often have v-if that will do type narrowing on for the children, those type narrowings are really useful and is a pain to need to cast inside of the @event possibly causing bugs down the line (if a v-if is changed, etc)
// simple example
<template>
<div v-if="test" @click="(test as number).toString()">
</template>
<script setup lang=ts>
declare const test: number | undefined;
</script>
// more complex example
<template>
<div v-if="'a' in test" @click="(test as {a: string; aa: string}).aa.toString()"></div>
<div v-else-if="test" @click="(test as {b: string; bb: string})..bb.toString()"></div>
</template>
<script lang="ts" setup>
let test: { a: string, aa: string } | { b: string, bb: string } | undefined;
</script>As you can see, you need to cast it to be able to work, which is a pain, good luck inferring types correctly from third-party or nested types...
Solution
To solve this @events functions should have the same type narrowing than the render component.
This makes sense in vue because events are not fired/emitted after a component is disposed (which happens on v-if=false).
Philosophy
At first sight we can blame this to typescript, because variable can change to other types, but in vue a component is not render if the v-if condition is false, making the scope of the event pretty much the scope after the v-if (only when using prop function that might introduce a error)
Implementation
We can implement this by following this 3 rules:
- add all the parent v-if and negate them in order if(!(BRANCH_CONDITION) return
- If there's branches, add if(BRANCH_V_IF_CONDITION) return
- add the @click code after the conditions
Example:
<div v-if="test" @click="test .toString()">
if(test) {
<div onClick={$event=> {
if(!(test)) return
test.toString() // safely a number here
})
} <div v-if="'a' in test" @click="(test as {a: string; aa: string}).aa.toString()"></div>
<div v-else-if="test" @click="(test as {b: string; bb: string})..bb.toString()"></div>
if (test && 'a' in test) {
h('div', {
onclick() {
if (!(test && 'a' in test)) return
test.aa.toString()
}
})
} else if (test) {
h('div', {
onClick() {
// DO NOT NEGATE previous branches
if ((test && 'a' in test)) return
// NEGATE Current Branch
if (!(test)) return;
test.bb.toString()
}
})
}