当前位置:网站首页>Typescript learning [6] interface type

Typescript learning [6] interface type

2022-06-09 04:46:00 Large American without sugar

Interface Interface type

TypeScript It can not only help the front-end change the way of thinking , It can also strengthen the thinking and ability of interface oriented programming , And this is due to Interface Interface type . Through the interface type , We can clearly define what is inside the module 、 Cross module 、 Communication rules across project codes .

TypeScript The type detection of objects follows a process called “ The duck type ”(duck typing) perhaps “ Structured type (structural subtyping)” Principle of , That is, as long as the structures of the two objects are consistent , The types of properties and methods are consistent , Then their types are consistent .

The following is a sample code to first understand the interface type , As shown in the following code :

function Study(language: {
     name: string; age: () => number }) {
    
  console.log(` This is a paragraph created in  ${
      language.age()}  Code years ago , Name is  ${
      language.name}`);
}
Study({
    
  name: 'TypeScript',
  age: () => new Date().getFullYear() - 2012 //  This is a paragraph created in  10  Code years ago , Name is  TypeScript
});

In the above code , We have defined a possession string Type attribute name、 Function type properties age The object of language As a function of parameters . meanwhile , We also use similar definitions JavaScript The syntax of object literals defines an inline interface type to constrain the type of parameter object .

then , We passed on a name The attribute is ‘TypeScript’ String 、age Property to call the function with the literal value of the object that calculates the year difference function as a parameter .

In the process of calling the function ,TypeScript Static type detected that the passed object literal type is string Of name The attribute and type are () => number Of age Property is consistent with the type defined by the function parameter , So you won't throw a type error .

If we pass in a name The attribute is number Type or missing age The object literal of the property , As shown in the following code :

Study({
    
  name: 2; // error TS2322: Type 'number' is not assignable to type 'string'.
});

Study({
    
  name: 'TypeScript'; // error TS2345: Argument of type '{ name: string; }' is not assignable to parameter of type '{ name: string; age: () => number; }'.Property 'age' is missing in type '{ name: string; }' but required in type '{ name: string; age: () => number; }'.
});

At this time , The first 2 The guild prompts an error : ts(2322) number Cannot assign a value to string, The first 7 Line will also prompt an error : ts(2345) Argument and formal parameter types are incompatible , Missing required attribute age.

Again , If we pass in an object literal that contains attributes that are not in the parameter type definition as arguments , You will also get a type error ts(2345), Argument and formal parameter types are incompatible , As shown in the following code :

Study({
    
  id: 1, // error TS2345: Argument of type '{ id: number; name: string; age: () => number; }' is not assignable to parameter of type '{ name: string; age: () => number; }'.Object literal may only specify known properties, and 'id' does not exist in type '{ name: string; age: () => number; }'.
  name: 'TypeScript',
  age: () => new Date().getFullYear() - 2012
});

What's interesting is that , In the example above , If we first assign this object literal to a variable , Then pass the variable to the function to call , that TypeScript Static type detection will only detect the attribute types defined in the parameter type , And inclusively ignore any superfluous attributes , It will not throw a ts(2345) Type error . As shown in the figure below :

let ts = {
    
  id: 2,
  name: 'TypeScript',
  age: () => new Date().getFullYear() - 2012
};
Study(ts); // ok

This is not an oversight or bug, Instead, it deliberately distinguishes between object literals and variables , We call this the case of object literals freshness.

Because this inline interface type is defined at the syntax level with the well-known JavaScript Deconstruction is quite similar , So it's easy to confuse us . Let's compare the effect of mixing deconstruction syntax with inline interface type through the following example .

//  pure JavaScript Deconstructive grammar 
function StudyJavaScript({
    name, age}) {
    
  console.log(name, age);
}
// TypeScript  Mix inner deconstruction with inline type 
function StudyTypeScript({
    name, age}: {
    name: string, age: () => number}) {
    
  console.log(name, age);
}
/**  pure  JavaScript  Deconstructive grammar , Define an alias  */
function StudyJavaScript({
    name: aliasName}) {
     //  Definition name Another name for 
  console.log(aliasName);
}
/** TypeScript */
function StudyTypeScript(language: {
    name: string}) {
    
  // console.log(name); //  Can't print directly name
  console.log(language.name);  
}

From the above code we can see , In the function , Object deconstruction is similar to the syntax for defining interface types , Be careful not to confuse . actually , Defining inline interface types is not reusable , So we should use more interface Keyword to extract reusable interface types .

