Open kjleitz opened 5 years ago
I tried adding the test case to the project and couldn't reproduce:
Vue.extend({
props: {
isValid: {
type: Function,
default: () => true,
}
},
methods: {
useFooFn(): void {
const bar = this.isValid()
alert(bar)
}
}
});
@posva Are you using the same TypeScript setup and seeing no compile errors?
no, I'm using the one we have in the repo
Can you try with the setup I posted?
@posva
Found a clue:
I just downgraded vue
(and vue-template-compiler
) to 2.5.17 and it works fine, as it used to. Then I upgraded both to 2.5.18, and now I see a bunch of compiler errors (including this one) which had never occurred before:
// for Function-type props with a default like `() => false`, `(arg: number) => false`, etc.:
// => TS2349: Cannot invoke an expression whose type lacks a call signature. Type 'boolean | Function' has no compatible call signatures.
// (same error occurs with any return value)
function foo(barEl: HTMLElement) { /* ... */ }
foo(this.$el);
// => TS2345: Argument of type 'Element' is not assignable to parameter of type 'HTMLElement'.
const bar = this.$el.innerText;
// => TS2339: Property 'innerText' does not exist on type 'Element'.
Seems that:
Function
or the return type of their default
functionthis.$el
has become Element
instead of HTMLElement
vue
/vue-template-compiler
2.5.18+, but not 2.5.17~It may have something to do with "noImplicitThis": true
in tsconfig.json
; setting it to false
removes all those errors (there is one new error in our project after setting it to false
but it's to do with a lack of type inference on this.$store
, all in one component rather than scattered across the project)~
Reverting https://github.com/vuejs/vue/pull/8537, specifically this change:
diff --git a/types/options.d.ts b/types/options.d.ts
index cc58affe6a..25eb8a0fdf 100644
--- a/types/options.d.ts
+++ b/types/options.d.ts
@@ -133,7 +133,7 @@ export type PropValidator<T> = PropOptions<T> | Prop<T> | Prop<T>[];
export interface PropOptions<T=any> {
type?: Prop<T> | Prop<T>[];
required?: boolean;
- default?: T | null | undefined | (() => object);
+ default?: T | null | undefined | (() => T | null | undefined);
validator?(value: T): boolean;
}
...fixes the Function
-type prop issue.
And reverting https://github.com/vuejs/vue/pull/8809, specifically this change:
diff --git a/types/vue.d.ts b/types/vue.d.ts
index 44a892ead3..3832f2c9e4 100644
--- a/types/vue.d.ts
+++ b/types/vue.d.ts
@@ -21,7 +21,7 @@ export interface CreateElement {
}
export interface Vue {
- readonly $el: HTMLElement;
+ readonly $el: Element;
readonly $options: ComponentOptions<Vue>;
readonly $parent: Vue;
readonly $root: Vue;
...fixes the this.$el
defaulting to Element
issue.
I'll see if I can write up a PR for a fix without resurfacing the original issues those PRs were trying to solve.
Aaaaand I've realized over the past few days that I am not good enough with TypeScript to figure out how to do this.
Where...
"noImplicitThis": true
in tsconfig.json
, and...in order to get a prop definition such as this:
// ...
props: {
isValid: {
type: Function,
default: () => true,
}
},
// ...
...to yield a type of:
this.isValid //=> Type: () => boolean
...instead of:
this.isValid //=> Type: boolean | () => boolean
You'd have to edit the PropOptions
interface such that:
export interface PropOptions<T=any> {
type?: Prop<T> | Prop<T>[];
required?: boolean;
// default?: T | null | undefined | (() => T | null | undefined);
// I guess...?
default?: Function extends T ? (T | null | undefined) : (T | null | undefined | (() => T | null | undefined));
validator?(value: T): boolean;
}
Unfortunately, that example doesn't work, and the types of other properties on Vue
are lost. I've tried a lot of different things over the past few days, but clearly I don't have the expertise to understand exactly how to fix the issue.
I believe, currently, a Function-type prop is a unique case. It is (correct me if I'm wrong), the only prop type that does not have the option of a default "factory" function:
props: {
fnProp1: {
type: Function,
default: () => false, // type of this.fnProp1 should be `() => boolean`
},
fnProp2: {
type: Function,
default: () => (() => false), // type of this.fnProp2 should be `() => (() => boolean)`
},
boolProp1: {
type: Boolean,
default: false, // type of this.boolProp1 should be `boolean`
},
boolProp2: {
type: Boolean,
default: () => false, // type of this.boolProp2 should STILL be `boolean`
},
strProp1: {
type: String,
default: 'hi', // type of this.strProp1 should be `string`
},
strProp2: {
type: String,
default: () => 'hi', // type of this.strProp2 should STILL be `string`
},
// etc.
},
Furthermore, if you want to return an object from the default, it completely skips the function type altogether:
// ...
props: {
returnsAnObject: {
type: Function,
default: () => ({}),
}
},
// ...
// Type SHOULD be `Function`, or `() => {}`, but...
this.returnsAnObject; //=> Type: {}
// ...which is not even the [broken] union `{} | () => {}` type like the other cases
this.returnsAnObject();
// Cannot invoke an expression whose type lacks a call signature.
// Type '{}' has no compatible call signatures.
// ...
I'd rather not keep bumping this unnecessarily, since it's mostly an echo chamber at the moment, but this 2.5.17
to 2.5.18+
patch update breaks our build, necessitates a lot of boilerplate around what used to be correctly-inferred properties on our components, and the causal changes seem to be fairly clear. Any attention or help would be greatly appreciated!
if you annotate with the PropType<>
it should work, this was a fix on https://github.com/vuejs/vue/pull/9733
const Example = Vue.extend({
template: `
<button @click="doSomethingWithFoo()">
<slot></slot>
</button>
`,
props: {
// original issue
fooFn: {
type: Function as PropType<()=>string>,
default: () => { return 'hey this is the default return value'; },
},
returnsAnObject: {
type: Function as PropType<()=>object>,
default: () => ({}),
}
},
methods: {
doSomethingWithFoo(): void {
const obj = this.returnsAnObject(); //obj is object
const bar = this.fooFn(); // bar is string
alert(bar);
},
},
});
there's an PR https://github.com/vuejs/vuejs.org/pull/2068 to update docs
Is this issue back on TypeScript 3.6? The following compiles fine on TS 3.5.3 and fails on the latest TS 3.6.3.
Vue: 2.6.10 TypeScript: 3.6.3
import Vue from 'vue';
export default Vue.extend({
props: {
cb: {
type: Function,
default: () => {},
},
},
created() {
this.cb();
}
});
13:10 This expression is not callable.
No constituent of type 'void | Function' is callable.
11 |
12 | created() {
> 13 | this.cb();
| ^
14 | }
15 | });
Removing default: () => {}
from cb
as well as annotating it with PropOptions<() => void>
helps, but this wasn't needed before.
Put a repro here: https://github.com/romansp/vue-typescript-prop-function-default.
May be related to #10455.
@romansp I'm fairly confident that hasn't worked without annotation since Vue 2.5.17. Annotating with PropType<...>
(note: not PropOptions<...>
) works just fine on Vue 2.6.10 and TypeScript 3.6.3, though.
@kjleitz I'm sure that it does work on TS 3.5.3 and Vue 2.6.10. You can try cloning my repro https://github.com/romansp/vue-typescript-prop-function-default. I just pushed ts-3.5.3
branch where vue serve
runs fine.
@romansp Ah, I see, you're not using the same tsconfig.json
as in my original example. The fact that you're using "strict": true
instead of "noImplicitThis": true
fixes it in your ts-3.5.3
branch (we've also switched to using "strict": true
since the time this ticket was written; much better!). Even with "strict": true
though, if you set Vue back to v2.6.8 it's actually still broken in your ts-3.5.3
branch. It's always been wonky.
Vue v2.6.10 & TS v3.5.3 must be one of those special combinations that don't error for function props 🤷♂ But even in that branch, the "working" case loses type info from this.cb
—better to use type: Function as PropType<() => void>,
instead of a bare type: Function,
.
This is still broken, even with "strict": true
; can't use a default
for a function-type prop. A more complete example:
const ComponentWithFunctionProps = Vue.extend({
props: {
functionProp: {
type: Function,
default: () => true,
},
functionPropWithBooleanReturnType: {
type: Function as PropType<() => boolean>,
default: () => true,
},
booleanProp: {
type: Boolean,
default: true,
},
booleanPropWithFunctionDefault: {
type: Boolean,
default: () => true,
},
},
methods: {
test(): void {
// ERROR!
// (property) functionProp: boolean | Function
// -------------------------------------------
// This expression is not callable.
// No constituent of type 'boolean | Function' is callable.ts(2349)
this.functionProp();
// ERROR!
// (property) functionPropWithBooleanReturnType: boolean | (() => boolean)
// -----------------------------------------------------------------------
// This expression is not callable.
// Not all constituents of type 'boolean | (() => boolean)' are callable.
// Type 'false' has no call signatures.ts(2349)
this.functionPropWithBooleanReturnType();
// const foo: boolean
const foo = this.booleanProp;
// const bar: boolean
const bar = this.booleanPropWithFunctionDefault;
},
},
});
I submitted a fix for this in https://github.com/vuejs/vue/pull/11223.
Version
2.5.22
Reproduction link
https://jsfiddle.net/keegan_openbay/gehkx7pf/10/
https://jsfiddle.net/keegan_openbay/018rs3ae/11/
(More explanation in the fiddle, but keep in mind that JSFiddle doesn't show TS errors)
Steps to reproduce
Function
, and with a default function that returns some value; e.g.,What is expected?
What is actually happening?
Vue version: 2.5.22 TypeScript version: 3.0.3
tsconfig.json: