“This is the 10th day of my participation in the August Gwen Challenge.


First, simple base types

Before we talk about TypeScript data types, let’s look at the basic syntax for defining data types in TypeScript.

Syntactically, TypeScript for default type annotations is identical to JavaScript. Therefore, you can think of TypeScript code writing as adding type annotations to JavaScript code.

In TypeScript syntax, annotation of types is done through type postscript syntax: “Variable: type”

let num = 996
let num: number = 996
Copy the code

The first line of the above code is syntactically compatible with both JavaScript and TypeScript syntax. Num is implicitly defined as a number type, so we cannot assign num to any other type. The second line of code explicitly declares that num is a numeric type. Similarly, num cannot be assigned to any other type, otherwise an error will be reported.

In JavaScript, primitive types are non-object data types with no methods, including: number, Boolean, string, NULL, undefined, symbol, bigInt.

Their corresponding TypeScript types are as follows:

JavaScript primitive base type TypeScript type
number number
boolean boolean
string string
null null
undefined undefined
symbol symbol
bigInt bigInt

Note the difference between number and number: TypeScript uses number when specifying types, which is the TypeScript type keyword. Number, which is JavaScript’s native constructor, is used to create values of numeric type. String, Boolean, and so on are TypeScript type keywords, not JavaScript syntax.

1. number

In TypeScript, like JavaScript, all numbers are floating point numbers, so there is only one number type.

TypeScript also supports new binary and octal literals in ES6, so TypeScript supports two, eight, 10, and 16 base values:

let num: number;
num = 123;
num = "123";     // Error cannot assign type "123" to type "number"
num = 0b1111011; // Binary 123
num = 0o173;     // octal 123
num = 0x7b;      // hexadecimal 123
Copy the code

2. string

String types can be wrapped in single and double quotes, but if you use the Tslint rule, quotes are checked, and the use of single or double quotes can be configured in the Tslint rule. In addition, you can easily concatenate variables and strings using template strings in ES6.

let str: string = "Hello World";
str = "Hello TypeScript";
const first = "Hello";
const last = "TypeScript";
str = `${first} ${last}`;
console.log(str) // Result: Hello TypeScript
Copy the code

3. boolean

The value of a variable of type Boolean can only be true or false. Alternatively, the value assigned to a Boolean can be an expression that evaluates to a Boolean:

let bool: boolean = false;
bool = true;

let bool: boolean=!!!!!0
console.log(bool) // false
Copy the code

4. A null, and undefined

In JavaScript, undefined and NULL are two basic data types. In TypeScript, both of these have their own types, undefined and NULL, which means they are both actual values and types. These two types are not very useful in practice.

let u: undefined = undefined;
let n: null = null;
Copy the code

Note that the first line of code may report a TSLint error: Unnecessary initialization to ‘undefined’, which means you cannot assign undefined to a variable. But it’s actually perfectly fine to assign undefined, so if you want to rationalize your code, you can configure tsLint to set “no-unnecessary-initializer” to false.

By default, undefined and NULL are subtypes of all types and can be assigned to any type of value. That is, undefined can be assigned to void or number. When set to “strictNullChecks”: true in the tsconfig.json “compilerOptions”, this is strictly enforced. Undefined and NULL can only be assigned to themselves or to void types. This also avoids errors.

5. bigInt

BigInt is a new data type introduced in ES6. It is a built-in object that provides a way to represent integers greater than 2-1. BigInt can represent arbitrarily large integers.

BigInt is used to safely store and manipulate large integers, even if the Number is outside the safe integer range that the JavaScript constructor Number can represent.

We know that the use of double-precision floating-point numbers in JavaScript results in limited precision, such as number. MAX_SAFE_INTEGER, which gives the largest possible integer that can be safely incremented, namely 2-1. Here’s an example:

const max = Number.MAX_SAFE_INTEGER;
const max1 = max + 1
const max2 = max + 2
max1 === max2     // true
Copy the code

As you can see, the result is true, which is the problem of going beyond the close reading range, and BigInt is designed to solve this kind of problem:

const max = BigInt(Number.MAX_SAFE_INTEGER);
const max1 = max + 1n
const max2 = max + 2n
max1 === max2    // false
Copy the code

BigInt(number) is used to convert the number to BigInt. If the number is BigInt, add n to the number.

In TypeScript, the number type and BigInt both represent numbers, but they are completely different:

declare let foo: number;
declare let bar: bigint;
foo = bar; // error: Type 'bigint' is not assignable to type 'number'.
bar = foo; // error: Type 'number' is not assignable to type 'bigint'.
Copy the code

6. symbol

We seldom use symbol, so we may not know much about it. Here we will talk about symbol in detail.

(1) Basic use of symbol

Symbol is a basic data type added to ES6 to represent unique values that can be generated through the Symbol constructor.

const s = Symbol(a);typeof s; // symbol
Copy the code

Note: Symbol cannot be preceded by the new keyword. This call creates a unique value of type Symbol.

You can use the Symbol method to create a Symbol type value by passing in an argument that needs to be a string. If the passed argument is not a string, the toString method is automatically called to convert the passed argument to a string:

const s1 = Symbol("TypeScript"); 
const s2 = Symbol("Typescript"); 
console.log(s1 === s2); // false
Copy the code

The third line of the code above might report an error: This condition will always return ‘false’ since the types ‘unique symbol’ and ‘unique symbol’ have no overlap. This is because the compiler detects that s1 === s2 is always false, so the compiler warns that this code is redundant and recommends optimization.

The Symbol method is used to create two Symbol objects. Both symbols are passed the same string, but both symbols are still false. This means that the Symbol method returns a unique value. The Symbol method passes in this string, which is convenient for us to distinguish Symbol values. We can call the toString method of the symbol value to convert it to a string:

const s1 = Symbol("Typescript"); 
console.log(s1.toString());  // 'Symbol(Typescript)'
console.log(Boolean(s));     // true 
console.log(! s);// false
Copy the code

Using symbol in TypeScript specifies a value of type symbol:

let a: symbol = Symbol(a)Copy the code

TypeScript also has a unique Symbol type, which is a subtype of Symbol. Values of this type can only be created by symbol () or symbol.for (), or variables can be of that type by specifying a type. Values of this type can only be used for constant definitions and for attribute names. Note that defining unique Symbol values must be const instead of let. Here’s an example of using the Symbol value as a property name in TypeScript:

const key1: unique symbol = Symbol(a)let key2: symbol = Symbol(a)const obj = {
    [key1]: 'value1',
    [key2]: 'value2'
}
console.log(obj[key1]) // value1
console.log(obj[key2]) // Error type "symbol" cannot be used as an index type.
Copy the code

(2) Symbol is the attribute name

In ES6, object attributes support expressions and can be used with a variable as the attribute name. This is useful for code simplification. Expressions must be enclosed in braces:

let prop = "name"; 
const obj = { 
  [prop]: "TypeScript" 
};
console.log(obj.name); // 'TypeScript'
Copy the code

Symbol can also be used as an attribute name because the value of symbol is unique, so when used as an attribute name, it does not duplicate any other attribute name. When accessing this property, only the symbol value can be used (it must be accessed using square brackets) :

let name = Symbol(a);let obj = { 
  [name]: "TypeScript" 
};
console.log(obj); // { Symbol(): 'TypeScript' }

console.log(obj[name]); // 'TypeScript' 
console.log(obj.name);  // undefined
Copy the code

When accessed using obj.name, it is actually the string name, which is the same as accessing an attribute name of a normal string type. Square brackets must be used to access an attribute of type Symbol. The name in square brackets is the variable name of type symbol we define.

(3) Symbol attribute name traversal

Use the Symbol type value for the attribute name. This attribute is not for… In traversing the, also not be Object. The keys (), the Object. The getOwnPropertyNames (), JSON. Stringify () method to get:

const name = Symbol("name"); 
const obj = { 
  [name]: "TypeScript".age: 18 
};
for (const key in obj) { 
  console.log(key); 
}  
// => 'age' 
console.log(Object.keys(obj));  // ['age'] 
console.log(Object.getOwnPropertyNames(obj));  // ['age'] 
console.log(JSON.stringify(obj)); // '{ "age": 18 }
Copy the code

Although these methods can access to the Symbol type of attribute names, but the properties of Symbol type is not the private property, you can use the Object. The getOwnPropertySymbols method for objects of all types of Symbol attribute names:

const name = Symbol("name"); 
const obj = { 
  [name]: "TypeScript".age: 18 
};
const SymbolPropNames = Object.getOwnPropertySymbols(obj); 
console.log(SymbolPropNames); // [ Symbol(name) ] 
console.log(obj[SymbolPropNames[0]]); // 'TypeScript' 
Copy the code

In addition to this method, we can also use ES6’s Reflect static method reflect. ownKeys, which returns all property names, as well as Symbol names:

const name = Symbol("name"); 
const obj = { 
  [name]: "TypeScript".age: 18 
};
console.log(Reflect.ownKeys(obj)); // [ 'age', Symbol(name) ]
Copy the code

(4) static method

Symbol contains two static methods, for and keyFor.

For () with a 1) Symbol.

Values of Symbol type created with Symbol are unique. Passing in a string using the symbol. for method first checks for the Symbol value created by calling the symbol. for method with the string. If so, return the value; If not, a new one is created using the string. The symbol value created using this method is registered at the global scope.

const iframe = document.createElement("iframe"); 
iframe.src = String(window.location); 
document.body.appendChild(iframe); 

iframe.contentWindow.Symbol.for("TypeScript") = = =Symbol.for("TypeScript"); // true // Note: This code is fine if you are in JavaScript, but in TypeScript development it may cause an error: the attribute "Symbol" does not exist on type "Window". Ifame. ContentWindow is of type Window. However, the TypeScript declaration file does not include the Symbol field in the definition of Window.
Copy the code

In the above code, we create an iframe node and place it in the body. We get the window object of the iframe object through the contentWindow of the iframe object. Adding a value to iframe.contentWindow is equivalent to defining a global variable on the current page. As you can see, the TypeScript symbol value defined in the iframe is equal to the TypeScript symbol value defined in the current page, indicating that they are the same value.

2) Symbol. KeyFor ()

This method passes in a symbol value that returns the globally registered key name of the value: symbol

const sym = Symbol.for("TypeScript"); 
console.log(Symbol.keyFor(sym)); // 'TypeScript'
Copy the code

Complex base types

After looking at simple data types, let’s take a look at more complex data types, including arrays and objects in JavaScript and new tuples, enumerations, Any, void, never, and unknown in TypeScript.

1. array

There are two ways to define arrays in TypeScript:

  • Direct definition: Use the number[] form to specify that the elements of this type are array types of type number. This is recommended.
  • Array generics:Definition in Array form. When using this form definition, TSLint may warn us to use the first form definition, which can be defined in thetslint.jsonAdd to rules"array-type": [false]You can turn off tsLint for this item.
let list1: number[] = [1.2.3];
let list2: Array<number> = [1.2.3];
Copy the code

The two ways of defining array types are essentially the same, but the first form is preferred. On the one hand, you can avoid JSX syntax conflicts, on the other hand, you can reduce the amount of code.

Note that number specifies the type of the array element, and you can specify the array element as any other type. If you want to specify the elements of an array can be either value also can be a string, you can use this way: number | string [].

2. object

In JavaScript, object is a reference type that stores a reference to a value. In TypeScript, we use this type when we want the type of a variable or function parameter to be an object:

let obj: object
obj = { name: 'TypeScript' }
obj = 123             // Error cannot assign type 123 to type object
console.log(obj.name) // Attribute 'name' does not exist on error type 'object'
Copy the code

As you can see, an error is reported when an object is assigned to a variable of an object type. Object types are more suitable for the following scenarios:

