Self-study TypeScript - basics, compilation, types


TypeScript is a superset of JavaScript, extends JavaScript syntax, and supports the ECMAScript 6 standard. So existing JavaScript code can work with TypeScript without any modification, and TypeScript provides compile-time static type checking through type annotations. TypeScript can process existing JavaScript code and compile only the TypeScript code into pure JavaScript.

Compared with JS, the added functions of TS include:

  • Type annotations and compile-time type checking
  • type inference
  • type erasure
  • interface
  • enumerate
  • mix
  • Generic programming
  • namespace
  • tuple
  • Await

And some functions are backported from ECMA2015:

  • kind
  • module
  • Arrow syntax for lambda functions
  • Optional parameters and default parameters

Because TS is a superset of JS, only the differences from JS will be studied here.

TS compiled to JS

Usually we use . .tsas the extension for TypeScript code files. This file can be compiled into JS code using tscthe command. For example

// app.ts
var message:string = "Hello World" 
console.log(message)
tsc app.ts
// app.ts 编译为 app.js
var message = "Hello World";
console.log(message);

Of course, because most new versions of browsers support ts syntax, you can skip the step of compiling to js.

Type support

Because the JS type system has "innate defects", most of the errors in the JS code are type errors (Uncaught TypeError), which increases the time for finding and modifying bugs and seriously affects development efficiency. TS is a statically typed programming language that can detect errors during compilation , making it easy to find and modify bugs.

TS supports all JS types, but JS will not check type changes , but TS will.

type annotation

In TS, type annotations are used to add type constraints to variables.

let age: number = 18	// 声明变量为数值类型

The primitive type constraint keywords of TS are:

  • number
  • string
  • boolean
  • null
  • undefined
  • symbol

base type

The array type will not be strictly a new type in TS, but the object constraints of TS will be subdivided according to different specific types, so that arrays can also be a class of their own.

  • array type
let number: number[] = [1, 3, 5]
let strings: Array<string> = ['a', 'b', 'c']
  • union type
// 如果不确定变量的具体类型,或变量可能使用多个类型,则声明为联合类型
let a: number | string = 'a'	// 可以声明为数值或字符串
let arr: number | string[] = ['a', 'b', 'c']	// 可以声明为数值或字符串数组
// 如果数组中类型不止一种,则这样注解
let arr: (number | string)[] = [1, 'a', 3, 'b']		// 数组的元素可以是数值或字符串
  • Type aliases (custom types)
    When the same complex type is used multiple times, type aliases can be used to simplify the use of the type.
type CustomArray = (number | string)[]
let arr1: CustomArray = [1, 'a', 3, 'b']
  • Function type (parameter type, return value type)
// 单独指定参数和返回值的类型
function add(num1: number, num2: number): number {
    
    
	return num1 + num2
}
const add = (num1: number, num2: number): number => {
    
    
	return num1 + num2
}
// 同时指定参数、返回值类型
const add: (num1: number, num2: number) => number = (num1, num2) => {
    
    
	return num1 + num2
}
// 函数没有返回值,则返回值类型为 void
function greet(name: string): void {
    
    
	console.log('Hello', name)
}
// 可选参数
function mySlice(start?: number, end?: number): void {
    
    
	console.log('起始索引:', start, '结束索引:', end)
}
  • Object type (that is, type constraints on the properties and methods of the object)
let person: {
    
     name: string; age: number; sayHi(): void } = {
    
    
	name: 'jack',
	age: 19,
	sayHi() {
    
    }
}
// 对象的属性和方法也可以是可选的
let config: {
    
     url: string; method?: string } = {
    
    ...}
  • Interface type (when an object type is used multiple times, the interface is generally used to describe the object type to achieve reuse )
interface IPerson {
    
    
	name: string
	age: number
	sayHi(): void
}
let person: IPerson = {
    
    
	name: 'jack',
	age: 19,
	sayHi() {
    
    }
}
// 接口类型和别名的区别在于,接口只能为对象指定类型,别名能为任意类型指定别名
// 如果两个接口之间有相同的属性或方法,可以将公共属性或方法抽离出来,通过继承来实现复用
interface Point2D {
    
     x: number; y: number }
interface Point3D extends Point2D {
    
     z: number }	// 继承了 Point2D 接口
  • Tuple type (another type of array that determines the number of elements and the type of the element at a specific index)
let posision: [number, number] = [39.5427, 116.2317]
  • Type inference (where the type is not explicitly stated, the type inference mechanism is used, that is, type annotations are omitted)
// 类型推论场景1:声明变量时初始化
let age = 18
// 决定函数返回值时
function add(num1: number, num2: number) {
    
     return num1 + num2 }
  • Type Assertion
    Because ts is a static language, the specific properties of some broader objects are not determined at compile time, which will cause errors such as inability to access some special properties. So you need to use type assertions to specify specific types.
// 当有一个标签 <a href="http://www.test.com/" id="link"></a>
const aLink = document.getElementById('link')
// 变量 aLink 的类型是 HTMLElement,该是一个宽泛(不具体)的类型
// 包含所有标签公共的属性和方法,例如 id 属性,而不包含特有的属性,例如 href
// 如要操作特有的属性或方法,要进行类型断言
const aLink = document.getElementById('link') as HTMLAnchorElement
// as 后的类型必须为之前对象的子类
const aLink = <HTMLAnchorElement>document.getElementById('link')
  • Literal type
    In ts, because the value of a constant cannot be changed, its type is a special defined type, that is, a literal type. The literal type can be any JS literal. Usually literal types are used to represent an explicit list of optional values .
const str = 'Hello TS'		// const 声明的变量为字面量类型,即 'Hello TS' 类型,而非 string 类型
function changeDirection(direction: 'up' | 'down' | 'left' | 'right') {
    
    		// 指定类型更加清晰明确
	console.log(direction)
}
  • Enumeration type
    The function of enumeration type is similar to the combination function of literal type + union type. It can also represent a clear set of optional values.
// 定义一组命名常量为枚举类型,它描述一个值,该值为这些命名常量中的一个
enum Direction {
    
     Up, Down, Left, Right }

function changeDirection(direction: Direction) {
    
    
	return direction
}
// 类似于对象,使用时可以使用 . 来访问枚举成员
changeDirection(Direction.Left)
// 枚举成员是有值的,默认为从数值 0 开始自增,即 Up, Down, Left, Right 值分别为 0, 1, 2, 3
// 如有需要,可以在定义时初始化枚举值,未明确定义初始值的以前一个初始值增加1
enum Direction {
    
     Up = 10, Down, Left, Right }	// 枚举值为 10, 11, 12, 13
// 也可以初始化每个枚举成员
enum Direction {
    
     Up = 10, Down = 16, Left = 22, Right = 32 }
// 枚举成员也可以定义为字符串枚举,字符串枚举的每个成员必须初始化值
enum Direction {
    
