I would like to express that a paremeter should be an object or a simple value type (number, bool, string, etc.), but not a function.
If I use Object
, the compiler let's me to assign a function.
var test: Object = () => "a";
If I use any
, the same result of course too. Is there a type or trick which can help me out in this case?
My underlying goal is to garantee safety when using Knockout observables, so that I don't forget those little paranthesis to unwrap them :)
In TypeScript, object is the type of all non-primitive values (primitive values are undefined , null , booleans, numbers, bigints, strings). With this type, we can't access any properties of a value.
In TypeScript, there are multiple syntaxes for declaring the type of a function: Method signatures. Function type literals. Object type literals with call/construct signatures.
Yes, you should make it a habit to explicitly set all types, it's will prevent having unexpected values and also good for readability.
The TypeScript any type allows you to store a value of any type. It instructs the compiler to skip type checking. Use the any type to store a value that you don't actually know its type at the compile-time or when you migrate a JavaScript project over to a TypeScript project.
Added November 2018 - as conditional types are a thing now!
Conditional types provide a possible solution to this, as you can create a NotFunction
conditional type, as shown below:
type NotFunction<T> = T extends Function ? never : T;
This works as follows:
const aFunction = (input: string) => input;
const anObject = { data: 'some data' };
const aString = 'data';
// Error function is not a never
const x: NotFunction<typeof aFunction> = aFunction;
// OK
const y: NotFunction<typeof anObject> = anObject;
const z: NotFunction<typeof aString> = aString;
The only weakness in this is that you have to put the variable on the left side and right side of the statement - although there is safety if you make a mistake such as:
// Error - function is not a string
const x: NotFunction<typeof aString> = aFunction;
You can provide a runtime check using typeof
, which although isn't a compile time check will catch those instances where you forget to execute the function:
function example(input: any) {
if (typeof input === 'function') {
alert('You passed a function!');
}
}
function someFunction() {
return 1;
}
// Okay
example({ name: 'Zoltán' });
example(1);
example('a string');
example(someFunction());
// Not okay
example(function () {});
example(someFunction);
You almost can, because you could use an overload to allow "one of many types", for example:
class Example {
someMethod(input: number);
someMethod(input: string);
someMethod(input: boolean);
someMethod(input: any) {
}
}
Here comes the rub: in order to allow object types, you would have to add an overload signature of someMethod(input: Object);
or someMethod(input: {});
. As soon as you do this, functions would become allowed, because function inherits from object.
If you could narrow down object
to something less general, you could simply add more and more overloads (yikes) for all the types you want to allow.
You can do this in TypeScript 2.8 with conditional types.
type NotFunc<T> = Exclude<T, Function>
function noFunc <T> (notF: T & NotFunc<T>) { return notF }
const f = () => 2
noFunc(f) // error!
noFunc(f()) // compiles!
If the type system can decide that T
extends Function
(i.e., T
is a function) then the type will be never
, which is a compile-time error. Otherwise, the type will be T
and your code will compile.
However, you should read this to more clearly understand what is happening:
// type NotFunc<T> = T extends Function ? never : T
type NotFunc<T> = Exclude<T, Function>
// the imporant cases for us are any and {}
type AnyNotFunc = NotFunc<any> // any
type ObjNotFunc = NotFunc<{}> // {}
type NullNotFunc = NotFunc<null> // never
// some functions, explicitly typed and inferred
const f: Function = () => 2
const ff = () => 2
const g: Function = (): null => null
const gg = (): null => null
// so a function like this won't work:
function badNoFunc <T> (notF: NotFunc<T>) { return notF }
// these all compile, because T is inferred as {} and NotFunc<{}> is just {}
badNoFunc(f)
badNoFunc(g)
badNoFunc(ff)
badNoFunc(gg)
// so need the T & NotFunc<T> to give the compiler a hint as to the type of T
function noFunc <T> (notF: T & NotFunc<T>) { return notF }
// now f is correctly inferred to be Function
noFunc(f) // error! f is assignable to Function
noFunc(g) // error! g is assignable to Function
noFunc(f()) // OK! 2 is not assignable to Function
// but we would expect g() === null to be never since NotFunc<null> === never
noFunc(g()) // OK? even though null is assignable to Function?
noFunc<null>(g()) // Error! Ah, type Function represents () => any but NotFunc<null> is never
// if we use the implicitly typed version, gg, the compiler infers the null return value correctly
noFunc(gg()) // Error! Correct
noFunc(ff) // error! The type is correctly inferred to be function
noFunc(gg) // error! The type is correctly inferred to be function
noFunc(ff()) // OK! 2 is not assignable to Function
Takeaways:
Function
type is as bad as any
, so avoid itIf you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With