function getKeys (obj: object) {
  return Object.keys(obj) // The values in obj are returned as a list
}
getKeys({ a: 'a' }) // ['a']
getKeys(123)        // Parameters of error type 123 cannot be assigned to parameters of type object
Copy the code

3. The tuples

There is no concept of tuples in JavaScript. As a dynamically typed language, it has the advantage of supporting arrays of elements of multiple types. But for extensibility, readability, and stability, we usually stuff different types of values into an object as key-value pairs and then return the object, rather than using arrays with no limits at all. TypeScript’s tuple types complement this, making it possible to define arrays that contain a fixed number of elements, each of which may not be of the same type.

A tuple can be seen as an extension of an array, representing an array with a known number and type of elements. It is particularly suitable for multi-valued returns. Given the type of the element at each position in the array, the tuple index can be assigned to the element:

let arr: [string.number.boolean];
arr = ["a".2.false]; // success
arr = [2."a".false]; // Error cannot assign type "number" to type "string". Type "string" cannot be assigned to type "number".
arr = ["a".2];        // error Property '2' is missing in type '[string, number]' but required in type '[string, number, boolean]'
arr[1] = 996					
Copy the code

As you can see, the number of elements and the type of elements in the defined ARR tuple are determined. When assigning a value to arR, the type of elements in each position must correspond and the number of elements must be consistent.

When accessing a tuple element, TypeScript also does type checking on the element. If the element is a string, it can only use string methods; if it is of any other type, an error is reported.

In newer versions of TypeScript, TypeScript makes out-of-bounds judgments about tuples. An element that exceeds the specified number is called an out-of-bounds element. The element assignment must correspond to both the type and the number of elements, and cannot exceed the defined number of elements.

In the new version, the declaration of the tuple type [string, number] can be seen as equivalent to the following declaration:

interface Tuple extends Array<number | string> { 
   0: string; 
   1: number;
   length: 2; 
}
Copy the code

Here we define the interface Tuple, which inherits the array type and whose elements are a combination of number and string, so that the interface Tuple has all the properties of an array type. Tuple: Tuple: Tuple: Tuple: Tuple: Tuple: Tuple: Tuple: Tuple: Tuple: Tuple: Tuple: Tuple: Tuple: Tuple: Tuple: Tuple: Tuple: Tuple: Tuple: Tuple: Tuple: Tuple: Tuple: Tuple: Tuple: Tuple: Tuple: Tuple: Tuple: Tuple: Tuple: Tuple: Tuple: Tuple: Tuple: Tuple: Tuple: Tuple It’s not going to be 2, it’s going to be greater than 2, it’s not going to satisfy this interface definition, so it’s going to get an error; Of course, an error is reported if there are less than two elements, because the value with index 0 or 1 is missing.

4. The enumeration

TypeScript adds enumerated types to ES, making it developer-friendly to assign names to sets of values in TypeScript. Enum types are defined using enum:

enum Roles {
  SUPER_ADMIN,
  ADMIN,
  USER
}
Copy the code

The above enumerated type Roles has three values that TypeScript assigns numbers to each of them, starting at 0 by default, so you can use names instead of remembering names:

enum Roles {
  SUPER_ADMIN = 0,
  ADMIN = 1,
  USER = 2 
}

const superAdmin = Roles.SUPER_ADMIN;
console.log(superAdmin); / / 0
console.log(Roles[1])    // ADMIN
Copy the code

Alternatively, you can change the value to make SUPER_ADMIN = 1 so that the following values are 2 and 3, respectively. It is also possible to assign each value to a different, out-of-order value:

enum Roles {
   SUPER_ADMIN = 1,
   ADMIN = 3,
   USER = 7 
}
Copy the code

5. any

When writing code, it’s not always clear what type a value is. Use any, which is an arbitrary type. Variables defined as any bypass TypeScript’s static type detection. A value declared as any can do anything to it, including get properties and methods that don’t actually exist, and TypeScript can’t detect whether the properties exist or are of the correct type.

We can define a value as any, or we can define an array type using any to specify that the element in the array is of any type:

let value: any;
value = 123;
value = "abc";
value = false;