     Up = 'UP', Down = 'DOWN', Left = 'LEFT', Right = 'RIGHT' }
  • any type (TS does not recommend using the any type, because it will lose the advantage of TS's type protection)
    The any type is any type and will give up the type constraints of TS.
// 显式的声明 any 类型
let obj: any = {
    
     x: 0 }
// 隐式的声明 any 类型
let obj 	// 不提供类型注解也不初始化赋值
function fun(param) {
    
    		// 声明参数时不提供类型注解
	console.log(param)
}

typeofoperator

In JS, you can use typeofthe operator to view the type of data. TS can also use this operator and use it in type annotations.

let p = {
    
     x: 1, y: 2 }
function formatPoint(point: typeof p) {
    
    }

Advanced type

class class

TS fully supports classes introduced in ES2015, and adds type annotations and other syntax (such as visibility modifiers). The basis of class is the same as using TS and JS

class Person {
    
    }
const p = new Person()

According to the type inference in ts, the type of instance object p of Person class is Person. The class in ts not only provides the class syntax function, but also exists as a type.

ts declaration class needs to be type annotated or add a default initial value

class Person {
    
    
	age: number
	gender = '男'		// 类型推论
}

Constructors and instance methods

The constructor of a class needs to specify a type annotation, otherwise it will be implicitly inferred as any. Constructor does not require return value type

class Person {
    
    
	age: number
	gender: string

	constructor(age: number, gender: string) {
    
    
		this.age = age
		this.gender = gender
	}
}

The type annotations (parameters and return values) of class instance methods are the same as those of functions.

class Point {
    
    
	x = 10
	y = 10
	
	scale(n: number): void {
    
    
		this.x *= n
		this.y *= n
	}
}

inherit

js provides class inheritance, which extendscan inherit the parent class, while ts provides another inheritance method: implements, implement the interface

// extends 继承父类
class Animal {
    
    
	move() {
    
     console.log('animal move') }
}
class Dog extends Animal {
    
    		// 继承父类
	bark() {
    
     console.log('wang!') }
}
const dog = new Dog()
// implements 实现接口
interface Singable {
    
    
	sing(): void
}
class Person implements Singable {
    
    		// 类中必须提供接口中所有方法和属性
	sing() {
    
    
		console.log('唱歌')
	}
}

The difference between interface and class in ts is that interface is a description of the properties and methods of an object, and is a type and constraint. Classes are templates for objects, and classes can be instantiated into objects.

visibility

You can use visibility modifiers to control whether a class's methods or properties are visible to code outside the class. Visibility modifiers include:

  • public (public)
    Public members can be accessed from anywhere and are the default visibility.
class Animal {
    
    
	public move() {
    
    
		console.log('Moving!')
	}
}
  • protected (protected)
    A protected member is only visible to the class and subclasses in which it is declared (non-instance objects)
class Animal {
    
    
	protected move() {
    
     console.log('Moving!') }
}
class Dog extends Animal {
    
    
	bark() {
    
    
		console.log('wang!')
		this.move()		// 可以访问父类的 protected 方法
	}
}
  • Private (private)
    private members are only visible to the current class, and are invisible to instance objects and subclasses.
class Animal {
    
    
	private move() {
    
     console.log('Moving!') }
	walk() {
    
    
		this.move()
	}
}

read only

Commonly used modifiers for class classes include readonlythe modifier, which is used to prevent attributes from being assigned outside the constructor.

class Person {
    
    
	readonly age: number = 18
	constructor(age: number) {
    
    
		this.age = age
	}
}

Type compatibility

Type compatibility is not a specific type, but a characteristic of the types in ts.

Two types of systems commonly used today are

  • Structural Type System Structural Type System
  • Nominal Type System indicates the type system

TS uses a structured type system, also called duck typing . Type checking focuses on the shape of the value . That is, in a structural type system, two objects are considered to be of the same type if they have the same shape.

class Point {
    
     x: number; y: number }
class Point2D {
    
     x: number; y: number }
const p: Point = new Point2D()			// 类型注解为 Point 类型,但是由于类型兼容性,可以使用 Point2D 类进行实例化

Because TS is a structured type system, it only checks whether the structures of Point and Point2D are the same. If you use a marked type system, such as C#, Java, etc., the types are not compatible because they are different classes.

In the actual object type used, if the members of y are at least the same as x, then x is compatible with y (more members can be assigned to fewer members), for example

class Point {
    
     x: number; y: number }
class Point3D {
    
     x: number; y: number; z: number }
const p: Point = new Point3D()

In addition to class, other types of TS are also compatible with each other:

  • The compatibility between interfaces is similar to that of classes, and classes and interfaces are also compatible
  • There is also type compatibility between functions, but for assignment, you need to consider the number of parameters, parameter types, and return value types.
// 参数个数的影响:参数多的兼容参数少的(参数少的可以赋值给多的)
type F1 = (a: number) => void
type F2 = (a: number, b: number) => void
let f1: F1
let f2: F2 = f1
// 最常用的是数组的 forEach 方法,此方法的函数参数应该有3个参数,但实际使用中经常只使用第一个参数
// 即省略用不到的函数参数
arr.forEach(item => {
    
    })
arr.forEach((value, index, array) => {
    
    })
// 参数类型的影响:相同位置的参数类型要相同(原始类型)或兼容(对象类型)
type F1 = (a: number) => string
type F2 = (a: number) => string
let f1: F1
let f2: F2 = f1
// 如果函数参数是接口或 class,则和之前的接口或对象兼容性冲突
interface Point2D {
    
     x: number; y: number }
interface Point3D {
    
     x: number: y: number; z: number }
type F2 = (p: Point2D) => void
type F3 = (p: Point3D) => void
let f2: F2
let f3: F3 = f2		// f2 的成员少,则可以赋值给成员多的 f3,不可以反过来
// 返回值类型的影响:只关注返回值类型本身
// 返回值类型是原始类型,可以互相兼容
type F5 = () => string
type F6 = () => string
let f5: F5
let f6: F6 = f5		
// 返回值类型是对象或接口,成员多的可以赋值给成员少的
type F7 = () => {
    
     name: string }
type F8 = () => {
    
     name: string; age: number }
let f7: F7
let f8: F8
f7 = f8

Crossover type

Cross type ( &) function is similar to interface inheritance (extends), used to combine multiple types into one type (commonly used for object types)

interface Person {
    
     name: string }
interface Contact {
    
     phone: string }
type PersonDetail = Person & Contact	// PersonDetail 同时具备了 Person 和 Contact 的所有属性类型

&extendsThe difference between and is that the way to handle type conflicts between properties with the same name is different.

interface A {
    
    
	fn: (value: number) => string
}
interface B extends A {
    
    
	fn: (value: string) => string	// 会报错,因为 extends 检查到类型不兼容
}
interface C {
    
    
	fn: (value: string) => string
}
type D = A & C		// 不会报错,交叉类型兼容两者,相当于 fn: (value: string | number) => string

Generics

Generics allow functions to work with multiple types while ensuring type safety , thereby achieving reuse . They are often used in functions, interfaces, and classes. For example:

// 函数返回参数数据本身,可以接收任意类型。如果不使用 any 类型,普通的类型只能实现单一类型,而使用泛型则可以实现
// 在函数名称后使用 <> 尖括号,并添加类型变量 Type。它是一种特殊类型的变量,它处理类型而不是值
function id<Type>(value: Type): Type {
    
     return value }
// 调用时再指定 Type 的具体类型
const num = id<number>(10)
const str = id<string>('a')
// 在实际使用中,通常会省略尖括号达到简化使用
const num1 = id(10)
const str1 = id('a')

Generic constraints

Because by default, the variable type of a generic function Typecan represent multiple types, this results in no access to any properties, such as

function id<Type>(value: Type): Type {
    
    
	console.log(value.length)		// 会报错,因为类型 Type 不一定有 length 属性
	return value
}

At this point, generic constraints can be added to shrink the type range. There are two main ways to implement generic constraints: 1. Specify a more specific type, 2. Add constraints

// 指定更加具体的类型,收缩至数组类型
function id<Type>(value: Type[]): Type[] {
    
    
	console.log(value.length)
	return value
}
// 添加约束,满足某些条件要求
interface ILength {
    
     length: number }
function id<Type extends ILenght>(value Type): Type {
    
    
	console.log(value.length)
	return value
}

multiple generics

Generic type variables can have multiple types, and type variables can also be constrained . For example

// 设置两个泛型,第二个泛型受到第一个泛型约束
function getProp<Type, Key extends keyof Type>(obj: Type, key: key) {
    
    
	return obj[key]
}

Generic interface

Interfaces can also use generics, but when using them, you need to explicitly specify the specific type.

interface IdFunc<Type> {
    
    
	id: (value: Type) => Type
	ids: () => Type[]
}
let obj: IdFunc<number> = {
    
    
	id(value) {
    
     return value },
	ids() {
    
     return [1, 3, 5] }
}

In fact, the array in JS is a generic interface in TS

const strs = ['a', 'b', 'c']
const nums = [1, 2, 3]
// forEach 方法的参数就是根据数组元素不同而不同

Generic class

Classes can also use generics. For example, the base class of a React component is a generic class, and different components will have different member classes.

interface IState {
    
     count: number }
interface IProps {
    
     maxLength: number }
class InputCount extends React.Component<IProps, IState> {
    
    
	state: IState = {
    
     count: 0 }
	render() {
    
     return <div>{
    
    this.props.maxLength}</div> }
}

Creating a generic class is similar to creating a generic interface

class GenericNumber<NumType> {
    
    
	defaultValue: NumType
	add: (x: NumType, y: NumType) => NumType
}
const myNum = new GenericNumber<number>()
myNum.defaultValue = 10

Generic tools

TS has built-in some commonly used tool types to simplify some common operations in TS. The most commonly used ones are the following:

  • Partial<Type>Used to construct a type and set all properties of Type to optional
interface Props {
    
    
	id: string
	children: number[]
}
type PartialProps = Partial<Props>		// Props 的属性是必选的,经过 Partial 处理,新类型所有属性为可选
  • Readonly<Type>Used to construct a type and set all properties of Type to read-only
  • Pick<Type, Keys>A new type can be constructed by selecting a set of properties from Type
interface Props {
    
    
	id: string
	title: string
	children: number[]
}
type PickProps = Pick<Props, 'id' | 'title'>
  • Record<Keys, Type>Used to construct an object type. The attribute key is Keys and the attribute type is Type . Note that all attribute types are the same.
// 创建一个对象,属性键为 a, b, c 类型均为 string[]
type RecordObj = Record<'a' | 'b' | 'c', string[]>
// 等同于
type RecordObj = {
    
    
	a: string[];
	b: string[];
	c: string[];
}

Index signature type

In most cases, the structure of the object can be determined and the correct type added to the object before it is used. But occasionally it is impossible to determine which attributes are in the object (or any number of attributes can appear in the object). In this case, the index signature type can be used .

interface AnyObj {
    
    
	[key: string]: number
}

In this example, is used [key: string]to constrain the types of attribute names allowed to appear in the interface, so that any number of attributes that meet the constraints can appear. key is just a placeholder and can be replaced by any legal variable name. For example, in JS, {}the key of object is stringof type.

Mapping type

Mapping types can create new types (object types) based on old types, reducing duplication and improving development efficiency. For example, the previous generic types were implemented internally by mapped types.

// 根据联合类型创建
type PropKeys = 'x' | 'y' | 'z'
type Type1 = {
    
     x: number; y: number; z: number}
// 使用映射类型实现:
type Type2 = {
    
     [key in PropKeys]: number }
// 根据对象类型创建
type Props = {
    
     a: number; b: string; c: boolean }
type Type3 = {
    
     [key in keyof Props]: number }
// 泛型工具类型 Partial<Type> 的实现
type Partial<Type> = {
    
    
	[P in keyof Type]?: T[P]
}

Mapping types are based on index signature types, so the syntax is similar to index signature types and is also used []. It should be noted that mapped types can only be used in type aliases, not in interfaces.

Index query (access) type

In Partial<Type>the implementation of , the syntax of is called the index query (access) typeT[P] in TS and can be used to query the type of the attribute.

type Props = {
    
     a: number; b: string; c: boolean }
type TypeA = Props['a']		// 即 number
type TypeB = Props['a' | 'b']	// number | string
type TypeC = Props[keyof Props]		// number | string | boolean

type declaration file

TS needs to be compiled into JS code for execution. TS provides a type protection mechanism. In order to extend this mechanism to JS, a type declaration file can be used to provide type information for JS.

There are two file types in TS , ts files and d.ts files. The ts file is the executable code file of ts, and the d.ts file is the type declaration file , which is only used for type declaration.

Use a type declaration file and d.tsuse exportexport in the file (you can also use it import/exportto implement modular functionality). In files that need to use shared types ts, importjust import them ( .d.tsthe suffix can be omitted when importing)

// index.d.ts
type Props = {
    
     x: number; y: number }

export {
    
     Props }
// a.ts
import {
    
     Props } from './index'

let p1: Props = {
    
     x: 1, y:2 }

Guess you like

Origin blog.csdn.net/runsong911/article/details/132293380