大家好,我是小丞同学,一名大二的前端爱好者
这篇文章是学习 TypeScript 的学习笔记
非常感谢你的阅读,不对的地方欢迎指正
愿你忠于自己,热爱生活
全局安装 typesctipt
npm i -g typescript
创建一个 TS
文件
console.log('Hello Ts');
通过命令转化 TS
tsc '文件名'
js 是动态类型语言,所以有很多时候都不会报错,但是会存在很多的问题
定义一个 number
类型的值
let a: number
a = 10
a = 'hello' //这一行代码会报错,因为 a 的变量类型是 number ,不能赋值字符串
但是编译还是会成功的
定义一个 string
类型的值
let b: string
b = 'hello'
b = 10 // b
声明完直接赋值
let c: boolean = true
如果变量的声明和赋值是同时进行的,TS 可以自动对变量进行类型检测
let c = false
c = 123 // 报错
限定取值范围为男和女
let d: '男' | "女"
d = '男'
任意类型,相当于对改变量关闭了类型检测,显示 any
使用 TS ,不建议使用 any
let e: any
e = 1
e = 'hello'
声明变量不指定类型,就会被设置为 any
,隐式 any
unknown
表示未知类型,是一个类型安全的 any
unknown
类型的变量,不能直接赋值给其他变量
let f: unknown
f = 'hello'
f = 1
f = true
类型断言
当我们需要将 unknown
类型的变量赋值给其他类型的变量的时候,我们可以给他指定类型
c = f as boolean
// 第二种写法
c = <boolean>f
在括号后面跟上一个指定的类型
function sum(a: number, b: number): number {
return a + b
}
void
表示空,表示没有返回值
never
表示永远不会返回值
// 指定对象包含的属性
let b: { name: string }
b = { name: '小丞' }
必须对象成员完全一致,如果对象中有可选属性,我们可以采用 ?
来处理
let b: { name: string, age?: string }
b = { name: '小丞' }
这样有没有 age
都可以
如果需要设置任意都可以,可以采用下面的方式
let b: {
name: string, [propName: string]: any }
但是一定要有 name
成员
声明指定类型的数组
let e: string[]
e = ['a', 'b']
也可以用这种方法
let f: Array<number>
固定长度的数组
let h: [string, string]
h = ['hello', 'asd']
h = [123, 'saf'] // 错误
enum Gender {
Male,
Female
}
// 指定枚举类型
let i: { sex: Gender }
// 设定值
i = {
sex: Gender.Male
}
这里表示且
的意思,也可以用 |
表示或噢
let j: {
name: string } & {
age: number }
j = {
name: 'nan', age: 20 }
type myType = 1 | 2 | 3 | 4 | 5
用来简化类型的书写
tsconfig.json
是 ts
的配置文件,可以根据这个信息来进行代码编译
include
表示需要编译的文件,**
表示任意文件夹,*
表示任意文件
"include": [
"./src/**/*"
]
指定 src
下任意目录的任意文件
exclude
表示排除的文件,不需要编译的文件,一般不用,和 include
的用法一样
重头戏 compilerOptions
它是一个对象,里面有很多的配置
target
用来配置编译后的 js
版本
"compilerOptions": {
"target": "ES3"
}
例如这里,我们指定编译版本为 ES3
ES6
语法就会降级为 ES3
需要使用的模块化方案
"module": "ES2015"
有以下可选值
'none', 'commonjs', 'amd', 'system', 'umd', 'es6', 'es2015', 'es2020', 'esnext'.
用来指定项目使用到的库
"compilerOptions": {
"target": "ES3",
"module": "ES6",
"lib": ["DOM", "ES6"]
}
指定编译后文件所在的目录
"compilerOptions": {
"target": "ES3",
"module": "ES6",
"lib": ["DOM", "ES6"],
"outDir": "./dist"
}
把代码合并成一个文件
设置outFile
后,把所有全局作用域中的代码合并到一个文件
只有模块化规范为 amd
和 system
时可以合并
"compilerOptions": {
"target": "ES3",
"module": "ES6",
"lib": ["DOM", "ES6"],
"outDir": "./dist",
// "outFile": "./dist/app.js"
}
是否对 JS 文件进行编译,默认是 false
"compilerOptions": {
"target": "ES3",
"module": "ES6",
"lib": ["DOM", "ES6"],
"outDir": "./dist",
// "outFile": "./dist/app.js"
"allowJs": false
}
是否检查 JS
代码是否符合语法规范,默认 false
"checkJs": false
是否移除注释,默认是 false
"removeComments": true
不生成编译后的文件,默认是 false
只想用来检查语法的时候可以用
"noEmit": true
决定编译报错的时候是否编译,默认是 false
"noEmitOnError": true
用来设置编译后的文件是否使用严格模式,默认为false
"compilerOptions": {
"target": "ES3",
"module": "ES6",
"lib": ["DOM", "ES6"],
"outDir": "./dist",
// "outFile": "./dist/app.js"
"allowJs": false,
"checkJs": false,
"removeComments": true,
"noEmit": true,
"noEmitOnError": true,
"alwaysStrict": true
}
是否允许隐式的 any
类型
"noImplicitAny": true
是否允许隐式的 this
"noImplicitThis": true
检查是否存在空值
"strictNullChecks": true
可以用
a?.b
来改正
开启所有的严格检查
"strict": true
{
"include": ["./src/**/*"],
// "exclude": [],
"compilerOptions": {
"target": "ES3",
"module": "ES6",
"lib": ["DOM", "ES6"],
"outDir": "./dist",
// "outFile": "./dist/app.js"
"allowJs": false,
"checkJs": false,
"removeComments": true,
"noEmit": true,
"noEmitOnError": true,
"alwaysStrict": true,
"noImplicitAny": true,
"noImplicitThis": true,
"strictNullChecks": true,
"strict": true
}
}
安装一些依赖包,webpack
两个包推荐全局安装
yarn add webpack webpack-cli typescript ts-loader
创建一个 webpack.config.js
文件
const path = require("path");
// 所有配置信息都写在这里
module.exports = {
// 指定入口文件
entry: "./index.ts",
output: {
// 指定打包文件目录
path: path.resolve(__dirname, "dist"),
// 打包后文件的名字
filename: "bundle.js",
},
// 指定使用的模块
module: {
rules: [
{
// test 指定的是规则生效的文件
test: /\.ts$/,
use: "ts-loader",
exclude: /node_modules/,
},
],
},
};
在 package.json
的配置文件中,添加 build
命令,启动 webpack
打包
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack"
},
安装 html-webpack-plugin
包
yarn add html-webpack-plugin
引入插件
const HTMLWebpackPlugin = require("html-webpack-plugin")
配置插件
plugins:[
new HTMLWebpackPlugin()
]
后面的 webpack
没什么价值,之前学过了,在这里学浪费时间,建议学第 1 节就好了
面向对象建议直接跳到抽象类,浪费时间
当我们不需要这个类来创建对象的时候,我们就可以使用对象类
例如,我们在创建 Dog
类的时候,需要继承 Animal
类,但是我们并不需要 animal
类来创建东西,为了避免它被用来创建对象,因此我们可以使用抽象类 abstarct
abstract class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
sayHello() {
console.log('动物在叫');
}
}
这样我们就把 Animal
对象创建为了抽象类,那我们就不能使用它来创建对象了
const aaa = new Animal('sss') // 报错
我们可以在抽象类中添加抽象方法,没有方法体
// 定义抽象方法
abstract sayHello():void
只能定义在抽象类中,子类必须对抽象方法进行重写
接口时用来定义一个类结构,用来定义一个类中应该包含哪些属性和方法的
它和 type
有一点相似互通之处
我们可以采用 type
来描述一个对象类型
type myType = {
name: string,
age: number
}
我们也可以用接口来声明一个类型
interface myInterface {
name: string,
age: number
}
我们可以正常的调用它
const obj: myInterface = {
name: 'sss',
age: 111
}
在这一点上,interface
和 type
的区别在于,type
只能声明一个,而 interface
可以声明多次,例如
interface myInterface {
name: string,
age: number
}
interface myInterface {
sex: string
}
这样就以 2 个接口合并起来为准
接口可以用来在定义类的时候用来限制类的结构
接口中的所有属性都不能有实际的值,接口只定义对象的结构,而不考虑实际值,在接口中所有的方法都是抽象方法
例如这里,我们限制了一个类,有 name
还有一个 sayHello
的函数
interface myInterface {
name: string,
sayHello(): void
}
我们写一个类来实现这个接口
我们需要采用 implements
指定我们要实现的接口是哪一个
class Myclass implements myInterface {
name: string
constructor(name: string) {
this.name = name
}
sayHello() {
console.log('好~ ');
}
}`
在接口中定义一个标准,指定我们需要去实现一个类 ,在我们创建类的时候需要指定它需要实现的接口,使用
implements
现在属性是在对象中设置的,属性可以任意的被修改,这样会
导致对象中的数据变得非常不安全
我们可以在属性前添加属性的修饰符
public
修饰的属性可以在任意位置访问private
定义为私有属性,私有属性只能在类内部访问
protected
受包含的属性,只能在当前类和当前类的子类中访问// 定义私有变量
private name: String
// 定义操作的方法
setName(value: string) {
this.name = value
}
// 修改 name 的值
per.setName('猪猪')
这样我们如果不需要修改时,我们可以把修改 name
的方法隐藏起来,如果需要限制修改的值,我们可以添加 if
判断
我们也可以采用 getter
和 setter
来设置存取器
get name() {
return this._name
}
这样我们就可以直接使用 per.name
来获取值
当我们需要设置值的时候,我们可以采用 set
方法
set name(value) {
this._name = value
}
我们可以直接将属性定义在 构造函数 中,相当于一个语法糖,简化书写
constructor(public name: string, public age: number) {
}
等价于
name: string,
age: number
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
在定义函数或类时,如果遇到类型不明确时就可以使用泛型
首先我们需要在函数名后面,添加一个
,用来定义一个泛型 ,这里的 k
是自己随意取的,可以理解为是一个 k
类型,只有函数执行的时候,我们才知道它具体是什么类型
function fn(a: k): k {
return a
}
我们可以直接条用具有泛型的函数
fn(10)
像这里我们传入了一个数字 10
,它会自动推断出这次的函数调用中泛型的类型为 number
但是不是每一次都能自动推断出类型的,因此我们可以使用 类型断言
提前告知我们此次传入的数据类型
fn<string>('hello')
我们也可以指定多个泛型,建议用大写字母
function fn<k, t>(a: k, b: t): k {
return a
}
fn<string, number>('hello', 1)
指定泛型必须为某一个类
interface Inter {
length: number
}
function fn<T extends Inter > (a:T):number {
return a.length
}
在这里,我们设置了泛型 T
必须是 inter
的实现类,也就是必须有 length
属性
在类中使用泛型
class MyClass<T> {
name: T
constructor(name: T) {
this.name = name
}
}
学会用 TS 的思维去写代码,面向对象
可以设置多个预先类型
let myNumber: string | number
myNumber = 'six'
myNumber = 6
当多个变量有相同类型时,例如 youNumber
变量也需要 string
或者 number
的类型,我们再写一遍上面的代码会显得代码冗余,我们可以建立一个新的类型
type xNumber = string | number
这样我们再需要这种联合类型时,就可以直接使用
let myNumber: xNumber
这就像是接口 interface
一样,在很多情况下这两个是可以互换的
当我们需要使用一种类型时,但又想里面的参数都是可选时
我们可以采用 partial
type Person = {
name: string,
age: number
}
const myName: Partial = {age: 20}
全部可选
实现 Partial
原理
type Partial = {
[P in keyof T]?: T[P];
}
怎么理解呢?
首先 T
会接收到一个对象,也就是上面我们传入的 Person
,从此 T
表示 Person
对象,keyin T
的作用是,将 T
中的 key
值取出来,因此这里得到的是 name
和 age
的一个联合类型。
再采用 P in XXX
遍历 对象中的所有键,因此左边就是键值了,右边的 T[P]
得到的就是对应 P
对应的 value
值了,?:
符表示可选类型
删除指定类型中的某个类型,这些操作不影响原类型噢
例如我们定义了一个 Person
类型
type Person = {
name: string,
age: number
}
但我们在某个情况使用的时候,需要必须传递 age
,name
值可选
我们可以采用 Omit
,第二个参数表示要删除的类型
const myName: Omit = {age: 20}
实现原理
keyof T
键名的联合类型,K
要删除的类型,通过 Exclude
来排除 K
,再通过 Pick
取出剩下的类型
Pick>
从联合类型中挑选几个类型
type Person = {
name: string,
age: number
}
const myName: Pick = { age: 20 }
从联合类型中删除几个类型
type Person = {
name: string,
age: number
}
const myName: Exclude = { age: 20 } // 报错