const array: any[] = [1."a".true];
Copy the code

The any type is propagated in the call chain of an object, that is, any property of an object of any type is of type ANY, as shown in the following code:

let obj: any = {};
let z = obj.x.y.z; // z is of type any
z();               // success
Copy the code

One caveat: Don’t abuse any. If your code is full of any, TypeScript and JavaScript are no different, so avoid using any and turn on Settings that disable implicit any unless you have a good reason to.

6. void

Void is the opposite of any, which means any type, and void means no type. This is used when defining a function that returns no value:

const consoleText = (text: string) :void= > {
  console.log(text); 
};
Copy the code

Note: Void variables can only be assigned to undefined and null. Other types cannot be assigned to void variables.

7. never

The never type is a type that never has a value. It is the return type of function expressions that always throw an exception or return no value at all. The variable is also of never type if it is bound by type protection that never exists.

The following function always throws an exception, so its return value type is never to indicate that its return value does not exist:

const errorFunc = (message: string) :never= > {
   throw new Error(message); 
};
Copy the code

The never type is a subtype of any type, so it can be assigned to any type; No type is a subtype of never, so no type (including any) can assign a value to never except itself.

let neverVariable = (() = > {
   while (true) {} 
})();
neverVariable = 123; // Error cannot assign type "number" to type "never"
Copy the code

This function returns a value of never, so the assignment of neverVariable is never. When 123 is assigned to neverVariable, an error is reported. Because no type can be assigned to never except itself.

Based on the never feature, we can use never as an attribute type under the interface type to disallow manipulation of specific attributes under the interface:

const props: {
  id: number, name? :never
} = {
  id: 1
}
props.name = null;  // error
props.name = 'str'; // error
props.name = 1;     // error
Copy the code

As you can see, no matter what type of value is assigned to props. Name, it will give a type error, which is equivalent to making the name property read-only.

8. unknown

Unknown is a new TypeScript type in version 3.0 that describes variables of uncertain type. It looks similar to any, but there are differences. Unknown is more secure than any.

For any, let’s look at an example:

let value: any
console.log(value.name)
console.log(value.toFixed())
console.log(value.length)
Copy the code

If value is an object, access to the name attribute is ok. If value is an object, access to the name attribute is ok. When value is a numeric type, calling its toFixed method is fine; It is ok to get the length property of a value if it is a string or array.

When you specify a value of type unknown, you cannot do anything with it without narrowing it down. In general, values of unknown type cannot be manipulated arbitrarily. So what is type narrowing? Here’s an example:

function getValue(value: unknown) :string {
  if (value instanceof Date) { 
    return value.toISOString();
  }
  return String(value);
}
Copy the code

To narrow the type of value to a Date instance, value.toisostring () is used to convert a Date object to a string using the ISO standard.

You can also narrow the type range by:

let result: unknown;
if (typeof result === 'number') {
  result.toFixed();
}
Copy the code

Note the following when using the unknown type:

  • A value of any type can be assigned to unknown:
let value1: unknown;
value1 = "a";
value1 = 123;
Copy the code
  • Unknown cannot be assigned to any other type except unknown and any:
let value2: unknown;
let value3: string = value2; // Error cannot assign type "unknown" to type "string"
value1 = value2;
Copy the code
  • A value of type unknown cannot do anything:
let value4: unknown;
value4 += 1; // The error object is of type "unknown"
Copy the code
  • Unknown can only be equal or unequal. Other operations cannot be performed:
value1 === value2; value1 ! == value2; value1 += value2;// error
Copy the code
  • A value of type unknown cannot access its properties, be called as a function, or create an instance as a class:
let value5: unknown;
value5.age;   // error
value5();     // error
new value5(); // error
Copy the code

In practice, avoid any if the type cannot be determined, because any loses the type information. Once a type is specified as any, it is legal to do anything on it, so unexpected things can happen. Therefore, if you cannot determine the type, consider using unknown first.

That concludes this article, which focuses on the basic data types in TypeScript. The next article will look at enumerated types in more detail.