For those of you who want to learn TypeScript, this article will guide you through 14 basic TypeScript basics. See below for a detailed outline:
If you want to learn TypeScript generics systematically, read this article to understand TypeScript generics and applications (7.8k).
What is TypeScript
TypeScript is a free and open source programming language developed by Microsoft. It is a superset of JavaScript and essentially adds optional static typing and class-based object-oriented programming to the language.
TypeScript offers the latest and evolving JavaScript features, including those from ECMAScript 2015 and future proposals, such as asynchronous functionality and Decorators, to help build robust components. The following figure shows the relationship between TypeScript and ES5, ES2015, and ES2016:
1.1 Differences between TypeScript and JavaScript
TypeScript | JavaScript |
---|---|
Supersets of JavaScript are used to address the code complexity of large projects | A scripting language used to create dynamic web pages. |
Errors can be found and corrected during compilation | As an interpreted language, errors can only be found at runtime |
Strong typing, support for static and dynamic typing | Weakly typed, with no static typing option |
It is eventually compiled into JavaScript code that the browser can understand | It can be used directly in the browser |
Supports modules, generics, and interfaces | Modules, generics, or interfaces are not supported |
Supports ES3, ES4, ES5, and ES6 | Other ES3, ES4, ES5, or ES6 functions are not supported |
Community support is still growing, and it’s not huge yet | Lots of community support and lots of documentation and problem-solving support |
1.2 get TypeScript
The command-line TypeScript compiler can be installed using the node.js package.
1. Install the TypeScript
$ npm install -g typescript
Copy the code
2. Compile TypeScript files
$ tsc helloworld.ts
# helloworld.ts => helloworld.js
Copy the code
Of course, people who are new to TypeScript can use the online TypeScript Playground to learn new syntax or features without installing TypeScript.
TypeScript Playground:www.typescriptlang.org/play/
Base TypeScript types
2.1 a Boolean type
let isDone: boolean = false;
// ES5: var isDone = false;
Copy the code
2.2 Number type
let count: number = 10;
// ES5: var count = 10;
Copy the code
2.3 type String
let name: string = "Semliker";
// ES5: var name = 'Semlinker';
Copy the code
2.4 Array type
let list: number[] = [1.2.3];
// ES5: var list = [1,2,3];
let list: Array<number> = [1.2.3]; // Array
generic syntax
// ES5: var list = [1,2,3];
Copy the code
2.5 Enum type
Using enumerations we can define constants with names. Use enumerations to articulate intent or to create a distinct set of use cases. TypeScript supports numeric and string-based enumerations.
1. Enumeration of numbers
enum Direction {
NORTH,
SOUTH,
EAST,
WEST,
}
let dir: Direction = Direction.NORTH;
Copy the code
By default, NORTH starts at 0, and the remaining members automatically grow from 1. In other words, the value of direction.south is 1, the value of direction.east is 2, and the value of direction.west is 3. The enumeration sample code above compiles to produce the following code:
"use strict";
var Direction;
(function (Direction) {
Direction[(Direction["NORTH"] = 0)] = "NORTH";
Direction[(Direction["SOUTH"] = 1)] = "SOUTH";
Direction[(Direction["EAST"] = 2)] = "EAST";
Direction[(Direction["WEST"] = 3)] = "WEST";
})(Direction || (Direction = {}));
var dir = Direction.NORTH;
Copy the code
We can also set the initial value of NORTH, such as:
enum Direction {
NORTH = 3,
SOUTH,
EAST,
WEST,
}
Copy the code
2. String enumeration
In TypeScript 2.4, we are allowed to use string enumerations. In a string enumeration, each member must be initialized with a string literal, or with another string enumeration member.
enum Direction {
NORTH = "NORTH",
SOUTH = "SOUTH",
EAST = "EAST",
WEST = "WEST",}Copy the code
The above code for ES5 is as follows:
"use strict";
var Direction;
(function (Direction) {
Direction["NORTH"] = "NORTH";
Direction["SOUTH"] = "SOUTH";
Direction["EAST"] = "EAST";
Direction["WEST"] = "WEST";
})(Direction || (Direction = {}));
Copy the code
3. Heterogeneous enumeration
The member values of a heterogeneous enumeration are a mixture of numbers and strings:
enum Enum {
A,
B,
C = "C",
D = "D",
E = 8,
F,
}
Copy the code
The above code for ES5 is as follows:
"use strict";
var Enum;
(function (Enum) {
Enum[Enum["A"] = 0] = "A";
Enum[Enum["B"] = 1] = "B";
Enum["C"] = "C";
Enum["D"] = "D";
Enum[Enum["E"] = 8] = "E";
Enum[Enum["F"] = 9] = "F";
})(Enum || (Enum = {}));
Copy the code
By looking at the generated ES5 code above, we can see that numeric enumerations are more “reverse mapped” than string enumerations:
console.log(Enum.A) // Output: 0
console.log(Enum[0]) // Output: A
Copy the code
2.6 Any type
In TypeScript, any type can be classified as any. This makes the any type the top-level type of the type system (also known as the global supertype).
let notSure: any = Awesome!;
notSure = "Semlinker";
notSure = false;
Copy the code
The any type is essentially an escape pod for the type system. As developers, this gives us a lot of freedom: TypeScript allows us to do anything with a value of type any without performing any kind of checks beforehand. Such as:
let value: any;
value.foo.bar; // OK
value.trim(); // OK
value(); // OK
new value(); // OK
value[0] [1]; // OK
Copy the code
In many scenarios, this is too loose. Using the any type, you can easily write code that is of the right type but problematic at run time. If we use any, we won’t be able to use many of TypeScript’s protections. To address the problem of any, TypeScript 3.0 introduces unknown types.
2.7 Unknown type
Just as all types can be assigned to any, all types can be assigned to unknown. This makes Unknown another top-level type in the TypeScript type system (the other is any). Here’s an example of how unknown can be used:
let value: unknown;
value = true; // OK
value = 42; // OK
value = "Hello World"; // OK
value = []; // OK
value = {}; // OK
value = Math.random; // OK
value = null; // OK
value = undefined; // OK
value = new TypeError(a);// OK
value = Symbol("type"); // OK
Copy the code
All assignments to the value variable are considered type-correct. But what happens when we try to assign a value of type unknown to a variable of another type?
let value: unknown;
let value1: unknown = value; // OK
let value2: any = value; // OK
let value3: boolean = value; // Error
let value4: number = value; // Error
let value5: string = value; // Error
let value6: object = value; // Error
let value7: any[] = value; // Error
let value8: Function = value; // Error
Copy the code
The unknown type can only be assigned to the any type and the unknown type itself. Intuitively, this makes sense: Only containers that can hold values of any type can hold values of type unknown. After all, we don’t know what type of value is stored in the variable value.
Now let’s see what happens when we try to operate on a value of type unknown. Here is the same action we looked at in the previous Any chapter:
let value: unknown;
value.foo.bar; // Error
value.trim(); // Error
value(); // Error
new value(); // Error
value[0] [1]; // Error
Copy the code
With the value variable type set to unknown, these operations are no longer considered type-correct. By changing the any type to the unknown type, we have changed the default setting, which allows all changes, to disallow any changes.
2.8 the Tuple type
As we all know, arrays usually consist of values of the same type, but sometimes we need to store different types of values in a single variable, and we can use tuples. There are no tuples in JavaScript. Tuples are TypeScript specific types that work like arrays.
Tuples can be used to define types that have a limited number of unnamed attributes. Each attribute has an associated type. When using tuples, you must supply the value of each attribute. To make the concept of tuples more intuitive, let’s look at a concrete example:
let tupleType: [string.boolean];
tupleType = ["Semlinker".true];
Copy the code
In the above code, we define a variable named tupleType, whose type is an array of types [string, Boolean], and then we initialize the tupleType variables in order of the correct type. As with arrays, we can access the elements of a tuple by subscript:
console.log(tupleType[0]); // Semlinker
console.log(tupleType[1]); // true
Copy the code
If a type mismatch occurs during tuple initialization, for example:
tupleType = [true."Semlinker"];
Copy the code
At this point, the TypeScript compiler displays the following error message:
[0]: Type 'true' is not assignable to type 'string'.
[1]: Type 'string' is not assignable to type 'boolean'.
Copy the code
It’s obviously a type mismatch. During tuple initialization, we must also provide the value of each attribute, otherwise we will also have an error, such as:
tupleType = ["Semlinker"];
Copy the code
At this point, the TypeScript compiler displays the following error message:
Property '1' is missing in type '[string]' but required in type '[string, boolean]'.
Copy the code
2.9 Void type
In some ways, the void type looks like the opposite of any. It means there is no type. When a function does not return a value, you will usually see a return value of type void:
// Declare the function return void
function warnUser() :void {
console.log("This is my warning message");
}
Copy the code
The ES5 code generated by compiling the above code is as follows:
"use strict";
function warnUser() {
console.log("This is my warning message");
}
Copy the code
Note that declaring a void variable has no effect, since its value can only be undefined or null:
let unusable: void = undefined;
Copy the code
2.10 Null and Undefined types
In TypeScript, undefined and NULL have their own types: undefined and NULL.
let u: undefined = undefined;
let n: null = null;
Copy the code
By default null and undefined are subtypes of all types. That means you can assign null and undefined to a variable of type number. However, if you specify the –strictNullChecks tag, null and undefined can only be assigned to void and their respective types.
2.11 Never type
The never type represents the type of values that will never exist. For example, the type never is the return value type for function or arrow function expressions that always throw an exception or never return a value at all.
// A function that returns never must have an unreachable endpoint
function error(message: string) :never {
throw new Error(message);
}
function infiniteLoop() :never {
while (true) {}}Copy the code
In TypeScript, you can use the never feature to implement comprehensiveness checks. Here’s an example:
type Foo = string | number;
function controlFlowAnalysisWithNever(foo: Foo) {
if (typeof foo === "string") {
// Here foo is narrowed to string
} else if (typeof foo === "number") {
// Here foo is narrowed to the number type
} else {
// Foo in this case is never
constcheck: never = foo; }}Copy the code
Notice that in the else branch, we assign foo, narrowed down to never, to a variable that displays the declaration of never. If all logic is correct, then this should compile through. But suppose one day your colleague changes the type of Foo:
type Foo = string | number | boolean;
Copy the code
But he forgot to modify controlFlowAnalysisWithNever method in control process at the same time, this time the else branch foo type can be narrowed to a Boolean type, lead to cannot be assigned to never type, then will generate a compiler error. In this way, we can make sure that
ControlFlowAnalysisWithNever method always end with all possible types of Foo. From this example, we can conclude that using never avoids the possibility that there is no corresponding implementation for a newly joined type, in order to write type-safe code.
TypeScript assertions
Sometimes you will encounter situations where you know more about a value than TypeScript does. Usually this happens when you clearly know that an entity has a more precise type than it already has.
Type assertions are a way of telling the compiler, “Trust me, I know what I’m doing.” Type assertions are like conversions in other languages, but without the special data checking and deconstruction. It has no runtime impact, only at compile time.
Type assertions come in two forms:
3.1 Angle bracket syntax
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
Copy the code
3.2 the as grammar
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
Copy the code
Type guard
A type guard is some expression that pers A runtime check that guarantees the type in some scope
A type guard is an expression that can be checked at runtime to ensure that the type is within a certain range. In other words, type protection guarantees that a string is a string, even though its value can also be a numeric value. Type protection is not entirely different from feature detection; the main idea is to try to detect attributes, methods, or stereotypes to determine how to handle values. At present, there are four main ways to realize type protection:
4.1 In Keyword
interface Admin {
name: string;
privileges: string[];
}
interface Employee {
name: string;
startDate: Date;
}
type UnknownEmployee = Employee | Admin;
function printEmployeeInformation(emp: UnknownEmployee) {
console.log("Name: " + emp.name);
if ("privileges" in emp) {
console.log("Privileges: " + emp.privileges);
}
if ("startDate" in emp) {
console.log("Start Date: "+ emp.startDate); }}Copy the code
4.2 Typeof Keyword
function padLeft(value: string, padding: string | number) {
if (typeof padding === "number") {
return Array(padding + 1).join("") + value;
}
if (typeof padding === "string") {
return padding + value;
}
throw new Error(`Expected string or number, got '${padding}'. `);
}
Copy the code
Only two types of typeof protection are supported: Typeof V === “typename” and Typeof V! == TYPename, “typename” must be “number”, “string”, “Boolean” or “symbol”. But TypeScript doesn’t prevent you from comparing other strings; the language doesn’t recognize those expressions as type-protected.
4.3 Instanceof keyword
interface Padder {
getPaddingString(): string;
}
class SpaceRepeatingPadder implements Padder {
constructor(private numSpaces: number) {}
getPaddingString() {
return Array(this.numSpaces + 1).join(""); }}class StringPadder implements Padder {
constructor(private value: string) {}
getPaddingString() {
return this.value; }}let padder: Padder = new SpaceRepeatingPadder(6);
if (padder instanceof SpaceRepeatingPadder) {
// The padder type is narrowed to 'SpaceRepeatingPadder'
}
Copy the code
4.4 User-defined type predicates for type protection
function isNumber(x: any) :x is number {
return typeof x === "number";
}
function isString(x: any) :x is string {
return typeof x === "string";
}
Copy the code
Join types and type aliases
5.1 Union Type
Union types are usually used with null or undefined:
const sayHello = (name: string | undefined) = > {
/ *... * /
};
Copy the code
Here, for example, the type of the name is the string | undefined means that can be a string or undefined values passed to the sayHello function.
sayHello("Semlinker");
sayHello(undefined);
Copy the code
From this example, you can intuitively know that A combined type of A and B is A type that accepts both A and B values.
5.2 Discernable association
TypeScript Discriminated Unions types are also known as algebraic data types or label union types. It has three main points: identifiers, federated types, and type guards.
The essence of this type is a type protection method that combines union types and literal types. If a type is a union type of multiple types, and the types have a common property, you can use this common property to create different types of protected blocks.
1. Can recognize
Identifiers require that each element in the union type contains a singleton type attribute, such as:
enum CarTransmission {
Automatic = 200,
Manual = 300
}
interface Motorcycle {
vType: "motorcycle"; // discriminant
make: number; // year
}
interface Car {
vType: "car"; // discriminant
transmission: CarTransmission
}
interface Truck {
vType: "truck"; // discriminant
capacity: number; // in tons
}
Copy the code
In the above code, we define three interfaces, namely Motorcycle, Car and Truck, and each of these interfaces contains a vType attribute, which is called the identifiable attribute, while other attributes are only related to the characteristic interface.
2. Union type
Based on the three interfaces defined previously, we can create a Vehicle union type:
type Vehicle = Motorcycle | Car | Truck;
Copy the code
Now we can start using the Vehicle union type, which can represent different types of vehicles for a variable of Vehicle type.
3. Type guard
Let’s define an evaluatePrice method to calculate the price based on the vehicle type, capacity, and evaluation factor, as follows:
const EVALUATION_FACTOR = Math.PI;
function evaluatePrice(vehicle: Vehicle) {
return vehicle.capacity * EVALUATION_FACTOR;
}
const myTruck: Truck = { vType: "truck", capacity: 9.5 };
evaluatePrice(myTruck);
Copy the code
For the above code, the TypeScript compiler will prompt the following error:
Property 'capacity' does not exist on type 'Vehicle'.
Property 'capacity' does not exist on type 'Motorcycle'.
Copy the code
The reason is that there is no capacity attribute for Motorcycle, and there is no capacity attribute for Car. So how can we solve these problems now? At this point, we can use the type guard. Let’s refactor the evaluatePrice method to look like this:
function evaluatePrice(vehicle: Vehicle) {
switch(vehicle.vType) {
case "car":
return vehicle.transmission * EVALUATION_FACTOR;
case "truck":
return vehicle.capacity * EVALUATION_FACTOR;
case "motorcycle":
returnvehicle.make * EVALUATION_FACTOR; }}Copy the code
In the above code, we implement the type guard using the switch and case operators to ensure that in the evaluatePrice method, we can safely access the properties contained in the Vehicle object to correctly calculate the price for the vehicle type.
5.3 Type Alias
A type alias is used to give a type a new name.
type Message = string | string[];
let greet = (message: Message) = > {
// ...
};
Copy the code
Cross type
TypeScript cross-typing combines multiple types into a single type. This allows us to add existing types together into a single type that contains all the required features of the type.
interface IPerson {
id: string;
age: number;
}
interface IWorker {
companyId: string;
}
type IStaff = IPerson & IWorker;
const staff: IStaff = {
id: 'E1006',
age: 33,
companyId: 'EFT'
};
console.dir(staff)
Copy the code
In the above example, we first defined different members for the IPerson and IWorker types, and then we defined the IStaff crossover type with the & operator, so that the type has members of both IPerson and IWorker types.
TypeScript functions
7.1 Differences between TypeScript functions and JavaScript functions
TypeScript | JavaScript |
---|---|
Contains the type | No type |
Arrow function | Arrow function (ES2015) |
Function types | No function type |
This parameter is mandatory and optional | All parameters are optional |
The default parameters | The default parameters |
The remaining parameters | The remaining parameters |
Function overloading | No function overload |
7.2 Arrow Function
1. Common grammar
myBooks.forEach((a)= > console.log('reading'));
myBooks.forEach(title= > console.log(title));
myBooks.forEach((title, idx, arr) = >
console.log(idx + The '-'+ title); ) ; myBooks.forEach((title, idx, arr) = > {
console.log(idx + The '-' + title);
});
Copy the code
2. Example
// The arrow function is not used
function Book() {
let self = this;
self.publishDate = 2016;
setInterval(function () {
console.log(self.publishDate);
}, 1000);
}
// Use the arrow function
function Book() {
this.publishDate = 2016;
setInterval((a)= > {
console.log(this.publishDate);
}, 1000);
}
Copy the code
7.3 Parameter Types and Return Types
function createUserId(name: string, id: number) :string {
return name + id;
}
Copy the code
7.4 Function Types
let IdGenerator: (chars: string, nums: number) = > string;
function createUserId(name: string, id: number) :string {
return name + id;
}
IdGenerator = createUserId;
Copy the code
7.5 This parameter is optional
Function createUserId(name: string, ID: number, age? : number): string { return name + id; } function createUserId(name: string = "Semlinker", id: number, age? : number ): string { return name + id; }Copy the code
When you declare a function, you can pass? Number to define optional parameters, such as age? : number In practice, it is important to place the optional arguments after the normal arguments, otherwise it will cause a compilation error.
7.6 Remaining Parameters
function push(array, ... items) {
items.forEach(function (item) {
array.push(item);
});
}
let a = [];
push(a, 1.2.3);
Copy the code
7.7 Function Overloading
Function overload or method overload is the ability to create multiple methods with the same name and different numbers or types of arguments. The way to solve the previous problem is to overload the same function by providing multiple function type definitions, and the compiler will handle the call based on this list.
function add(a: number, b: number) :number;
function add(a: string, b: string) :string;
function add(a: string, b: number) :string;
function add(a: number, b: string) :string;
function add(a: Combinable, b: Combinable) {
if (typeof a === "string" || typeof b === "string") {
return a.toString() + b.toString();
}
return a + b;
}
Copy the code
In the code above, we overload the add function by providing multiple function type definitions. After that, the nasty error message disappears again, because the result variable is of type String. In TypeScript, in addition to overloading normal functions, we can also override member methods in classes.
Method overloading is a technique in which a method with the same name in the same class has different parameters (different parameter types, different number of parameters, or different order of parameters for the same number of parameters), and the method that matches the argument is selected to perform the operation based on the form of the argument. So a member method in a class can be overloaded only if it has the same name and different argument lists in the same class. Here’s an example of member method overloading:
class Calculator {
add(a: number, b: number) :number;
add(a: string, b: string) :string;
add(a: string, b: number) :string;
add(a: number, b: string) :string;
add(a: Combinable, b: Combinable) {
if (typeof a === "string" || typeof b === "string") {
return a.toString() + b.toString();
}
returna + b; }}const calculator = new Calculator();
const result = calculator.add("Semlinker"." Kakuqo");
Copy the code
The important thing to note here is that when the TypeScript compiler handles function overloading, it looks up the list of overloads and tries to use the first overload definition. Use this if there is a match. Therefore, when defining overloads, be sure to put the most precise definition first. Also in the Calculator class, add(A: Combinable, B: Combinable){} is not part of the overload list, so we only define four overload methods for the Add member method.
TypeScript arrays
8.1 Array Deconstruction
let x: number; let y: number; let z: number;
let five_array = [0.1.2.3.4];
[x,y,z] = five_array;
Copy the code
8.2 Array expansion operator
let two_array = [0.1];
let five_array = [...two_array, 2.3.4];
Copy the code
8.3 Array Traversal
let colors: string[] = ["red"."green"."blue"];
for (let i of colors) {
console.log(i);
}
Copy the code
9. TypeScript objects
9.1 Object Deconstruction
let person = {
name: "Semlinker",
gender: "Male"};let { name, gender } = person;
Copy the code
9.2 Object expansion operator
let person = {
name: "Semlinker",
gender: "Male",
address: "Xiamen"};// Assemble the object
letpersonWithAge = { ... person, age:33 };
// Get all but certain items
let{ name, ... rest } = person;Copy the code
TypeScript interfaces
In object-oriented language, interface is a very important concept, it is the abstraction of behavior, and the concrete action needs to be implemented by the class.
Interfaces in TypeScript are a very flexible concept. In addition to abstracting part of the behavior of a class, they are often used to describe the Shape of an object.
10.1 Shapes of objects
interface Person {
name: string;
age: number;
}
let Semlinker: Person = {
name: "Semlinker",
age: 33};Copy the code
10.2 optional | read-only property
interface Person {
readonly name: string; age? :number;
}
Copy the code
The read-only property is used to restrict the value of an object to be changed only when it is newly created. TypeScript also provides ReadonlyArray
, which is similar to Array
except that all mutable methods are removed, thus ensuring that the Array cannot be modified once it has been created.
let a: number[] = [1.2.3.4];
let ro: ReadonlyArray<number> = a;
ro[0] = 12; // error!
ro.push(5); // error!
ro.length = 100; // error!
a = ro; // error!
Copy the code
TypeScript classes
11.1 Properties and methods of the class
In an object-oriented language, a class is a construct of an object-oriented computer programming language. It is a blueprint for creating an object, describing the common properties and methods of the created object.
In TypeScript, we can define a Class using the Class keyword:
class Greeter {
// Static properties
static cname: string = "Greeter";
// Member attributes
greeting: string;
// Constructor - performs the initialization operation
constructor(message: string) {
this.greeting = message;
}
// Static methods
static getClassName() {
return "Class name is Greeter";
}
// Member methods
greet() {
return "Hello, " + this.greeting; }}let greeter = new Greeter("world");
Copy the code
So what’s the difference between a member property and a static property, or between a member method and a static method? Without further explanation, let’s take a look at the compiled ES5 code:
"use strict";
var Greeter = /** @class */ (function () {
// Constructor - performs the initialization operation
function Greeter(message) {
this.greeting = message;
}
// Static methods
Greeter.getClassName = function () {
return "Class name is Greeter";
};
// Member methods
Greeter.prototype.greet = function () {
return "Hello, " + this.greeting;
};
// Static properties
Greeter.cname = "Greeter";
returnGreeter; } ());var greeter = new Greeter("world");
Copy the code
11.2 the visitor
In TypeScript, getters and setters are used to encapsulate and validate data to prevent abnormal data.
let passcode = "Hello TypeScript";
class Employee {
private _fullName: string;
get fullName(): string {
return this._fullName;
}
set fullName(newName: string) {
if (passcode && passcode == "Hello TypeScript") {
this._fullName = newName;
} else {
console.log("Error: Unauthorized update of employee!"); }}}let employee = new Employee();
employee.fullName = "Semlinker";
if (employee.fullName) {
console.log(employee.fullName);
}
Copy the code
11.3 Class inheritance
Inheritance is a hierarchical model that connects classes to classes. The ability for a class (called a subclass, or subinterface) to inherit the functionality of another class (called a parent class, or parent interface) and add new functionality of its own. Inheritance is the most common relationship between classes or interfaces.
Inheritance is an IS-A relationship:
In TypeScript, we can implement inheritance with the extends keyword:
class Animal {
name: string;
constructor(theName: string) {
this.name = theName;
}
move(distanceInMeters: number = 0) {
console.log(`${this.name} moved ${distanceInMeters}m.`); }}class Snake extends Animal {
constructor(name: string) {
super(name);
}
move(distanceInMeters = 5) {
console.log("Slithering...");
super.move(distanceInMeters); }}let sam = new Snake("Sammy the Python");
sam.move();
Copy the code
11.4 ECMAScript Private Fields
ECMAScript private fields have been supported since TypeScript version 3.8. They can be used as follows:
class Person {
#name: string;
constructor(name: string) {
this.#name = name;
}
greet() {
console.log(`Hello, my name is ${this.#name}! `); }}let semlinker = new Person("Semlinker");
semlinker.#name;
/ / ~ ~ ~ ~ ~
// Property '#name' is not accessible outside class 'Person'
// because it has a private identifier.
Copy the code
Unlike regular properties (even those declared with the private modifier), keep the following rules in mind for private fields:
- Private fields to
#
Character, sometimes we call it a private name; - Each private field name is uniquely qualified to the class it contains;
- You cannot use TypeScript accessibility modifiers (such as public or private) on private fields;
- Private fields cannot be accessed outside of the contained class and cannot even be detected.
TypeScript generics
In software engineering, we should not only create consistent, well-defined apis, but also consider reusability. Components can support not only current data types, but future data types as well, which gives you great flexibility when creating large systems.
In languages like C# and Java, you can use generics to create reusable components that can support multiple types of data. This allows users to use components with their own data types.
The key purpose of designing generics is to provide meaningful constraints between members: instance members of the class, methods of the class, function arguments, and function return values.
Generics is a template that allows the same function to accept parameters of different types. Instead of using the any type, it is better to use generics to create reusable components because generics preserve the parameter types.
12.1 Generic Interface
interface GenericIdentityFn<T> {
(arg: T): T;
}
Copy the code
12.2 a generic class
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) = > T;
}
let myGenericNumber = new GenericNumber<number> (); myGenericNumber.zeroValue =0;
myGenericNumber.add = function (x, y) {
return x + y;
};
Copy the code
12.3 Generic variables
The generic variables T and E, K and V can be a bit of a shock to anyone new to TypeScript generics. In fact, there is no essential difference between these capital letters, it is just a agreed upon specification. That is, if you define A type variable with capital letters A to Z, it is A generic type. If you change T to A, it is the same. Here’s what some common generic variables mean:
- T (Type) : represents a TypeScript Type
- K (Key) : indicates the Key type of an object
- V (Value) : indicates the Value type of an object
- E (Element) : indicates the Element type
12.4 Generic tool types
Common utility types such as Partial, Required, Readonly, Record, and ReturnType are built into TypeScript for developers’ convenience. For the sake of space, we’ll only briefly cover the Partial tool types here. But before we get into that, let’s cover some basics so you can learn about the other types of tools on your own.
1.typeof
In TypeScript, the typeof operator can be used to get the typeof a variable declaration or object.
interface Person {
name: string;
age: number;
}
const sem: Person = { name: 'semlinker', age: 30 };
type Sem= typeof sem; // -> Person
function toArray(x: number) :Array<number> {
return [x];
}
type Func = typeof toArray; // -> (x: number) => number[]
Copy the code
2.keyof
The keyof operator can be used for all key values in an object:
interface Person {
name: string;
age: number;
}
type K1 = keyof Person; // "name" | "age"
type K2 = keyof Person[]; // "length" | "toString" | "pop" | "push" | "concat" | "join"
type K3 = keyof { [x: string]: Person }; // string | number
Copy the code
3.in
In is used to iterate over enumeration types:
type Keys = "a" | "b" | "c"
type Obj = {
[p in Keys]: any
} // -> { a: any, b: any, c: any }
Copy the code
4.infer
In conditional type statements, you can use infer to declare a type variable and use it.
type ReturnType<T> = T extends (
...args: any[]
) => infer R ? R : any;
Copy the code
In the preceding code, infer R means to declare a variable to carry the type of the return value passed into the function signature. In other words, infer can be used to obtain the type of the return value of the function for later use.
5.extends
Sometimes when we define a generic that we don’t want to be too flexible or want to inherit from some class, we can add a generic constraint with the extends keyword.
interface ILengthwise {
length: number;
}
function loggingIdentity<T extends ILengthwise> (arg: T) :T {
console.log(arg.length);
return arg;
}
Copy the code
Now the generic function is defined with constraints, so it no longer applies to any type:
loggingIdentity(3); // Error, number doesn't have a .length property
Copy the code
In this case, we need to pass in values that conform to the constraint type, which must contain the required attributes:
loggingIdentity({length: 10, value: 3});
Copy the code
6.Partial
Partial
makes all properties of a certain type optional, right? .
Definition:
/** * node_modules/typescript/lib/lib.es5.d.ts * Make all properties in T optional */
type Partial<T> = {
[P inkeyof T]? : T[P]; };Copy the code
In the above code, we first get all the attribute names of T through keyof T, then use in to iterate, assign the value to P, and finally get the corresponding attribute value through T[P]. In the middle? The number used to make all properties optional.
Example:
interface Todo {
title: string;
description: string;
}
function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) {
return{... todo, ... fieldsToUpdate }; }const todo1 = {
title: "organize desk",
description: "clear clutter"};const todo2 = updateTodo(todo1, {
description: "throw out trash"});Copy the code
In the updateTodo method above, we use the Partial
tool type and define the fieldsToUpdate to be of type Partial
, that is:
{ title? :string | undefined; description? :string | undefined;
}
Copy the code
TypeScript decorators
13.1 What is a Decorator
- It’s an expression
- When the expression is executed, it returns a function
- The input parameters of the function are target, name, and descriptor
- This function may return an object that is used to configure the Target object
13.2 Classification of decorators
- Class decorators
- Property decorators
- Method decorators
- Parameter decorators
Class 13.3 decorators
Class decorators declare:
declare type ClassDecorator = <TFunction extends Function>(
target: TFunction
) => TFunction | void;
Copy the code
Class decorators, as the name suggests, decorate classes. It takes a parameter:
- Target: TFunction – The class to be decorated
After the first look, you don’t even feel good. Ok, let’s do an example:
function Greeter(target: Function) :void {
target.prototype.greet = function () :void {
console.log("Hello Semlinker!");
};
}
@Greeter
class Greeting {
constructor() {
// Internal implementation}}let myGreeting = new Greeting();
myGreeting.greet(); // console output: 'Hello Semlinker! ';
Copy the code
In the example above, we define the Greeter class decorator, and we use the @Greeter syntax sugar to use the decorator.
As a bonus, you can simply copy the above code and run it in your TypeScript Playground to see the results.
As some readers may be wondering, the example always prints Hello Semlinker! , can you customize the output greeting? That’s a good question, and the answer is yes.
The specific implementation is as follows:
function Greeter(greeting: string) {
return function (target: Function) {
target.prototype.greet = function () :void {
console.log(greeting);
};
};
}
@Greeter("Hello TS!")
class Greeting {
constructor() {
// Internal implementation}}let myGreeting = new Greeting();
myGreeting.greet(); // console output: 'Hello TS! ';
Copy the code
13.4 Property decorators
Property decorator declaration:
declare type PropertyDecorator = (target:Object,
propertyKey: string | symbol ) => void;
Copy the code
Property decorators, as the name suggests, decorate the properties of a class. It takes two arguments:
- Target: Object – The class to be decorated
- PropertyKey: string | symbol – be decorating class attribute names
Strike while the iron is hot. Here’s an example to warm up:
function logProperty(target: any, key: string) {
delete target[key];
const backingField = "_" + key;
Object.defineProperty(target, backingField, {
writable: true,
enumerable: true,
configurable: true
});
// property getter
const getter = function (this: any) {
const currVal = this[backingField];
console.log(`Get: ${key}= >${currVal}`);
return currVal;
};
// property setter
const setter = function (this: any, newVal: any) {
console.log(`Set: ${key}= >${newVal}`);
this[backingField] = newVal;
};
// Create new property with getter and setter
Object.defineProperty(target, key, {
get: getter,
set: setter,
enumerable: true,
configurable: true
});
}
class Person {
@logProperty
public name: string;
constructor(name : string) {
this.name = name; }}const p1 = new Person("semlinker");
p1.name = "kakuqo";
Copy the code
We define a logProperty function to track the user’s actions on the property. When the code runs successfully, the console will print the following:
Set: name => semlinker
Set: name => kakuqo
Copy the code
13.5 Method decorators
Method decorator declaration:
declare type MethodDecorator = <T>(target:Object, propertyKey: string | symbol,
descriptor: TypePropertyDescript<T>) => TypedPropertyDescriptor<T> | void;
Copy the code
Method decorators, as the name suggests, decorate the methods of a class. It takes three arguments:
- Target: Object – The class to be decorated
- PropertyKey: string | symbol – the method name
- Descriptor: TypePropertyDescript – Property descriptor
Without further ado, let’s go to the following example:
function LogOutput(tarage: Function, key: string, descriptor: any) {
let originalMethod = descriptor.value;
let newMethod = function(. args:any[]) :any {
let result: any = originalMethod.apply(this, args);
if(!this.loggedOutput) {
this.loggedOutput = new Array<any> (); }this.loggedOutput.push({
method: key,
parameters: args,
output: result,
timestamp: new Date()});return result;
};
descriptor.value = newMethod;
}
class Calculator {
@LogOutput
double (num: number) :number {
return num * 2; }}let calc = new Calculator();
calc.double(11);
// console ouput: [{method: "double", output: 22, ...}]
console.log(calc.loggedOutput);
Copy the code
Let’s introduce the parameter decorator.
13.6 Parameter decorators
Parameter decorator declaration:
declare type ParameterDecorator = (target: Object, propertyKey: string | symbol,
parameterIndex: number) = >void
Copy the code
The parameter decorator, as its name suggests, decorates function arguments. It takes three arguments:
- Target: Object – The class to be decorated
- PropertyKey: string | symbol – the method name
- ParameterIndex: number – Specifies the index value of a parameter in a method
function Log(target: Function, key: string, parameterIndex: number) {
let functionLogged = key || target.prototype.constructor.name;
console.log(`The parameter in position ${parameterIndex} at ${functionLogged} has
been decorated`);
}
class Greeter {
greeting: string;
constructor(@Log phrase: string) {
this.greeting = phrase; }}// console output: The parameter in position 0
// at Greeter has been decorated
Copy the code
After covering the basics of getting started with TypeScript, and guessing that many people who are just getting started with TypeScript already have the idea of “getting started and giving up”, let’s briefly introduce the compilation context.
Xiv. Compilation context
14.1 Functions of tsconfig.json
- The root path that identifies a TypeScript project;
- Configure the TypeScript compiler;
- Used to specify the file to compile.
14.2 tsconfig.json Important fields
- Files – Sets the name of the file to compile;
- Include – sets the files to be compiled and supports path pattern matching.
- Exclude – Sets the files that do not need to be compiled to support path pattern matching.
- CompilerOptions – Sets the options related to the compile process.
14.3 compilerOptions options
CompilerOptions supports many options, such as baseUrl, Target, baseUrl, moduleResolution, and lib.
The details for each option are as follows:
{"compilerOptions": {/* compilerOptions */ "target": "es5", // specify the ECMAScript target version: 'ES3' (default), 'ES5', 'ES6'/'ES2015', 'ES2016', 'ES2017', or 'ESNEXT' "module": "commonjs", // Specify the module to use: 'commonjs', 'amd', 'system', 'umd' or 'es2015' "lib": [], // specify the library "allowJs" to be included in the compilation: "CheckJs ": true, // Report errors in javascript files" JSX ": "preserve", // specify JSX code generation: 'preserve', 'react native', or 'react' 'declaration' : true, '.d.ts' = 'sourceMap' : True, / / generate the corresponding 'map' file "outFile" : ". / ", / / the output files into one file "outDir" : ". / ", / / specifies the output directory "rootDir" : "RemoveComments ": true, // remove all compiled comments "noEmit": true, // do not generate the output file "importHelpers": True, // Import the helper function isolatedModules from tslib: True, // treat each file as a separate module (similar to 'ts.transpilemodule'). /* Strict type checking options */ "strict": true, // enable all strict type checking options "noImplicitAny": True, // error "strictNullChecks": true, // enable strictNullChecks" noImplicitThis": True, // Generates an error "alwaysStrict" when this expression is of type any: True, // check each module in strict mode, and add 'use strict' to each file /* additional check */ "noUnusedLocals": true, // throw the error "noUnusedParameters" if there are unused variables: True, / / a unused parameters, throw an error "noImplicitReturns" : true, / / not all there is in the function code when the return value, throw an error "noFallthroughCasesInSwitch" : True, // Reports a fallthrough error for the switch statement. /* moduleResolution options */ "moduleResolution": 'node' (node.js) or 'classic' (TypeScript pre-1.6) "baseUrl": "./", // for resolving non-relative module names for the base "paths": {}, // list of module names to baseurL-based pathmapping "rootDirs": [], // list of root folders whose combined contents represent the project runtime structure content "typeRoots": [], // list of files containing type declarations "types": [], / / need to include the type of statement file name list "allowSyntheticDefaultImports" : true, / / allows never to set the default import export modules by default. /* Source Map Options */ "sourceRoot": "./", // specifies where the debugger should find the TypeScript file instead of the Source file "mapRoot": "./", // specifies where the debugger should find the mapping file instead of generating the file "inlineSourceMap": true, // generate a single soucemaps file instead of sourcemaps generating different files "inlineSources": "ExperimentalDecorators ": // generate code and sourcemaps in a file that requires both --inlineSourceMap or --sourceMap properties to be set /* other options */ "experimentalDecorators": "EmitDecoratorMetadata ": true // provide metadata support for decorators}}Copy the code
If you want more, check out my list of 1.5K+ open source projects on Github: Awesome -typescript.
Github.com/semlinker/a…
15. Reference Resources
- mariusschulz – the-unknown-type-in-typescript
- In-depth understanding of typescript-compilation context