简体   繁体   English

以编程方式从界面创建打字稿用户定义的类型防护

[英]Programatically Create Typescript User Defined Type Guards from Interface

In my Angular2 project I have created an Interface for GridMetadata : 在我的Angular2项目中,我创建了一个GridMetadata接口:

grid-metadata.ts 网格metadata.ts

export interface GridMetadata {
  activity: string;
  createdAt: object;
  totalReps: number;
  updatedAt: object;
}

Inside my Service I have a public method create which requires on argument which should be an Object with one property activity that has a String value eg { activity: 'Push-Ups' } . 在我的服务中我有一个公共方法create ,它需要参数,该参数应该是一个具有String值的属性activity的Object,例如{ activity: 'Push-Ups' } You can find this towards the bottom of GridService : 你可以在GridService的底部找到这个:

grid.service.ts grid.service.ts

import { Injectable } from '@angular/core';

import {
  AngularFireDatabase,
  FirebaseListObservable
} from 'angularfire2/database';
import * as firebase from 'firebase/app';
// import { Observable } from 'rxjs/Observable';

import { AuthService } from './auth.service';
// TODO: Is this the correct order for interfaces?
import { GridMetadata } from './grid-metadata';

@Injectable()
export class GridService {
  private gridMetadata: FirebaseListObservable<any>;

  constructor(
    private afDb: AngularFireDatabase,
    private authService: AuthService
  ) {
    this.init();
  }

  private init(): void {
    this.gridMetadata = this.afDb.list('gridMetadata');
  }

  create(metadata: GridMetadata): Promise<any> {
    function isGridMetadata(obj: any): obj is GridMetadata {
      return typeof obj.activity === 'string' &&
        typeof obj.createdAt === 'object' &&
        typeof obj.totalReps === 'number' &&
        typeof obj.updatedAt === 'object' ?
          true :
          false;
    }

    return new Promise((resolve, reject) => {
      if (this.authService.isAuthenticated === false) {
        return reject(new Error('Can’t create new grid; you’re not auth’d.'));
      }

      let key: string = this.gridMetadata.push(undefined).key;
      let uri: string = [this.authService.currentUserId, key].join('/');

      metadata.createdAt = firebase.database.ServerValue.TIMESTAMP;
      metadata.totalReps = 0;
      metadata.updatedAt = firebase.database.ServerValue.TIMESTAMP;

      if (isGridMetadata(metadata) === false) {
        return reject(new TypeError('`metadata` doesn’t match the signature of the `GridMetadata` interface.'));
      }

      this.gridMetadata.update(uri, metadata)
        .then(() => {
          resolve();
        })
        .catch((error) => {
          reject(new Error(error.message));
        });
    });
  }
}

Question 1 问题1

First of all notice that in my create method I've said the required argument metadata should match the Interface GridMetadata — which it doesn't. 首先请注意,在我的create方法中,我已经说过必需的参数metadata应该与Interface GridMetadata匹配 - 它没有。 I'm only passing it an Object { activity: 'Push-Ups' } and notice in the interface that there are three other required properties ( createdAt , totalReps and updatedAt ). 我只是传递一个对象{ activity: 'Push-Ups' }和通知界面,还有其他三个必需的属性( createdAttotalRepsupdatedAt )。

There are no errors at either compile-time or runtime. 编译时或运行时都没有错误。 Why is this? 为什么是这样?

I have most likely just misunderstood this as I am new to TypeScript. 我很可能只是误解了这一点,因为我是TypeScript的新手。

Question 2 问题2

I believe in question 1 there should be an error; 我相信问题1应该有一个错误; this could be fixed easily I am just curious as to why no error is thrown. 这可以很容易修复我只是好奇为什么没有错误被抛出。

My real question is you can see I go on to build the metadata Object to match the signature of the Interface GridMetadata by adding the missing properties ( createdAt , totalReps and updatedAt ). 我真正的问题是,你可以看到我去建metadata对象相匹配的接口的签名GridMetadata通过添加缺少的属性( createdAttotalRepsupdatedAt )。

I then reject the Promise if the Object does not match the Interface. 如果Object与Interface不匹配,我会拒绝Promise。

How can I check that createdAt and updatedAt are not only Objects, but also match the signature { .sv: 'timestamp' } , and is there a better way to do this than the if statement I'm using now? 我如何检查createdAtupdatedAt不仅是对象,但也匹配签名{ .sv: 'timestamp' }和有没有更好的办法做到这一点比if语句我现在使用?

Question 3 问题3

And finally; 最后; for convenience is there a way to programatically create a User Defined Type Guard from an Interface? 为方便起见,有一种方法可以从接口以编程方式创建用户定义类型保护吗?

I would imaging not as the Interface in the eyes of TypeScript is not an object, therefore can't be programatically worked with in a function, for eg extract the properties activity, createdAt, ... and the values string, object, ... and use these to loop through and programatically check against in the guard. 我不会成像,因为TypeScript眼中的界面不是一个对象,因此无法在函数中以编程方式工作,例如提取属性activity, createdAt, ...以及值string, object, ...通过使用这些循环和编程核对中后卫。


While there a three questions here, in reality Q2 is my primary concern as I am sure Q1 has a simple answer and Q3 is just bonus points — I will accept answers to just Q2 and really appreciate your help! 虽然这里有三个问题,但实际上Q2是我最关心的问题,因为我确信Q1有一个简单的答案,Q3只是奖励积分 - 我会接受Q2的答案,非常感谢你的帮助!

Question 1 问题1

The Typescript compiler doesn't complain b/c the compiler isn't looking at your HTML templates. Typescript编译器没有抱怨b / c编译器没有查看你的HTML模板。

Question 2 问题2

In the end it's all Javascript, so there's no way to verify the "signature" of an object without checking to see if the object has the desired properties. 最后它是所有Javascript,因此没有办法验证对象的“签名”,而无需检查对象是否具有所需的属性。 For example, adding to your original logic: 例如,添加到原始逻辑:

typeof obj.createdAt === 'object' &&
    obj.createdAt.hasOwnProperty('.sv') && 
    typeof obj.createdAt['.sv']  === 'string'

But I might be more inclined to first check for the existence of the properties that are objects (rather than checking their type): 但我可能更倾向于首先检查是否存在属性对象(而不是检查它们的类型):

if (!obj.createdAt || !obj.updatedAt) {
    return false;
}
// now check for the child properties
return obj.createdAt.hasOwnProperty('.sv') &&
    typeof obj.createdAt['.sv'] === 'string' &&
    ...

You could obviously structure this differently, but it all boils down to the same sorts of checks. 显然你可以用不同的方式构建它,但这一切都归结为相同类型的检查。

Question 3 问题3

Interfaces in Typescript are only used at compile time. Typescript中的接口仅在编译时使用。 They are never included in the compiled output. 它们永远不会包含在编译输出中。 So at run time the interfaces do not exist and cannot be accessed programmatically. 因此,在运行时,接口不存在,无法以编程方式访问。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM