Typescript official manual translation plan [v]: object type

Time:2022-5-9
  • explain: at present, there is no Chinese translation of the latest official documents of typescript on the Internet, so there is such a translation plan. Because I am also a beginner of typescript, I can’t guarantee the 100% accuracy of translation. If there are errors, please point them out in the comment area;
  • Translation content: tentative translationTypeScript HandbookLater, other parts of the translated documents will be supplemented when available;
  • Project addressTypeScript-Doc-Zh, if it helps you, you can click a star~

Official document address of this chapter:Object Types

object type

In JavaScript, the most basic way to group and transfer data is to use objects. In typescript, we represent it by object type.

As you saw earlier, object types can be anonymous:

function greet(person: { name: string; age: number }) {
  return "Hello " + person.name;
}

Alternatively, you can use an interface to name:

interface Person {
  name: string;
  age: number;
}
 
function greet(person: Person) {
  return "Hello " + person.name;
}

Or use a type alias:

type Person = {
  name: string;
  age: number;
};
 
function greet(person: Person) {
  return "Hello " + person.name;
}

In the above example, the object accepted by the function we wrote containsnameProperty (type must be)string)AndageProperty (type must be)number)。

Attribute modifier

Each attribute in the object type can specify something: attribute type, whether the attribute is optional, and whether the attribute is writable.

optional attribute

Most of the time, we will find that the object we deal with may have an attribute set. In this case, we can add the following after the names of these attributes?Symbols and mark them as optional attributes.

interface PaintOptions {
  shape: Shape;
  xPos?: number;
  yPos?: number;
}
 
function paintShape(opts: PaintOptions) {
  // ...
}
 
const shape = getShape();
paintShape({ shape });
paintShape({ shape, xPos: 100 });
paintShape({ shape, yPos: 100 });
paintShape({ shape, xPos: 100, yPos: 100 });

In this case,xPosandyPosAre optional properties. We can choose to provide or not provide these two properties, so the abovepaintShapeAll calls are valid. What selectivity really wants to express is that if this property is set, it’s better to have a specific type.

These properties are also accessible – but if enabledstrictNullChecks, typescript will prompt us that these properties may beundefined

function paintShape(opts: PaintOptions) {
  let xPos = opts.xPos;
                  ^^^^
            // (property) PaintOptions.xPos?: number | undefined
  let yPos = opts.yPos;
                  ^^^^   
            // (property) PaintOptions.yPos?: number | undefined
  // ...
}

In JavaScript, even if a property has never been set, we can still access it — the value isundefined。 We can be rightundefinedSpecial treatment shall be made in this case.

function paintShape(opts: PaintOptions) {
  let xPos = opts.xPos === undefined ? 0 : opts.xPos;
      ^^^^ 
    // let xPos: number
  let yPos = opts.yPos === undefined ? 0 : opts.yPos;
      ^^^^ 
    // let yPos: number
  // ...
}

Note that this mode of setting default values for unspecified values is common, so JavaScript provides syntax level support.

function paintShape({ shape, xPos = 0, yPos = 0 }: PaintOptions) {
  console.log("x coordinate at", xPos);
                                 ^^^^ 
                            // (parameter) xPos: number
  console.log("y coordinate at", yPos);
                                 ^^^^ 
                            // (parameter) yPos: number
  // ...
}

Here we arepaintShapeParameters usedDeconstruction model, but also forxPosandyPosProvidedDefault value。 Now?xPosandyPosstaypaintShapeThere must be a value in the function body, and these two parameters are still optional when calling the function.

Note that there is currently no way to use type annotations in Deconstruction patterns. This is because the following syntax has other semantics in JavaScript

function draw({ shape: Shape, xPos: number = 100 /*...*/ }) {
  render(shape);
        ^^^^^^
     // Cannot find name 'shape'. Did you mean 'Shape'?
  render(xPos);
         ^^^^^
    // Cannot find name 'xPos'.
}

In an object deconstruction mode,shape: ShapeIndicates captureshapeProperty and redefine it as aShapeLocal variable for. Similarly,xPos: numberA file namednumberThe value of the variable is the value in the parameterxPosValue of.

useMapping modifierOptional attributes can be removed.

Read only attribute

In typescript, we can mark the attribute asreadonly, indicating that this is a read-only property. Although this does not change any behavior at runtime, it is marked asreadonlyThe property of cannot be overridden during type checking.

interface SomeType {
  readonly prop: string;
}
 
function doSomething(obj: SomeType) {
  //Can read obj prop
  console.log(`prop has the value '${obj.prop}'.`);
 
  //But it cannot be reassigned
  obj.prop = "hello";
// Cannot assign to 'prop' because it is a read-only property.
}

usereadonlyModifiers do not necessarily mean that a value is completely immutable — or, in other words, that its contents are immutable.readonlyOnly indicates that the property itself cannot be overridden.

interface Home {
  readonly resident: { name: string; age: number };
}
 
function visitForBirthday(home: Home) {
  //We can read and update home Resident property
  console.log(`Happy birthday ${home.resident.name}!`);
  home.resident.age++;
}
 
function evict(home: Home) {
  //However, we cannot override the resident attribute itself of the home type
  home.resident = {
       ^^^^^^^^
// Cannot assign to 'resident' because it is a read-only property.
    name: "Victor the Evictor",
    age: 42,
  };
}

understandreadonlyThe meaning of is very important. In the process of developing with typescript, it can effectively indicate how an object should be used. When typescript checks whether two types are compatible, it does not consider whether their properties are read-only, so read-only properties can also be modified through aliases.

interface Person {
  name: string;
  age: number;
}
 
interface ReadonlyPerson {
  readonly name: string;
  readonly age: number;
}
 
let writablePerson: Person = {
  name: "Person McPersonface",
  age: 42,
};
 
//It can be executed normally
let readonlyPerson: ReadonlyPerson = writablePerson;
 
console. log(readonlyPerson.age); //  Print 42
writablePerson.age++;
console. log(readonlyPerson.age); //  Print 43

useMapping modifierYou can remove read-only attributes.

Index signature

Sometimes you can’t know the names of all attributes of a type in advance, but you know the types of these attribute values. In this case, you can use index signatures to describe the types of possible values. for instance:

interface StringArray {
    [index: number]: string
}
const myArray: StringArray = getStringArray();
const secondItem = myArray[1];
      ^^^^^^^^^^    
     // const secondItem: string

In the code above,StringArrayInterface has an index signature. This index signature indicates that whenStringArraycovernumberWhen the value of type is indexed, it will returnstringValue of type.

The attribute type of an index signature is eitherstring, ornumber

Of course, you can also support two types at the same time

But the premise is that the type returned by the numeric index must be a subtype of the type returned by the string index. This is because when you use numeric values to index object properties, JavaScript actually converts the values into strings first. This means using100Index and use"100"(string) for indexing, the effect is the same, so the two must be consistent.

interface Animal {
  name: string;
}
 
interface Dog extends Animal {
  breed: string;
}
 
// Error: indexing with a numeric string might get you a completely separate type of Animal!
interface NotOkay {
  [x: number]: Animal;
// 'number' index type 'Animal' is not assignable to 'string' index type 'Dog'.
  [x: string]: Dog;
}

However, if the type described by the index signature itself is the union type of each attribute type, different types of attributes are allowed:

interface NumberOrStringDictionary {
  [index: string]: number | string;
  length: number; //  Length is a number and can be
  name: string; //  Name is a string. You can
}

Finally, you can set the index signature to be read-only, which can prevent the attribute of the corresponding index from being re assigned:

interface ReadonlyStringArray {
  readonly [index: number]: string;
}
 
let myArray: ReadonlyStringArray = getReadOnlyStringArray();
myArray[2] = "Mallory";
// Index signature in type 'ReadonlyStringArray' only permits reading.

The index signature cannot be changed because it is set to read-onlymyArray[2]Value of.

Expansion type

It is a common requirement to develop a more specific type based on a type. For example, we have oneBasicAddressType is used to describe the address information required to mail a letter or package.

interface BasicAddress {
    name?: string;
    street: string;
    city: string;
    country: string;
    postalCode: string;
}

Usually, this information is enough, but if there are many units in the building of an address, the address information usually needs to have a unit number. At this time, we can use oneAddressWithUnitTo describe the address information:

interface AddressWithUnit {
    name?: string;
    unit: string;
    street: string;
    city: string;
    country: string;
    postalCode: string;
}

Of course, this is no problem, but the disadvantage is that although we just add a domain, we have to write it repeatedlyBasicAddressAll domains in. We might as well expand the original methodBasicAddressType, and addAddressWithUnitUnique new domain.

interface BasicAddress {
  name?: string;
  street: string;
  city: string;
  country: string;
  postalCode: string;
}
 
interface AddressWithUnit extends BasicAddress {
  unit: string;
}

Following an interfaceextendsKeywords allow us to efficiently copy members from other named types and add any new members we want. This has a great effect on reducing the type declaration statements we have to write, and it can also indicate the relationship between several different type declarations with the same attributes. for instance,AddressWithUnitThere is no need to write repeatedlystreetProperty, and becausestreetAttribute fromBasicAddress, developers can know that there is a connection between the two types.

Interfaces can also be extended from multiple types:

interface Colorful {
  color: string;
}
 
interface Circle {
  radius: number;
}
 
interface ColorfulCircle extends Colorful, Circle {}
 
const cc: ColorfulCircle = {
  color: "red",
  radius: 42,
};

Cross type

Interfaces allow us to build new types by extending existing types. Typescript also provides another structure called “cross type”, which can be used to combine existing object types.

adopt&Operator can define a crossover type:

interface Colorful {
    color: string;
}
interface Circle {
    radius: number;
}
type ColorfulCircle = Colorful & Circle;

Here, we combineColorfulandCircleType, creating a new type that hasColorfulandCircleAll members of.

function draw(circle: Colorful & Circle) {
  console.log(`Color was ${circle.color}`);
  console.log(`Radius was ${circle.radius}`);
}
 
//Can run
draw({ color: "blue", radius: 42 });
 
//Cannot run
draw({ color: "red", raidus: 42 });
/*
Argument of type '{ color: string; raidus: number; }' is not assignable to parameter of type 'Colorful & Circle'.
  Object literal may only specify known properties, but 'raidus' does not exist in type 'Colorful & Circle'. Did you mean to write 'radius'?
*/

Interface vs cross type

At present, we have learned that two similar but different types can be combined in two ways. Using the interface, we canextendsClause expands the original type; Using cross types, we can achieve a similar effect, and use type aliases to name new types. The essential difference between the two lies in the way they handle conflicts, and this difference is usually the main reason why we choose one of the type aliases of interfaces and cross types.

Generic object type

Suppose we have oneBoxType, which may contain values of any type:stringnumberGiraffeWait.

interface Box {
    contents: any;
}

Now?contentsThe type of the property isany, of course, it’s OK, but useanyMay cause type safety problems.

So we can useunknown。 But that means as long as we knowcontentsWe need to do a preventive check or use error prone type assertions.

interface Box {
  contents: unknown;
}
 
let x: Box = {
  contents: "hello world",
};
 
//We can check x.contents
if (typeof x.contents === "string") {
  console.log(x.contents.toLowerCase());
}
 
//Or use type assertions
console.log((x.contents as string).toLowerCase());

Another way to ensure type safety is for each different type ofcontents, create differentBoxType.

interface NumberBox {
  contents: number;
}
 