stay TypeScript in , The syntax of the interface is not very different from other types of languages , Let's see how the interface is defined by the code shown below :

interface test {
    
  name: string;
  age: () => number;
}

In the above code , We defined an interface , It contains an attribute of character type name And an attribute of a function type age . From this, we find that the syntax format of the interface is in interface After the space of the keyword + Interface name , Then, the definitions of attributes and attribute types are wrapped in flower brackets .

In the previous example , Defined by inline parameter types Study Function can be used directly test Interface to define parameters language The type of .

function Study(language: ProgramLanguage) {
    
  console.log(` This is a paragraph created in  ${
      language.age()}  Code years ago , Name is  ${
      language.name}`);
}

We can also constrain other logic by reusing interface type definitions . such as , We define a type as test The variable of TypeScript.

let TypeScript: test;

next , We assign an object literal that meets the interface type convention to this variable , As shown in the following code , At this time, there will be no prompt for type error .

TypeScript = {
    
  name: 'TypeScript',
  age: () => new Date().getFullYear() - 2012
}

And any non-compliance , Will prompt the wrong type . For example, we enter an empty object literal through the code shown below , An object literal type is also prompted {} The lack of name and age Attribute ts(2739) error .

TypeScript = {
    }; // error TS2739: Type '{}' is missing the following properties from type 'test': name, age

Add... As shown in the code below name After attribute , An object literal type will still be prompted { name: string; } Missing required age Attribute ts( 2741) error .

TypeScript = {
    
  name: 'TypeScript'
} // error TS2741: Property 'age' is missing in type '{ name: string; }' but required in type 'test'.

Besides , As shown in the following code , If we put one name The attribute is 2、age The attribute is ‘Jae Wong’ Is assigned to TypeScript , It will give you an error :ts(2322) number Type cannot be assigned to string And errors :ts(2322)string Cannot assign to function type .

TypeScript = {
    
  name: 2, // error TS2322: Type 'number' is not assignable to type 'string'.
  age: 'Jae Wong' // error TS2322: Type 'string' is not assignable to type '() => number'.
}

Or, as in the following example, there is an additional attribute that is not defined by the interface id, It will also prompt a ts(2322) error : Object literal cannot be assigned to test Variable of type TypeScript.

TypeScript = {
    
  name: 'TypeScript',
  age: () => new Date().getFullYear() - 2012,
  id: 1 // error TS2322: Type '{ name: string; age: () => number; id: number; }' is not assignable to type 'test'.Object literal may only specify known properties, and 'id' does not exist in type 'test'.
}

Default attribute

In the previous example , If we want to lack age The object literal of the property can also meet the Convention without throwing type errors , Specifically, in interface types age Property can be defaulted , Then we can add the following after the attribute name ? Syntax to label default properties or methods . As in the following example OptionalTest Interface has a function type that can be defaulted age attribute .

interface test {
    
  name: string;
  age?: () => number;
}

let TypeScript: test;

TypeScript = {
    
  name: 'TypeScript'
}

When the attribute is marked as defaultable , Its type becomes an explicitly specified type and undefined Of types Joint type , For example, in the example test Of age The attribute type becomes as follows :

(() => number) | undefined;

Think about it , Below test2 And test Is it equivalent :

interface test2 {
    
  name: string;
  age: (() => number) | undefined;
}

The answer, of course, is no equivalence , It can be defaulted signify You may not set the attribute key name , The type is undefined signify The attribute key name cannot be defaulted .

Since the value may be undefined , If we need to operate on the properties or methods of the object , You can use Type guard or Add... After the attribute name ? , As shown in the following code :

if (typeof test.age === 'function') {
    
  test.age();
}
test.age?.()

adopt typeof conditional , After making sure age We only call when the property is a function , This avoids runtime prompts age Not a function error .

Read-only property

We may encounter such a scene , Want to lock a write operation on a property or method of an object , For example, in the previous example , Defined TypeScript After a variable ,name The value of a property can no longer be changed . At this time , We can add... Before the attribute name readonly Modifier syntax to mark name Is a read-only property .

interface IReadOnlyTest {
    
  readonly name: string;
  readonly age: (() => number) | undefined;
}

let ReadOnlyTest: IReadOnlyTest = {
    
  name: 'TypeScript',
  age: undefined
}

