我想表示一个paremeter应该是一个对象或一个简单的值类型(数字,bool,字符串等),但不是函数。
如果我使用Object
,编译器让我分配一个函数。
var test: Object = () => "a";
如果我使用any
,当然也会得到同样的结果。在这种情况下,是否有类型或技巧可以帮助我?
我的基本目标是在使用Knockout observable时保证安全,这样我就不会忘记解开它们的那些小问题:)
答案 0 :(得分:6)
您可以使用conditional types在TypeScript 2.8中执行此操作。
type NotFunc<T> = Exclude<T, Function>
function noFunc <T> (notF: T & NotFunc<T>) { return notF }
const f = () => 2
noFunc(f) // error!
noFunc(f()) // compiles!
如果类型系统可以决定T
扩展Function
(即,T
是一个函数),那么类型将是never
,这是一个编译时错误。否则,类型将为T
,您的代码将编译。
但是,您应该阅读此内容以更清楚地了解正在发生的事情:
// 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
类型与any
一样糟糕,所以请避免使用答案 1 :(得分:5)
2018年11月添加 - 因为条件类型现在已经成为现实!
条件类型为此提供了可能的解决方案,因为您可以创建NotFunction
条件类型,如下所示:
type NotFunction<T> = T extends Function ? never : T;
其工作原理如下:
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;
唯一的缺点是你必须把变量放在声明的左侧和右侧 - 尽管如果你犯了错误就有安全性,例如:
// Error - function is not a string
const x: NotFunction<typeof aString> = aFunction;
您可以使用typeof
提供运行时检查,虽然编译时检查不会捕获您忘记执行该功能的实例:
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);
你几乎可以,因为你可以使用重载来允许&#34;许多类型之一&#34;,例如:
class Example {
someMethod(input: number);
someMethod(input: string);
someMethod(input: boolean);
someMethod(input: any) {
}
}
以下是:为了允许对象类型,您必须添加someMethod(input: Object);
或someMethod(input: {});
的重载签名。一旦这样做,函数就会被允许,因为函数继承自object。
如果你可以将object
缩小到不那么普通的东西,你可以简单地为你想要允许的所有类型添加越来越多的重载(yikes)。
答案 2 :(得分:1)
使用typescript 1.8,如果将函数定义为具有所有4个属性的函数,则可以非常接近:caller,bind,apply和call:
interface NoCaller {
caller?: void;
}
interface NoBind {
bind?: void;
}
interface NoApply {
apply?: void;
}
interface NoCall {
call?: void;
}
type NotAFunction = NoCaller | NoBind | NoApply | NoCall; // if it fails all 4 checks it's a function
function check<T extends NotAFunction>(t: T) {
// do something
}
function f() {
}
class T {
}
var o = new T();
check({});
check(o);
check({caller: 'ok', call: 3, apply: this});
//check(f); // fails
//check(T); // also fails: classes are functions, you can't really escape from javascript
令人惊讶的是,错误信息并不是那么糟糕:
error TS2345: Argument of type '() => void' is not assignable to parameter of type 'NoCaller | NoBind | NoApply | NoCall'.
Type '() => void' is not assignable to type 'NoCall'.
Types of property 'call' are incompatible.
Type '(thisArg: any, ...argArray: any[]) => any' is not assignable to type 'void'.
答案 3 :(得分:1)
这是一种定义所有有效(非函数)值然后使用递归定义的方法。我认为这适用于我的情况,并希望其他任何人遇到这个问题。
Example on Typescript Playground
type NoFunctionValue =
boolean
| string
| number
| null
| undefined
| NoFunctionObject
| NoFunctionArray
interface NoFunctionObject {
[key: string]: NoFunctionValue
}
interface NoFunctionArray extends Array<NoFunctionValue> { }
// Try putting a function anywhere in here to see error
const text: NoFunctionObject = {
bool: true,
str: 'string',
num: 7,
nul: null,
undef: undefined,
arr: [true, 'string', 7, null, undefined],
obj: {
bool: true,
str: 'string',
num: 7,
nul: null,
undef: undefined,
arr: [true, 'string', 7, null, undefined]
}
}