withDefaults() infers wrong type when using a default value factory function
Version
3.2.6
Reproduction link
github.com - vue3-issue-default-function-prop
Steps to reproduce
I have the following functions and the same type definitions in service.ts
, This function simply returns a string:
export const defaultFunc = function(): string {
return 'result'
}
export type DefaultFunc = () => string
Then import them in a setup script
and set props, defaults using defineProps
and withDefault
:
<script setup lang="ts">
import { defaultFunc } from './service';
import type { DefaultFunc } from './service';
interface Props {
fetchData?: DefaultFunc
}
// set default function prop
const props = withDefaults(defineProps<Props>(), {
fetchData: defaultFunc
});
// print default prop function value
console.log('======> default prop fetchData:', props.fetchData);
</script>
call this component and does not pass the prop fetchData
, then check the console output of the web page.
What is expected?
Then console print the function itself:
======> default prop fetchData: ƒ () {
return "result";
}
What is actually happening?
the console prints the return value of the default function, not the function itself.
======> default prop fetchData: result
How to resolve the issue
Passing in the function when the component is called.
Write directly to the type of the default function instead import it:
<script setup lang="ts">
import { defaultFunc } from './service';
interface Props {
fetchData?: () => string // <=============== look here
}
const props = withDefaults(defineProps<Props>(), {
fetchData: defaultFunc
});
console.log('======> default prop fetchData:', props.fetchData);
</script>
Then console will print the function itself correctly:
======> default prop fetchData: ƒ () {
return "result";
}
Other ways to trigger the issue
Use typeof
to get the function type and use it in the props interface:
<script setup lang="ts">
import { defaultFunc } from './service';
interface Props {
fetchData?: typeof defaultFunc // <=============== look here
}
const props = withDefaults(defineProps<Props>(), {
fetchData: defaultFunc
});
console.log('======> default prop fetchData:', props.fetchData);
</script>
More information
The default prop function in the
<template>
correctly returns the function itself.The same issue occurs when the default prop function return a
Promise
.Here is my
npx envinfo --binaries --system
:
System:
OS: Windows 10 10.0.18363
CPU: (8) x64 Intel(R) Core(TM) i5-1035G1 CPU @ 1.00GHz
Memory: 6.18 GB / 15.77 GB
Binaries:
Node: 12.16.1 - C:\Program Files\nodejs\node.EXE
Yarn: 1.22.10 - C:\Program Files\nodejs\yarn.CMD
npm: 6.13.4 - C:\Program Files\nodejs\npm.CMD
- the example above was created using
yarn create vite
with typescript.
This is expected and not related to withDefault()
specifically.
Functions as default value will be executed to get the actual default value. This is useful/needed when the default value should be an fresh object for each component instance, for example.
The solution is to wrap the function in another function:
const props = withDefaults(defineProps<Props>(), {
fetchData: () => defaultFunc
});
Though I can't test it right now and verify that types like that solution as well ...
Ok could test it. Types don't like it.
As a temporary workaround you can typecast it like so:
const props = withDefaults(defineProps<Props>(), {
fetchData: (() => defaultFunc) as unknown as DefaultFunc
});
Thanks for your quick answer!
In general, this truly useful, when I need a string[]
prop, ts will prompt me to set a function for default as a factory.
interface Props {
tags: string[]
}
const props = withDefaults(defineProps<Props>(), {
tags: () => []
});
Along this line of thinking, I think withDefaults
should prompt me to provide a function as the "function factory". But as you can see, withDefaults
wants me to provide a function with exactly same type.
And strangely, when I used the import DefaultFunc
type in props interafce, default function was executed.
import type { DefaultFunc } from './service';
interface Props {
fetchData?: DefaultFunc
}
// props.fetchData print "result" on console
But when I replaced DefaultFunc
with type () => string
, the function was not executed.
interface Props {
fetchData?: () => string
}
// props.fetchData print f() { return "result" } on console
I can't find difference between them.
Functions as default value will be executed to get the actual default value.
In fact,when prop type is Function,vue will not call the default value,see code.
interface Props {
fetchData?: () => string
}
// set default function prop
const props = withDefaults(defineProps<Props>(), {
fetchData: () => '1'
});
The type of fectchData is Function
.So the () => '1'
will not be called.
I can't find difference between them.
import type { DefaultFunc } from './service';
interface Props {
fetchData?: DefaultFunc
}
This is because In the condition, sfc-compiler can not generate the right type,see code
In the output js code.You can find that the type of fetchData
is null
rather than Function
compiler-sfc infers the runtime prop type from your TS Props
interface. When you use an imported type, compiler-sfc currently doesn't crawl external files and thus does not have the type information from the external. So it can only generate a loose runtime type - and because the runtime type is not Function
, the default value will be treated as a factory instead of an actual value.
This is currently a known limitation, the workaround is to prefer writing props types in the same file instead of importing it.