ReadOnlyTest.name = 'Change' // error TS2540: Cannot assign to 'name' because it is a read-only property.

It should be noted that , This is only read-only at the level of static type detection , In fact, it does not prevent tampering with objects . Because it is translated into JavaScript after ,readonly The modifier will be erased . therefore , Any time instead of directly modifying an object , Let's return a new object , This would be a safer practice .

Define function types

Interface types can also be used to define the types of functions ( Just defining the function type , It does not include the implementation of functions ), Specific examples are as follows .

interface ITest {
    
  name: string;
  age?:() => number
}

interface IStudy {
    
  (language: ITest): void
}

let StudyInterface: IStudy = language => console.log(` I am learning ${
      language.name}`);

StudyInterface({
     name: 'TypeScript' }); //  I am learning TypeScript

In the example , We define an interface type IStudy, It has an anonymous member of a function type , Function parameter type ITest, The type of return value is void, Interface types defined in this format are also called executable types , That is, a function type .

In later code , Declared a StudyInterface Variable of type , And assign it an arrow function as the value . Think about it Context type inference , To the left of the assignment operation IStudy Type can constrain the type of arrow function , So even if we don't explicitly specify function parameters language The type of ,TypeScript It can also be inferred that its type is ITest.

actually , We rarely use interface types to define the types of functions , Use more Inline type or Type the alias Use arrow function syntax to define function types , Specific examples are as follows :

type StudyInterfaceType = (language: ITest) => void

We assign an alias to the arrow function type StudyInterfaceType, It can be reused directly elsewhere StudyInterfaceType, Instead of redefining the new arrow function type definition .

Index signature

In practice , Objects are the most common types of interfaces , These objects have one thing in common , That is, all attribute names 、 The method names are determined .

actually , We often treat objects as Map Mapping uses , For example, the following code example defines the index as Arbitrary number The object of LanguageRankMap And index yes Any string The object of LanguageMap.

let LanguageRankMap = {
    
  1: 'TypeScript',
  2: 'JavaScript'
};

let LanguageMap = {
    
  TypeScript: 2012,
  JavaScript: 1995
};

This is the time , We need to use index signatures to define the object mapping structure mentioned above , And pass [ Index name : type ] The format of the constraint index .

The types of index names are divided into string and number Two kinds of , By means of LanguageRankInterface and LanguageYearInterface Two interfaces , We can describe objects whose index is any number or any string .

interface LanguageRankInterface {
    
  [rank: number]: string;
}

interface LanguageYearInterface {
    
  [name: string]: number;
}

let LanguageRankMap: LanguageRankInterface = {
    
  1: 'TypeScript',
  2: 'JavaScript',
  'JaeWong': 3 // error TS2322: Type '{ 1: string; 2: string; JaeWong: number; }' is not assignable to type 'LanguageRankInterface'.Object literal may only specify known properties, and ''JaeWong'' does not exist in type 'LanguageRankInterface'.
}

let LanguageMap: LanguageYearInterface = {
    
  TypeScript: 2012,
  JavaScript: 1995,
  2023: 2021
}

Be careful : In the example above , When numbers are used as object indexes , Its type can be compatible with numbers , It can also be compatible with strings , This is related to JavaScript Act in unison . therefore , Use 0 or ‘0’ When indexing objects , The two are equivalent .

Again , We can use readonly Annotation index signature , At this point, set the corresponding property to read-only , As shown in the following code :

interface LanguageRankInterface {
    
  readonly [rank: number]: string;
}
  
interface LanguageYearInterface {
    
  readonly [name: string]: number;
}

In the example above ,LanguageRankInterface and LanguageYearInterface Any number or string type attribute is read-only .

Be careful : Although attributes can be mixed with index signatures , But the type of the attribute must be the type of the corresponding numeric index or string index A subset of , Otherwise, an error message will appear .

Take the following example :

interface StringMap {
    
  [prop: string]: number;
  age: number;
  name: string; // ts(2411) name  Attribute  string  Type cannot be assigned to string index type  number
}

interface NumberMap {
    
  [rank: number]: string;
  1: string;
  0: number; // ts(2412) 0  Attribute  number  Type cannot be assigned to numeric index type  string
}

In the example above , Because of the interface StringMap attribute name The type of string Not its corresponding string index (prop: string) type number Subset , So it will prompt an error . Empathy , Because of the interface NumberMap attribute 0 The type of number Not its corresponding numerical index (rank: number) type string Subset , So it will also prompt an error .

