这个问题与Class type check with TypeScript
的直接类比我需要在运行时找出any类型的变量实现接口。这是我的代码:
interface A{
member:string;
}
var a:any={member:"foobar"};
if(a instanceof A) alert(a.member);
如果您在typescript playground中输入此代码,则最后一行将被标记为错误,“当前范围中不存在名称A”。但事实并非如此,该名称确实存在于当前范围内。我甚至可以在没有编辑投诉的情况下将变量声明更改为var a:A={member:"foobar"};
。浏览网页并在SO上找到另一个问题后,我将界面更改为类,但后来我无法使用对象文字来创建实例。
我想知道A类型是如何消失的,但是看看生成的javascript解释了这个问题:
var a = {
member: "foobar"
};
if(a instanceof A) {
alert(a.member);
}
没有A作为接口的表示,因此不可能进行运行时类型检查。
我知道javascript作为动态语言没有接口的概念。有没有办法输入接口检查?
打字稿操场的自动完成显示,打字稿甚至提供了方法implements
。我该如何使用它?
答案 0 :(得分:131)
您可以在没有instanceof
关键字的情况下实现您想要的目标,因为您现在可以编写自定义类型保护:
interface A{
member:string;
}
function instanceOfA(object: any): object is A {
return 'member' in object;
}
var a:any={member:"foobar"};
if (instanceOfA(a)) {
alert(a.member);
}
如果您需要检查很多成员以确定对象是否与您的类型匹配,您可以改为添加一个鉴别器。以下是最基本的示例,需要您管理自己的鉴别器......您需要深入了解模式以确保避免重复的鉴别器。
interface A{
discriminator: 'I-AM-A';
member:string;
}
function instanceOfA(object: any): object is A {
return object.discriminator === 'I-AM-A';
}
var a:any = {discriminator: 'I-AM-A', member:"foobar"};
if (instanceOfA(a)) {
alert(a.member);
}
答案 1 :(得分:65)
在TypeScript 1.6中,user-defined type guard将完成这项工作。
interface Foo {
fooProperty: string;
}
interface Bar {
barProperty: string;
}
function isFoo(object: any): object is Foo {
return 'fooProperty' in object;
}
let object: Foo | Bar;
if (isFoo(object)) {
// `object` has type `Foo`.
object.fooProperty;
} else {
// `object` has type `Bar`.
object.barProperty;
}
就像Joe Yang提到的那样:自TypeScript 2.0以来,你甚至可以利用标记的联合类型。
interface Foo {
type: 'foo';
fooProperty: string;
}
interface Bar {
type: 'bar';
barProperty: number;
}
let object: Foo | Bar;
// You will see errors if `strictNullChecks` is enabled.
if (object.type === 'foo') {
// object has type `Foo`.
object.fooProperty;
} else {
// object has type `Bar`.
object.barProperty;
}
它也适用于switch
。
答案 2 :(得分:33)
typescript 2.0引入了标记联合
interface Square {
kind: "square";
size: number;
}
interface Rectangle {
kind: "rectangle";
width: number;
height: number;
}
interface Circle {
kind: "circle";
radius: number;
}
type Shape = Square | Rectangle | Circle;
function area(s: Shape) {
// In the following switch statement, the type of s is narrowed in each case clause
// according to the value of the discriminant property, thus allowing the other properties
// of that variant to be accessed without a type assertion.
switch (s.kind) {
case "square": return s.size * s.size;
case "rectangle": return s.width * s.height;
case "circle": return Math.PI * s.radius * s.radius;
}
}
答案 3 :(得分:22)
用户定义类型防护怎么样? https://www.typescriptlang.org/docs/handbook/advanced-types.html
interface Bird {
fly();
layEggs();
}
interface Fish {
swim();
layEggs();
}
function isFish(pet: Fish | Bird): pet is Fish { //magic happens here
return (<Fish>pet).swim !== undefined;
}
// Both calls to 'swim' and 'fly' are now okay.
if (isFish(pet)) {
pet.swim();
}
else {
pet.fly();
}
答案 4 :(得分:13)
It's now possible, I just released an enhanced version of the TypeScript
compiler that provides full reflection capabilities. You can instantiate classes from their metadata objects, retrieve metadata from class constructors and inspect interface/classes at runtime. You can check it out here
Usage example:
In one of your typescript files, create an interface and a class that implements it like the following:
interface MyInterface {
doSomething(what: string): number;
}
class MyClass implements MyInterface {
counter = 0;
doSomething(what: string): number {
console.log('Doing ' + what);
return this.counter++;
}
}
now let's print some the list of implemented interfaces.
for (let classInterface of MyClass.getClass().implements) {
console.log('Implemented interface: ' + classInterface.name)
}
compile with reflec-ts and launch it:
$ node main.js
Implemented interface: MyInterface
Member name: counter - member kind: number
Member name: doSomething - member kind: function
See reflection.d.ts for Interface
meta-type details.
UPDATE: You can find a full working example here
答案 5 :(得分:6)
这是另一个选项:模块ts-interface-builder提供了一个构建时工具,可以将TypeScript接口转换为运行时描述符,ts-interface-checker可以检查对象是否满足它。
对于OP的例子,
interface A {
member: string;
}
您首先运行ts-interface-builder
,它会生成一个带有描述符的新简明文件,例如foo-ti.ts
,您可以这样使用:
import fooDesc from './foo-ti.ts';
import {createCheckers} from "ts-interface-checker";
const {A} = createCheckers(fooDesc);
A.check({member: "hello"}); // OK
A.check({member: 17}); // Fails with ".member is not a string"
您可以创建单行类型保护功能:
function isA(value: any): value is A { return A.test(value); }
答案 6 :(得分:5)
我想指出TypeScript没有提供动态测试对象是否实现特定接口的直接机制。
相反,TypeScript代码可以使用JavaScript技术检查对象上是否存在适当的成员集。例如:
var obj : any = new Foo();
if (obj.someInterfaceMethod) {
...
}
答案 7 :(得分:3)
在我看来这是最好的方法;在接口上附加一个“Fubber”符号。它的编写速度要快得多,对于 JavaScript 引擎来说比类型保护要快得多,支持接口继承并使类型保护在您需要时易于编写。
这是 ES6 使用符号的目的。
// Notice there is no naming conflict, because interfaces are a *type*
export const IAnimal = Symbol("IAnimal");
export interface IAnimal {
[IAnimal]: boolean; // the fubber
}
export const IDog = Symbol("IDog");
export interface IDog extends IAnimal {
[IDog]: boolean;
}
export const IHound = Symbol("IDog");
export interface IHound extends IDog {
// The fubber can also be typed as only 'true'; meaning it can't be disabled.
[IDog]: true;
[IHound]: boolean;
}
import { IDog, IAnimal } from './interfaces';
class Dog implements IDog {
// Multiple fubbers to handle inheritance:
[IAnimal] = true;
[IDog] = true;
}
class Hound extends Dog implements IHound {
[IHound] = true;
}
如果您想帮助 TypeScript 编译器,可以将此代码放入类型保护中。
import { IDog, IAnimal } from './interfaces';
let dog = new Dog();
if (dog instanceof Hound || dog[IHound]) {
// false
}
if (dog[IAnimal]?) {
// true
}
let houndDog = new Hound();
if (houndDog[IDog]) {
// true
}
if (dog[IDog]?) {
// it definitely is a dog
}
答案 8 :(得分:3)
与上面使用user-defined guards相同,但这次使用箭头函数谓词
interface A {
member:string;
}
const check = (p: any): p is A => p.hasOwnProperty('member');
var foo: any = { member: "foobar" };
if (check(foo))
alert(foo.member);
答案 9 :(得分:3)
TypeGuards
interface MyInterfaced {
x: number
}
function isMyInterfaced(arg: any): arg is MyInterfaced {
return arg.x !== undefined;
}
if (isMyInterfaced(obj)) {
(obj as MyInterfaced ).x;
}
答案 10 :(得分:1)
您可以在运行时使用ts-validate-type来验证TypeScript类型,就像这样(尽管需要Babel插件):
const user = validateType<{ name: string }>(data);
答案 11 :(得分:1)
我在文件filter-descriptor.interface.d.ts
中的@progress/kendo-data-query
中找到了一个示例
检查器
declare const isCompositeFilterDescriptor: (source: FilterDescriptor | CompositeFilterDescriptor) => source is CompositeFilterDescriptor;
示例用法
const filters: Array<FilterDescriptor | CompositeFilterDescriptor> = filter.filters;
filters.forEach((element: FilterDescriptor | CompositeFilterDescriptor) => {
if (isCompositeFilterDescriptor(element)) {
// element type is CompositeFilterDescriptor
} else {
// element type is FilterDescriptor
}
});
答案 12 :(得分:0)
由于类型在运行时是未知的,因此我编写了以下代码来比较未知对象,而不是将其与类型进行比较,而是与已知类型的对象进行比较:
这是我用于深度比较的(与接口无关的)代码:
function assertTypeT<T>(loaded: any, wanted: T, optional?: Set<string>): T {
// this is called recursively to compare each element
function assertType(found: any, wanted: any, keyNames?: string): void {
if (typeof wanted !== typeof found) {
throw new Error(`assertType expected ${typeof wanted} but found ${typeof found}`);
}
switch (typeof wanted) {
case "boolean":
case "number":
case "string":
return; // primitive value type -- done checking
case "object":
break; // more to check
case "undefined":
case "symbol":
case "function":
default:
throw new Error(`assertType does not support ${typeof wanted}`);
}
if (Array.isArray(wanted)) {
if (!Array.isArray(found)) {
throw new Error(`assertType expected an array but found ${found}`);
}
if (wanted.length === 1) {
// assume we want a homogenous array with all elements the same type
for (const element of found) {
assertType(element, wanted[0]);
}
} else {
// assume we want a tuple
if (found.length !== wanted.length) {
throw new Error(
`assertType expected tuple length ${wanted.length} found ${found.length}`);
}
for (let i = 0; i < wanted.length; ++i) {
assertType(found[i], wanted[i]);
}
}
return;
}
for (const key in wanted) {
const expectedKey = keyNames ? keyNames + "." + key : key;
if (typeof found[key] === 'undefined') {
if (!optional || !optional.has(expectedKey)) {
throw new Error(`assertType expected key ${expectedKey}`);
}
} else {
assertType(found[key], wanted[key], expectedKey);
}
}
}
assertType(loaded, wanted);
return loaded as T;
}
下面是我如何使用它的示例。
在此示例中,我希望JSON包含一个元组数组,其中第二个元素是称为User
(具有两个可选元素)的接口的实例。
TypeScript的类型检查将确保我的示例对象正确,然后assertTypeT函数检查未知(从JSON加载)的对象是否与示例对象匹配。
export function loadUsers(): Map<number, User> {
const found = require("./users.json");
const sample: [number, User] = [
49942,
{
"name": "ChrisW",
"email": "example@example.com",
"gravatarHash": "75bfdecf63c3495489123fe9c0b833e1",
"profile": {
"location": "Normandy",
"aboutMe": "I wrote this!\n\nFurther details are to be supplied ..."
},
"favourites": []
}
];
const optional: Set<string> = new Set<string>(["profile.aboutMe", "profile.location"]);
const loaded: [number, User][] = assertTypeT(found, [sample], optional);
return new Map<number, User>(loaded);
}
您可以在实现用户定义的类型防护时调用这样的检查。
答案 13 :(得分:0)
基于Fenton的answer,这是我执行的函数的实现,该函数可验证给定的const [doughnuts, setDoughnuts] = useState(24)
是否具有object
的密钥,无论是全部还是部分。
根据您的用例,您可能还需要检查每个接口属性的类型。下面的代码无法做到这一点。
interface
用法示例:
function implementsTKeys<T>(obj: any, keys: (keyof T)[]): obj is T {
if (!obj || !Array.isArray(keys)) {
return false;
}
const implementKeys = keys.reduce((impl, key) => impl && key in obj, true);
return implementKeys;
}
答案 14 :(得分:0)
export interface ConfSteps {
group: string;
key: string;
steps: string[];
}
private verify(): void {
const obj = `{
"group": "group",
"key": "key",
"steps": [],
"stepsPlus": []
} `;
if (this.implementsObject<ConfSteps>(obj, ['group', 'key', 'steps'])) {
console.log(`Implements ConfSteps: ${obj}`);
}
}
private objProperties: Array<string> = [];
private implementsObject<T>(obj: any, keys: (keyof T)[]): boolean {
JSON.parse(JSON.stringify(obj), (key, value) => {
this.objProperties.push(key);
});
for (const key of keys) {
if (!this.objProperties.includes(key.toString())) {
return false;
}
}
this.objProperties = null;
return true;
}
答案 15 :(得分:0)
使用字符串文字是困难的,因为如果您想重构方法或接口名称,那么您的IDE可能就不会重构这些字符串文字。 我为您提供了我的解决方案,如果界面中至少存在一种方法,那么该解决方案将起作用
export class SomeObject implements interfaceA {
public methodFromA() {}
}
export interface interfaceA {
methodFromA();
}
检查对象是否为接口类型
const obj = new SomeObject();
const objAsAny = obj as any;
const objAsInterfaceA = objAsAny as interfaceA;
const isObjOfTypeInterfaceA = objAsInterfaceA.methodFromA != null;
console.log(isObjOfTypeInterfaceA)
注意:即使我们删除了“ implements interfaceA”,我们也将实现,因为该方法仍然存在于SomeObject类中。
答案 16 :(得分:0)
这是我使用类和 lodash 提出的解决方案:(有效!)
// TypeChecks.ts
import _ from 'lodash';
export class BakedChecker {
private map: Map<string, string>;
public constructor(keys: string[], types: string[]) {
this.map = new Map<string, string>(keys.map((k, i) => {
return [k, types[i]];
}));
if (this.map.has('__optional'))
this.map.delete('__optional');
}
getBakedKeys() : string[] {
return Array.from(this.map.keys());
}
getBakedType(key: string) : string {
return this.map.has(key) ? this.map.get(key) : "notfound";
}
}
export interface ICheckerTemplate {
__optional?: any;
[propName: string]: any;
}
export function bakeChecker(template : ICheckerTemplate) : BakedChecker {
let keys = _.keysIn(template);
if ('__optional' in template) {
keys = keys.concat(_.keysIn(template.__optional).map(k => '?' + k));
}
return new BakedChecker(keys, keys.map(k => {
const path = k.startsWith('?') ? '__optional.' + k.substr(1) : k;
const val = _.get(template, path);
if (typeof val === 'object') return val;
return typeof val;
}));
}
export default function checkType<T>(obj: any, template: BakedChecker) : obj is T {
const o_keys = _.keysIn(obj);
const t_keys = _.difference(template.getBakedKeys(), ['__optional']);
return t_keys.every(tk => {
if (tk.startsWith('?')) {
const ak = tk.substr(1);
if (o_keys.includes(ak)) {
const tt = template.getBakedType(tk);
if (typeof tt === 'string')
return typeof _.get(obj, ak) === tt;
else {
return checkType<any>(_.get(obj, ak), tt);
}
}
return true;
}
else {
if (o_keys.includes(tk)) {
const tt = template.getBakedType(tk);
if (typeof tt === 'string')
return typeof _.get(obj, tk) === tt;
else {
return checkType<any>(_.get(obj, tk), tt);
}
}
return false;
}
});
}
自定义类:
// MyClasses.ts
import checkType, { bakeChecker } from './TypeChecks';
class Foo {
a?: string;
b: boolean;
c: number;
public static _checker = bakeChecker({
__optional: {
a: ""
},
b: false,
c: 0
});
}
class Bar {
my_string?: string;
another_string: string;
foo?: Foo;
public static _checker = bakeChecker({
__optional: {
my_string: "",
foo: Foo._checker
},
another_string: ""
});
}
在运行时检查类型:
if (checkType<Bar>(foreign_object, Bar._checker)) { ... }
答案 17 :(得分:0)
TS为此提供了类型防护罩。他们以以下方式定义它:
一些执行运行时检查以保证类型的表达式 在一定范围内。
这基本上意味着TS编译器在具有足够的信息时可以将类型缩小为更具体的类型。例如:
function foo (arg: number | string) {
if (typeof arg === 'number') {
// fine, type number has toFixed method
arg.toFixed()
} else {
// Property 'toFixed' does not exist on type 'string'. Did you mean 'fixed'?
arg.toFixed()
// TSC can infer that the type is string because
// the possibility of type number is eliminated at the if statement
}
}
回到您的问题,我们也可以将类型保护的概念应用于对象以确定其类型。要为对象定义类型保护,我们需要定义一个函数,其返回类型为类型谓词。例如:
interface Dog {
bark: () => void;
}
// The function isDog is a user defined type guard
// the return type: 'pet is Dog' is a type predicate,
// it determines whether the object is a Dog
function isDog(pet: object): pet is Dog {
return (pet as Dog).bark !== undefined;
}
const dog: any = {bark: () => {console.log('woof')}};
if (isDog(dog)) {
// TS now knows that objects within this if statement are always type Dog
// This is because the type guard isDog narrowed down the type to Dog
dog.bark();
}