interface StringBox {
  contents: string;
}
 
interface BooleanBox {
  contents: boolean;
}

But this means that we need to create different functions or overloads of functions in order to operate different functionsBoxType.

function setContents(box: StringBox, newContents: string): void;
function setContents(box: NumberBox, newContents: number): void;
function setContents(box: BooleanBox, newContents: boolean): void;
function setContents(box: { contents: any }, newContents: any) {
  box.contents = newContents;
}

This leads to a lot of boilerplate code. Moreover, we may introduce new types and overloads later, which is somewhat redundant. After all, ourBoxThe overload type is essentially the same as the overload type.

You might as well use another way, that is, letBoxType declares a type parameter and uses generics.

interface Box<Type> {
    contents: Type;
}

You can interpret this code as“BoxThe type of isType, itscontentsThe type of isType”。 Then, when we quoteBoxWe need to pass a type parameter to replaceType

let box: Box<string>;

If putBoxAs a template of actual type, thenTypeIs a placeholder that will be replaced by other types. When typescript seesBox<string>When it does, it willBox<Type>All inTypeReplace withstring, get a similar{ contents: string }Object. let me put it another way,Box<string>As in the previous exampleStringBoxIs equivalent.

interface Box<Type> {
  contents: Type;
}
interface StringBox {
  contents: string;
}
 
let boxA: Box<string> = { contents: "hello" };
boxA.contents;
     ^^^^^^^^   
    // (property) Box<string>.contents: string
 
let boxB: StringBox = { contents: "world" };
boxB.contents;
     ^^^^^^^^   
    // (property) StringBox.contents: string

becauseTypeCan be replaced by any type, soBoxReusable. That means when wecontentsWhen a new type is needed, there is no need to declare a new type at allBoxType (although there is no problem doing so).

interface Box<Type> {
    contents: Type;
}
interface Apple {
    //...
}
//Same as {contents: Apple}
type AppleBox = Box<Apple>;

This also means that by usingGeneric Functions , we can completely avoid overloading.

function setContents<Type>(box: Box<Type>, newContents: Type) {
    box.contents = newContents;
}

It is worth noting that type aliases can also use generics. Previously definedBox<Type>Interface:

interface Box<Type> {
    contents: Type;
}

Can be overridden to the following type alias:

type Box<Type> = {
    contents: Type;
};

Unlike interfaces, type aliases can not only be used to describe object types. So we can also write other generic tool types using type aliases.

type OrNull<Type> = Type | null;
type OneOrMany<Type> = Type | Type[];
type OneOrManyOrNull<Type> = OrNull<OneOrMany<Type>>;
     ^^^^^^^^^^^^^^
    //type OneOrManyOrNull<Type> = OneOrMany<Type> | null
type OneOrManyOrNullStrings = OneOrManyOrNull<string>;
     ^^^^^^^^^^^^^^^^^^^^^^          
    // type OneOrManyOrNullStrings = OneOrMany<string> | null         

We’ll come back to type aliases later.

Array type

Generic object types are usually container types that work independently of the types of members they contain. Data structures work ideally in this way and can be reused even if the data types are different.

In fact, in this manual, we have been dealing with a generic type, which isArray(array) type. We wrotenumber[]Type orstring[]Types, actuallyArray<number>andArray<string>Short for.

function doSomething(value: Array<string>) {
  // ...
}
 
let myArray: string[] = ["hello", "world"];
 
//The following two writing methods are OK!
doSomething(myArray);
doSomething(new Array("hello", "world"));

Just like the one in frontBoxThe same type,ArrayIt is also a generic type:

interface Array<Type> {
  /**
   *Gets or sets the length of the array
   */
  length: number;
 
  /**
   *Removes the last element of the array and returns it
   */
  pop(): Type | undefined;
 
  /**
   *Adds a new element to the array and returns the new length of the array
   */
  push(...items: Type[]): number;
 