in addition , Because of the particularity of numeric type index mentioned above , So we can't restrict that the numeric index attribute and the string index attribute have different types , Specific examples are as follows :

interface LanguageRankInterface {
    
  [rank: number]: string; // // ts(2413)  Digital index type  string  Type cannot be assigned to string index type  number
  [prop: string]: number;
}

Here we define LanguageRankInterface Digital index of rank The type is string, And the defined string index prop The type of number Are not compatible , So there will be a prompt ts(2413) error .

Inheritance and implementation

stay TypeScript in , The interface type can Inherit and be inherited , For example, we can use the following extends Keyword implements interface inheritance .

interface ITest {
    
  name: string;
  age?:() => number;
}

interface DynamicTest extends ITest {
    
  rank: number //  Define new properties 
}

interface TypeTest extends ITest {
    
  typeChecker: string; //  Define new properties 
}
//  Inherit multiple 
interface TypeScript extends DynamicTest, TypeTest {
    
  name: 'TypeScript';
}

In the example above , First, we defined two inheritance ITest The interface of DynamicTest and TypeTest, They will inherit ITest All attribute definitions . And then it defines that it also inherits DynamicTest and TypeTest The interface of TypeScript, It will inherit DynamicTest and TypeTest All attribute definitions , And use the same name name Attribute definitions override inherited name Attribute definitions .

Be careful : We can only override inherited properties with compatible types , As shown in the following code :

interface WrongType extends ITest {
    
  name: number;
}

In the above code , because ITest Of name The attribute is string type ,WrongType Of name The attribute is number, The two are incompatible , So you can't inherit , It will also prompt a ts(6196) error .

We can use interface types to constrain classes , Conversely, you can use classes to implement interfaces , What is the relationship between the two ? here , We do this by using implements Keyword describes the relationship between a class and an interface .

interface ITest {
    
  name: string;
  age:() => number;
}

class TestClass implements ITest {
    
  name: string = '';
  age = () => new Date().getFullYear() - 2012
}

In the above code , class TestClass Realized ITest Interface agreement name、age Other properties and methods , If we remove name perhaps age The implementation of the , A type error will be prompted .

Type Type the alias

One function of interface types is to pull out inline types , So as to realize type reusability . Actually , We can also use the type alias to receive the extracted inline types for reuse .

here , We can do this by type Alias name = The type definition To define type aliases :

type TestType = {
    
  name: string;
  age:() => number;
}

In the above code , At first glance, it looks a bit like defining variables , It's just that here we put let 、const 、var The keyword was changed to type only .

Besides , For scenarios that cannot be overwritten by interface types , such as Combination type Cross type , We can only use type aliases to receive , As shown in the following code :

interface ITest {
    
  name: string;
  age:() => number;
}
//  union 
type MixedType = string | number;
//  cross 
type IntersectionType = {
     id: number; name: string; } & {
     age: number; name: string; };
//  Extract the interface attribute type 
type AgeType = ITest['age'];

In the above code , We define a IntersectionType Type the alias , Represents the type where two anonymous interface types intersect ; At the same time, it defines a AgeType Type the alias , Represents extracted ITest age Type of attribute .

Be careful : Type the alias , As the name suggests , That is, we just give the type a new name , Instead of creating a new type .

Interface And Type The difference between

Through the above introduction , We already know that type aliases can be used to replace interface type annotations , Does this mean that the two are equivalent in the corresponding scenario ?

actually , In most cases, using interface types and type aliases is equivalent , But in some specific scenarios, there are still great differences between the two . such as , Duplicate defined interface type , Its properties will be superimposed , This feature makes it extremely convenient for us to control global variables 、 Extend the type of the third-party library , As shown in the following code :

interface ITest {
    
  id: number;
}

interface ITest {
    
  name: string;
}

let test: ITest = {
    
  id: 1,
  name: 'test'
}

In the above code , Two... Defined successively ITest Interface properties are superimposed together , At this point, we can assign to test A variable contains both id and name Object of property .

however , If we define type aliases repeatedly , As shown in the following code , You will be prompted with ts(2300) error :

// ts(2300)  Repeated signs 
type ITest {
    
  id: number;
}
// ts(2300)  Repeated signs 
type ITest {
    
  name: string;
}

let test: ITest = {
    
  id: 1,
  name: 'test'
}
原网站

版权声明
本文为[Large American without sugar]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/160/202206090433596808.html