除了函数之外,TypeScript中是否有类型?

时间:2014-07-07 15:12:42

标签: typescript

我想表示一个paremeter应该是一个对象或一个简单的值类型(数字,bool,字符串等),但不是函数。

如果我使用Object,编译器让我分配一个函数。

var test: Object = () => "a";

如果我使用any,当然也会得到同样的结果。在这种情况下,是否有类型或技巧可以帮助我?

我的基本目标是在使用Knockout observable时保证安全,这样我就不会忘记解开它们的那些小问题:)

4 个答案:

答案 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:

  1. OP可以做他们想做的事情
  2. 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]
    }
}