  // ...
}

Modern JavaScript also provides other data structures that are also generic, such asMap<K,V>Set<T>andPromise<T>。 Which means,MapSetandPromiseTheir representations enable them to handle arbitrary sets of types.

Read only array type

ReadonlyArray(read only array) is a special type that describes an array that cannot be modified.

function doStuff(values: ReadonlyArray<string>) {
  //We can read values 
  const copy = values.slice();
  console.log(`The first value is ${values[0]}`);
 
  // ... However, values cannot be modified
  values.push("hello!");
         ^^^^
        // Property 'push' does not exist on type 'readonly string[]'.
}

Like attributereadonlyLike modifier, it is mainly a tool used to indicate intention. When we see a function returnReadonlyArrayIt means that we do not intend to modify this array; When we see a function acceptReadonlyArrayAs a parameter, it means that we can pass any array to this function without worrying about the array being modified.

andArraydissimilarity,ReadonlyArrayNo corresponding constructor can be used.

new ReadonlyArray("red", "green", "blue");
    ^^^^^^^^^^^^^
// 'ReadonlyArray' only refers to a type, but is being used as a value here.

However, we can put ordinaryArrayAssign toReadonlyArray

const roArray: ReadonlyArray<string> = ["red","green","blue"];

Typescript is not onlyArray<Type>Abbreviations are providedType[], also forReadonlyArray<Type>Abbreviations are providedreadonly Type[]

function doStuff(values: readonly string[]) {
  //We can read values
  const copy = values.slice();
  console.log(`The first value is ${values[0]}`);
 
  // ... However, values cannot be modified
  values.push("hello!");
        ^^^^^
      // Property 'push' does not exist on type 'readonly string[]'.
}

The last thing to note is, andreadonlyAttribute modifiers are different, ordinaryArrayandReadonlyArrayThe assignability between is not bidirectional.

let x: readonly string[] = [];
let y: string[] = [];
 
x = y;
y = x;
^
// The type 'readonly string[]' is 'readonly' and cannot be assigned to the mutable type 'string[]'.

Tuple type

Tuple type is a special typeArrayType, its number of elements and the corresponding type of each element are clear,

type StringNumberPair = [string, number];

here,StringNumberPairIs a containingstringType andnumberTuple type of type. andReadonlyArraySimilarly, it has no corresponding runtime representation, but it is still very important for typescript. For type systems,StringNumberPairDescribes such an array: the subscript is0The location of contains astringValue of type, subscript1The location of contains anumberValue of type.

function doSomething(pair: [string, number]) {
  const a = pair[0];
        ^
     //const a: string
  const b = pair[1];
        ^        
     // const b: number
  // ...
}
 
doSomething(["hello", 42]);

If the subscript is out of bounds when accessing tuple elements, an error will be thrown.

function doSomething(pair: [string, number]) {
  // ...
 
  const c = pair[2];
                ^    
// Tuple type '[string, number]' of length '2' has no element at index '2'.
}

We can also use JavaScript array deconstruction toDeconstruction tuple

function doSomething(stringHash: [string, number]) {
  const [inputString, hash] = stringHash;
 
  console.log(inputString);
              ^^^^^^^^^^^    
        // const inputString: string
 
  console.log(hash);
              ^^^^   
            // const hash: number
}

Tuple types are useful in highly convention based APIs because the meaning of each element is “explicit”. This gives us the flexibility to give variables any name when we deconstruct tuples. In the above example, we can give any name to the elements with subscripts 0 and 1.

However, how can it be “clear”? Every developer has different opinions. Maybe you need to reconsider whether it would be better to use objects with description properties in the API.

In addition to length checking, a simple tuple type like this is actually equivalent to an object that declares the properties of a specific subscript and contains the properties of a numeric literal typelengthProperties.

interface StringNumberPair {
  //Specific properties
  length: 2;
  0: string;
  1: number;
 
