一、JavaScript语言特征介绍(类型)
JavaScript是一种弱类型的,动态类型检查的语言
1.1 弱类型和强类型
1.1.1 弱类型
在定义变量的时候,我们可以为变量赋值任何数据,变量的数据类型不是固定死的,这样的类型叫做弱类型
var a = 10
a = "abc"
a = []
a = function(){}
1.1.2 强类型
在声明变量的时候,一旦给变量赋值,那么变量的数据类型就已经确定,之后如果要给该变量赋值其他类型的数据,需要进行强制数据类型转换
int a = 10
a = "10"
1.2 动态类型和静态类型
动态类型和静态类型的核心区别: 动态类型的类型检查会在代码运行的时候进行,而静态类型的类型检查则是在编译时进行
运行时类型检查
代码中的错误,只能在代码运行的时候被发现
var obj = {}
obj.forEach(function(v, i){
})
编译时类型检查
代码中的错误,在代码编译的时候被发现
int num = 100
num = "abc"
二、TypeScript
相对于 Javascript 的优势
- 提早发现代码中的Bug
- 提高代码的可读性
- 减少了复杂的错误处理逻辑
- 便于代码重构
- 增强IDE的功能
2.1 编译
npm i -g typescript
# 将 ts 文件编译成 js,生成一个对应的 js 文件
tsc hello.ts
# node 运行
node hello.js
简化执行 TS 的执行步骤
安装 ts-node 包
ts-node hello.ts
优化编译
# 解决 TS 和 JS 冲突问题,生成配置文件
tsc --init
# 自动编译
tsc --watch
# 发出错误,阻止生成 JS 文件
tsc -noEmitOnError XXX.ts
2.2 基础使用
2.2.1 布尔值
let isDone: boolean = false
注意,使用构造函数 Boolean 创造的对象不是布尔值:
let createdByNewBoolean: boolean = new Boolean(1)
// Type 'Boolean' is not assignable to type 'boolean'.
// 'boolean' is a primitive, but 'Boolean' is a wrapper object. Prefer using 'boolean' when possible.
2.2.2 数值
let decLiteral: number = 6
let hexLiteral: number = 0xf00d
// ES6 中的二进制表示法
let binaryLiteral: number = 0b1010
// ES6 中的八进制表示法
let octalLiteral: number = 0o744
let notANumber: number = NaN
let infinityNumber: number = Infinity
2.2.3 字符串
let myName: string = 'Tom'
// 模板字符串
let sentence: string = `Hello, my name is ${myName}`
2.2.4 数组
// 1. 可以在元素类型后面接上 [],表示由此类型元素组成的一个数组
const list: number[] = [1, 2, 3]
// 2. 使用数组泛型,Array<元素类型>
const list: Array<number> = [1, 2, 3]
// 3. 元组 Tuple
const x: [string, number] = ['hello', 10] // OK
const y: [string, number] = [10, 'hello'] // Error
使用
number[]
的缺点:不严谨,因为该类型的数组中可以出现任意多个数字
元组类型可以确切地标记出有多少个元素
当访问一个越界的元素,会使用联合类型替代:
x[3] = 'world' // OK, 字符串可以赋值给(string | number)类型
console.log(x[5].toString()) // OK, 'string' 和 'number' 都有 toString
x[6] = true // Error, 布尔不是(string | number)类型
2.2.5 函数
输入多余的(或者少于要求的)参数,是不被允许的
// 函数声明(Function Declaration)
function sum(x: number, y: number): number {
return x + y
}
// 函数表达式(Function Expression)
let mySum: (x: number, y: number) => number = function (x: number, y: number): number {
return x + y
}
=> 用来表示函数的定义,左边是输入类型,需要用括号括起来,右边是输出类型
2.2.6 Void
JavaScript 没有空值(Void)的概念,在 TypeScript 中,可以用 void 表示没有任何返回值的函数:
function alertName(): void {
alert('My name is Tom')
}
声明一个 void 类型的变量没有什么用,因为你只能将它赋值为 undefined 和 null:
let unusable: void = undefined
let unnull: void = null
2.2.7 Null 和 Undefined
let u: undefined = undefined
let n: null = null
与 void 的区别是,undefined 和 null 是所有类型的子类型。也就是说 undefined 类型的变量,可以赋值给 number 类型的变量:
// 这样不会报错
let num: number = undefined
而 void 类型的变量不能赋值给 number 类型的变量:
let u: void
let num: number = u
// Type 'void' is not assignable to type 'number'.
2.2.8 never
不应该存在的状态(不会走到的流程),never 类型可以分配给任何类型,任何类型都不能分给 never(除本身)
type Circle = {
kind: 'circle'
}
type Square = {
kind: 'square'
}
type Shape = Circle | Square
// default 分支永远不会走到,这块代码不会报错
function getArea(shape: Shape) {
switch(shape.kind) {
case 'circle':
return 'circle'
case 'square':
return 'square'
default:
const _exhaustiveCheck: never = shape
return _exhaustiveCheck
}
}
2.3 接口
2.3.1 定义对象
interface Person {
name: string
age: number
}
let tom: Person = {
name: 'Tom',
age: 25
}
注意:定义的变量比接口少(多)了一些属性是不允许的
// 可选属性
interface Person {
name: string
age?: number
}
let tom: Person = {
name: 'Tom'
}
// 只读属性
interface Point {
readonly x: number
readonly y: number
}
let p1: Point = { x: 10, y: 20 }
p1.x = 5 // error!
// 使用 [propName: string] 定义了任意属性取 string 类型的值
interface Person {
name: string
age?: number
[propName: string]: any
}
let tom: Person = {
name: 'Tom',
gender: 'male'
}
一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集:
// 报错,任意属性的值允许是 string,但是可选属性 age 的值却是 number,number 不是 string 的子属性
interface Person {
name: string
age?: number
[propName: string]: string
}
let tom: Person = {
name: 'Tom',
age: 25,
gender: 'male'
}
2.3.2 定义函数
interface SearchFunc {
(source: string, subString: string): boolean
}
let mySearch: SearchFunc
mySearch = function(source: string, subString: string) {
return source.search(subString) !== -1
}
2.3.3 定义数组
interface NumberArray {
[index: number]: number
}
let fibonacci: NumberArray = [1, 2, 3, 5]
interface StringArray {
[index: number]: string
}
let myArray: StringArray = ['Bob', 'Fred']
TypeScript具有ReadonlyArray<T>类型,它与Array<T>相似,只是把所有可变方法去掉了,因此可以确保数组创建后再也不能被修改:
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!
// 把整个ReadonlyArray赋值到一个普通数组也是不可以的。 但是你可以用类型断言重写:
a = ro as number[]
2.4 type 与 interface
2.4.1 继承
interface Animal {
name: string
}
interface Bear extends Animal {
honey: boolean
}
type Animal = {
name: string
}
type Bear = Animal & {
honey: boolean
}
2.4.2 声明合并
// interface 可以声明合并, 此时 MyWindow 里有两种属性
interface MyWindow {
count: number
}
interface MyWindow {
title: string
}
// 使用 type 回报 标识符重复 错误
type MyWindow = {
count: number
}
type MyWindow = {
title: string
}
2.5 字面量类型
// 只能赋值 'hello', 模拟 const
let x: 'hello' = 'hello'
let y: 18 = 18
function handleRequest(method: 'GET' | 'POST') {}
// 固定对象属性
const req = {
method: 'GET'
} as const
handleRequest(req.method)
2.6 枚举
enum Direction {
Up = 1,
Down,
Left,
Right
}
// 枚举内部默认变成字符串
{
1: 'Up',
2: 'Down',
3: 'Left',
4: 'Right',
Up: 1,
Down: 2,
Left: 3,
Right: 4
}
定义了一个数字枚举, Up使用初始化为 1(不定义值的话则从0开始)。 其余的成员会从 1开始自动增长。 换句话说, Direction.Up的值为 1, Down为 2, Left为 3, Right为 4。
2.7 泛型
当输入值跟输出值有关联的时候,就可以使用泛型
2.7.1 类型推断
function func<T>(arr: T[]): T | undefined {
return arr[0]
}
func([1, 2, 3])
func([])
2.7.2 限制条件
// 限制参数 a,b 必须有 length 属性
function longest<Type extends { length: number }>(a: Type, b: Type) {
return a.length >= b.length ? a : b
}
longest([1, 2], '123')
// 类型变量 Key 受 Type 约束,可以理解为:Key 只能是 Type 所有键中的任意一个,或者说只能访问对象中存在的属性
function getProp<Type, Key extends keyof Type>(obj: Type, key: Key) {
return obj[key]
}
const person = { name: 'jack', age: 19 }
getProp(person, 'name')
2.7.3 泛型对象类型
interface Box<T> {
content: T
}
const box1: Box<string> = {
content: 'hello'
}
const box2: Box<number> = {
content: 100
}
2.7.4 泛型工具类型
-
Partial
Partial<Type>,创建一个类型,将 Type 的所有属性设置为可选的interface Props { id: string children: number[] } // PartialProps 里的属性都是可选的 type PartialProps = Partial<Props>
-
Readonly
Readonly<Type>,创建一个类型,将 Type 的所有属性设置为只读的
-
Pick
Pick<Type, Keys>,从 Type 中选择一组属性来构造新类型
-
Record
Record<Keys, Type>,构造一个对象类型,属性键为 Keys,属性类型为 Type
2.8 函数重载
以不同方式调用函数,通常需要写两个或者更多的函数签名
2.8.1 重载签名与实现签名
解决参数、参数类型、返回类型不正确问题
// 重载签名
function fn(s: string): string
function fn(s: number): number
// 实现签名
function fn(s: string | number): string | number {
return s
}
2.9 类
class Person {
// 必须先声明,需要指定类型
name: string
// 声明属性后,必须赋值一个默认值或者在构造函数中进行初始化
age: number
constructor(name:string, age:number) {
this.name = name
this.age = age
}
}
2.9.1 类继承
class Animal {
age: number
constructor(age: number) {
this.age = age
}
eat() {
console.log('吃个大鸡腿儿')
}
}
class Dog extends Animal {
type: string
constructor(type: string, age: number) {
super(age) // 调用父类
this.type = type
}
// 子类中如果出现了和父类同名的方法,则会进行覆盖
// 也就是调用的时候,调用的就是子类中的方法了!
eat() {
console.log('狗对象中的eat方法')
}
}
var dog = new Dog('哈士奇', 18)
dog.eat() // 狗对象中的eat方法
2.9.2 访问修饰符
指的就是可以在类的成员前通过添加关键字来设置当前成员的访问权限
- public: 公开的,默认 所有人都可以进行访问
- private: 私有的, 只能在当前类中进行访问
- protected: 受保护的,这能在当前类或者子类中进行访问
- readonly: 只读的,只能修饰属性不能修饰方法
enum Color {
red,
yellow,
blue
}
class Car {
// 如果不加访问修饰符 则当前成员默认是公开的 所有人都可以访问的
public color: Color
constructor() {
this.color = Color.red
this.run()
this.loadPeople()
}
// 加了private之后,当前成员就只能在当前类中使用了!
private run() {}
// 加了protected之后,当前成员就只能在当前类或者子类中使用了!
protected loadPeople() {}
}
let byd = new Car()
byd.color
byd.run() // 报错,方法是私有的
byd.loadPeople() // 报错
class Audi extends Car {
sayHi() {
this.run() // 报错,方法是私有的
this.loadPeople()
}
}
class Cat {
// type: string
// 构造函数中给参数前面加上修饰符,就相当于声明了一个属性!
constructor(public type: string) {
// this.type = type
}
}
var cat = new Cat('橘猫')
// cat.type
// 注意:age 后面的注解 number 如果不加,就是字面量类型 18
class Person {
readonly age: number = 18
constructor(age: number) {
this.age = age
}
}
2.9.3 类的存取器
class People {
private _name: string = ''
// 属性的存取器
get name(): string{
return this._name
}
set name(value: string) {
// 设置器中可以添加相关的校验逻辑
if (value.length < 2 || value.length > 5) {
throw new Error('名字不合法,不许使用!')
}
this._name = value
}
}
var p = new People()
p.name = '司法考试来得及'
console.log(p.name)
2.9.4 implements 实现接口
类实现接口
interface PersonInterFace {
name: string
age: number
eat(): void
}
class XiaoMing implements PersonInterFace {
name: string = "小明"
age: number = 18
eat() {}
}
XiaoMing 类中必须提供 PersonInterFace 接口中指定的所有方法和属性
2.9.5 接口继承接口
interface TwoDPoint {
x: number,
y: number
}
interface ThreeDPoint {
z: number
}
interface FourDPoint extends ThreeDPoint, TwoDPoint {
time: Date
}
let poi2: FourDPoint = {
z: 100,
x: 100,
y: 100,
time: new Date()
}
2.9.6 接口继承类
class Bird {
type: string = '画眉鸟'
fly(): void {}
}
interface Fly extends Bird {}
let flyingBird: Fly = {
type: "啄木鸟",
fly(): void {}
}
2.10 说明
2.10.1 类型断言
把不确定类型的值强制确定类型
尖括号”语法:
const strValue: any = 'str'
const strLength: number = (<string>strValue).length
as语法:
const strValue: any = 'str'
const strLength: number = (strValue as string).length
2.10.2 类型注释
给变量添加类型说明
// 此时 string 就是类型注释
const strValue: string = 'str'
2.10.3 类型别名
给类型定义一个名字,用这个名字代替定义的类型
type Point = {
x: number
y: number
}
type ID = number | string
2.10.4 类型谓词
type Fish = {
swim: () => void
}
type Brid = {
fly: () => void
}
// pet is Fish 就是 类型谓词,pet 一定要是函数里存在的参数
// 如果返回值是 true,pet 就是 Fish 类型,否则就是 Brid 类型
function isFish(pet: Fish | Brid): pet is Fish {
return (pet as Fish).swim !== undefined
}
2.10.5 索引类型签名
使用场景:当无法确定对象中有哪些属性(或者说对象中可以出现任意多个属性)
interface AnyObject {
[key: string]: any
}
interface MyArray {
[index: number]: any
}
2.11 操作符
2.11.1 typeof
可以在类型上下文中引用变量或属性的类型(类型查询)
使用场景:根据已有变量的值,获取该值的类型,来简化类型书写
let p = { x: 1, y: 2 }
function formatPoint(point: { x: number, y: number }) {}
// typeof 写法
function formatPoint(point: typeof p) {}
解释:
- 使用 typeof 操作符来获取变量 p 的类型,结果与第一种(对象字面量形式的类型)相同
- typeof 出现在类型注解的位置(参数名称的冒号后面)所处的环境就在类型上下文
- 注意:typeof 只能用来查询变量或属性的类型,无法查询其他形式的类型(比如:函数调用的类型)
2.11.2 in
映射类型:基于旧类型创建新类型(对象类型),减少重复、提升开发效率
2.12 类型声明文件
2.12.1 TS 中的两种文件类型
.ts
文件、.d.ts
文件
-
.ts
文件- 既包含类型信息又可执行代码
- 可以被编译为
.js
文件,然后执行代码 - 用途: 编写程序代码的地方
-
.d.ts
文件- 只包含类型信息的类型声明文件
- 不会生成
.js
文件,仅用于提供类型信息 - 用途: 为 js 提供类型信息
总结: .ts
是 implementation(代码实现文件);.d.ts
是 declaration(类型声明文件)
2.12.2 创建自己的类型声明文件
-
项目内共享类型
如果多个.ts
文件中都用到同一个类型,此时可以创建.d.ts
文件提供该类型,实现类型共享 -
为已有 JS 文件提供类型声明
操作步骤:
- 创建 index.d.ts 类型声明文件
- 创建需要共享的类型,并使用 export 导出(TS 中的类型也可以使用 import/export 实现模块化功能)
- 在需要使用共享类型的
.ts
文件中,通过 import 导入即可(.d.ts 后缀导入时,直接省略)
declare 关键字:用于类型声明,为其他地方(比如 .js
文件)已存在的变量声明类型,而不是创建一个新的变量
- 对于 type、interface 等这些明确就是 TS 类型的(只能在TS中使用的),可以省略 declare 关键字
- 对于 let、function 等具有双重含义(在JS、TS中都能用),应该使用 declare 关键字,明确指定此处用于类型声明
// utils.d.ts
declare let count: number
declare function add(x: number, y: number): number
export { count, add }
// utils.js
let count = 10
function add(x, y) {
return x + y
}
export { count, add }
2.13 在 React 中使用 Typescript
2.13.1 使用 CRA 创建支持 TS 的项目
相对于非 TS 项目,目录结构主要由以下三个变化:
- 项目根目录增加了 tsconfig.json 配置文件: 指定 TS 的编译选项
- React 组件的文件扩展名变为: *.tsx
- src 目录中增加了 react-app-env.d.ts: React 项目默认的类型声明文件
react-app-env.d.ts 文件
三斜线指令: 指定依赖的其他类型声明文件,types 表示依赖的类型声明文件包的名称
解释: 告诉 TS 帮我加载 react-scripts 这个包提供的类型声明。
react-scripts 的类型声明文件包含了两部分类型:
- react、react-dom、node 的类型
- 图片、样式等模块的类型,以允许在代码中导入图片、SVG 等文件
TS 会自动加载该 .d.ts 文件,以提供类型声明(通过修改 tsconfig.json)中的 include 配置来验证
2.13.2 组件属性
函数组件
import { FC } from 'react'
type Props = { name: string, age?: number }
const Hello: FC<Props> = ({ name, age }) => (
<div>您好,我叫:{name},我{age}岁</div>
)
// 简化写法
const Hello = ({ name, age }: Props)
class 组件
type State = { count: number }
type Props = { message?: string }
class C1 extends React.Component {} // 无 props、state
class C2 extends React.Component<Props> {} // 有 props、无 state
class C3 extends React.Component<{}, State> {} // 无 props、有 state
class C4 extends React.Component<Props, State> {} // 有 props、state