使用TypeScript装饰器扩展ES6类时扩展类型

时间:2019-02-26 19:03:05

标签: typescript ecmascript-6 es6-class typescript-decorator typescript-class

我正在尝试使用装饰器( a-la-angular 样式)装饰类,并向其添加方法和属性。

这是我的示例修饰类:

var token = await GetToken();
        RequestModel requestModel = new RequestModel(requestViewModel);
        string Baseurl = "https://example.api.com";

        using (var client = new HttpClient())
        {

            client.BaseAddress = new Uri(Baseurl);
            client.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", string.Format("Bearer {0}", token));
            var request = new HttpRequestMessage(HttpMethod.Post, "/example.api/api/test/individual/get");

            var keyValues = new List<KeyValuePair<string, string>>();
            keyValues.Add(new KeyValuePair<string, string>("SolicitantData", null));
            keyValues.Add(new KeyValuePair<string, string>("Name", requestViewModel.SolicitantDataView.Name));
            keyValues.Add(new KeyValuePair<string, string>("MiddleInitial", requestViewModel.SolicitantDataView.MiddleInitial));
            keyValues.Add(new KeyValuePair<string, string>("LastName", requestViewModel.SolicitantDataView.LastName));
            keyValues.Add(new KeyValuePair<string, string>("LastName2", requestViewModel.SolicitantDataView.LastName2));
            keyValues.Add(new KeyValuePair<string, string>("NickName", ""));
            keyValues.Add(new KeyValuePair<string, string>("SSN", requestViewModel.SolicitantDataView.SSN));
            keyValues.Add(new KeyValuePair<string, string>("BirthDate", requestViewModel.SolicitantDataView.BirthDate.ToString()));
            keyValues.Add(new KeyValuePair<string, string>("EmailAddress", requestViewModel.SolicitantDataView.EmailAddress));
            keyValues.Add(new KeyValuePair<string, string>("DriverLicense", ""));
            keyValues.Add(new KeyValuePair<string, string>("DeathDate", ""));
            keyValues.Add(new KeyValuePair<string, string>("Addresses", "[]"));
            keyValues.Add(new KeyValuePair<string, string>("PhoneNumbers", "[]"));
            keyValues.Add(new KeyValuePair<string, string>("TerminalId", requestViewModel.TerminalId.ToString()));
            keyValues.Add(new KeyValuePair<string, string>("OGPATGNumber", ""));
            keyValues.Add(new KeyValuePair<string, string>("OGPCorrelationID", ""));
            keyValues.Add(new KeyValuePair<string, string>("Source", requestViewModel.Source));
            keyValues.Add(new KeyValuePair<string, string>("UserId", ""));
            keyValues.Add(new KeyValuePair<string, string>("IndividualId", ""));
            keyValues.Add(new KeyValuePair<string, string>("SendByEmail", requestViewModel.SendByEmail.ToString()));


            request.Content = new FormUrlEncodedContent(keyValues);
            var response = await client.SendAsync(request); // <----- Code 500 here.
  }
     return View();
  }

这是装饰器:

@decorator
class Person{

}

const decorator = (target)=>{ return class New_Class extends target { myProp:string } } 不是Person的已知属性:

myProp

如何装饰打字稿类并保留打字完成,打字安全性等?

3 个答案:

答案 0 :(得分:3)

有一个GitHub issue about this,其中有很多个讨论。我认为它的摘要是:装饰器不要突变 class的类型(大多数讨论是关于应该还是应该't 那样),因此您无法按照自己的意愿进行操作,例如:

const decorator = (target: new (...args: any) => any) => {
  // note I'm extending target, not Person, otherwise you're not
  // decorating the passed-in thing
  return class New_Class extends target {
    myProp!: string
  }
}

@decorator
class Person {
  noKnowledgeOfMyProp: this['myProp'] = "oops"; // error
}

declare const p: Person;
p.myProp; // error, uh oh

您可以做的只是将装饰器用作普通的mixin函数,并让Person扩展该函数的返回值。您最终得到了两个类定义……一个被传递到decorator中,另一个被新类扩展。 “内部”类(传递给decorator())仍然不知道所添加的道具,但是“外部”类可以:

class Person extends decorator(class {
  innerProp: string = "inner";
  noKnowledgeOfMyProp: this['myProp'] = "oops"; // error
}) {
  outerProp: string = "outer"
  hasKnowledgeOrMyProp: this['myProp'] = "okay"; // okay
}

declare const p: Person;
p.myProp; // okay

有帮助吗?祝你好运!

答案 1 :(得分:2)

我找到了一种实现多种遗产(实际上是级联)的解决方案,但是值得一看。

假设您的Base类具有一些属性和方法:

class Base {
    tableName = 'My table name';
    hello(name) {
     return `hello ${name}`;
    }
}

并且您希望一个类扩展Base,但是您还定义了一些要重用的属性。为此将执行以下功能:

type Constructor<T = {}> = new (...args: any[]) => T;
function UserFields<TBase extends Constructor>(Base: TBase) {
    return class extends Base {
        name: string;
        email: string;
    };
}

现在,我们可以做一个扩展Base和扩展UserFields的类,而打字稿语言服务将在这两个类中找到属性。它模仿了多种遗产,但实际上是一个级联。

class User extends UserFields(Base) { }
const u = new User();
u.tableName = 'users'; // ok
u.name = 'John'; // ok

通过这种方式,您可以将UserFields函数与其他任何类一起重用。 一个明显的例子是,如果您希望将客户端的User对象公开为“干净”对象并具有可用的字段,那么您有一个UserDb对象,该对象与数据库连接并且任何其他服务器端方法都可以具有相同的字段太。我们只定义一次数据库字段!

另一个好处是,您可以将mixins mixin1(mixin2 ....(Base))链接起来,使其具有与您希望在同一类中拥有的属性一样多的属性。

每个人都希望装饰器属性对打字稿可见,但这是一个很好的解决方案。

答案 2 :(得分:1)

要补充jcalz response,返回到Decorator Pattern的定义,它不会更改其目标的接口/合同。不只是术语。 TypeScript装饰器与Java注释和.NET属性具有相似之处,这与事实相同,即不更改接口:它们只是添加元数据。

Class mixin是解决您的问题的理想人选。但是最好不要在名称中使用“ decorator”,以免造成混淆。

相关问题