  //Other members of array < string | number > type
  slice(start?: number, end?: number): Array<string | number>;
}

Another thing that you may be interested in is that tuple types can also have optional elements, just add after an element type?。 Optional tuple elements can only appear at the end and affect the length of the type.

type Either2dOr3d = [number, number, number?];
 
function setCoordinate(coord: Either2dOr3d) {
  const [x, y, z] = coord;
               ^
            // const z: number | undefined
 
  console.log(`Provided coordinates had ${coord.length} dimensions`);
                                             ^^^^^^                                
                                        // (property) length: 2 | 3
}

Tuples can also use the expansion operator, which must be followed by an array or tuple.

type StringNumberBooleans = [string, number, ...boolean[]];
type StringBooleansNumber = [string, ...boolean[], number];
type BooleansStringNumber = [...boolean[], string, number];
  • StringNumberBooleansRepresents a tuple whose first two elements arestringandnumberType, followed by several at the same timebooleanElement of type.
  • StringBooleansNumberRepresents a tuple whose first element isstringType, followed by severalbooleanElement of type. The last element isnumber
  • BooleansStringNumberRepresents such a tuple: it is preceded by several tuplesbooleanThe last two elements arestringandnumberType.

Tuples that use the expansion operator have no explicit length — what can be explicit is that they have elements of corresponding types in different positions.

const a: StringNumberBooleans = ["hello", 1];
const b: StringNumberBooleans = ["beautiful", 2, true];
const c: StringNumberBooleans = ["world", 3, true, false, true, false, true];

Why are optional elements and expansion operators useful? Because it allows typescript to map parameter lists to tuples. stayRemaining parameters and expansion operatorsTuples can be used in, so the following code:

function readButtonInput(...args: [string, number, ...boolean[]]) {
  const [name, version, ...input] = args;
  // ...
}

Is equivalent to this Code:

function readButtonInput(name: string, version: number, ...input: boolean[]) {
  // ...
}

Sometimes, we use the remaining parameters to accept several parameters with variable quantity. At the same time, it is required that the parameters should not be less than a certain quantity, and we do not want to introduce intermediate variables for this purpose. At this time, the above writing method is very convenient.

Read only tuple type

One last thing to note about tuple types is that tuple types can also be read-only by addingreadonlyModifier, we can declare a read-only tuple type — just like the abbreviation of read-only array.

function doSomething(pair: readonly [string, number]) {
  // ...
}

Any property of a read-only tuple cannot be overridden in typescript.

function doSomething(pair: readonly [string, number]) {
  pair[0] = "hello!";
       ^
// Cannot assign to '0' because it is a read-only property.
}

In most code, tuples do not need to be modified after they are created, so it is a good default to annotate tuples as read-only types. It is also important to useconstThe array literal of the assertion will be inferred as a read-only tuple.

let point = [3, 4] as const;
 
function distanceFromOrigin([x, y]: [number, number]) {
  return Math.sqrt(x ** 2 + y ** 2);
}
 
distanceFromOrigin(point);
                 ^^^^^^     
/* 
Argument of type 'readonly [3, 4]' is not assignable to parameter of type '[number, number]'.
  The type 'readonly [3, 4]' is 'readonly' and cannot be assigned to the mutable type '[number, number]'. 
*/

here,distanceFromOriginThe element of the tuple is not modified, but it expects to accept a variable tuple. becausepointThe type of is inferred asreadonly [3,4], so it and[number, number]Is incompatible because the latter cannot be guaranteedpointThe element will not be modified.

Recommended Today

Unity phone touch screen input

1. Get screen input New scriptTouchInput, add to maincamera public class TouchInput : MonoBehaviour { public LayerMask touchInputMask; // Declare the level, and the ray is only detected with the set level private Camera myCamera; // Declaration camera private List touchList = new List(); // To save the currently pressed object, you need to add […]