主要内容:
-
Typescirpt 背景(快)
-
用webpack 搭建一个ts环境(慢)
-
ts基础(快)
-
ts 进阶(慢)
-
装饰器(慢)
-
ts在 vue项目中使用
typescript 缘起
什么是typescript
为什么要用ts
ts的现状和未来
ts的ROI
主要内容
ts基础
ts 工程
强类型语言
弱类型语言
静态类型语言和动态类型语言
用webpack手动搭建一个ts环境
###
- npm i typescript -g
- npm list --depth=0
### 初始化
- npm init -y
- npm i typescript -g // 全局安装,可以在任何地方使用ts编译器,tsc
- tsc --init
- tsc
### webpack
- npm i webpack webpack-cli webpack-dev-server -D
- > build
- npm i ts-loader typescript HtmlWebpackPlugin --D
- HtmlWebpackPlugin 通过一个模板,帮助我们生成网站的首页,而且把输出文件自动切到这个文件中
- npm i webpack-merge@4.2.1 -D
###
- 修改npm脚本
- 入口 main
- start
- "start": "webpack-dev-server --mode=development --config ./build/webpack.config.js",
- build
- "build": "webpack --mode=production --config ./build/webpack.config.js"
在线编译 https://www.typescriptlang.org/play/index.html
基本类型
类型注解
一个角色判断的例子
枚举
枚举类型
一组有名字的常量集合
-
枚举成员的值是只读类型的,一旦定义了不能修改
-
实现原理:反向映射,枚举被编译成了一个对象,枚举成员的名称被作为了key,枚举成员的值被作为了value,value又作为key,成员的名称又被作为value;
-
适用情况:将程序中不容易记忆的硬编码,或者在未来可能改变的常量抽取出来,定义成枚举类型,可以提高程序的可读性和可维护性
-
在线编译 https://www.typescriptlang.org/play/index.html
enum { Reporter = 1, Developer } 最终被编译成: "use strict" var Role; (function(Role){ Role[Role["Reporter"] = 1] = "Reporter"]; Role[Role["developer"] = 1] = "developer"]; } )(Role || {Role = {})
类型别名
-
类型别名用来给一个类型起个新名字。
-
类型别名常用于联合类型
// - 我们使用 type 创建类型别名。 type Name = string; type NameResolver = () => string; type NameOrResolver = Name | NameResolver; function getName(n: NameOrResolver): Name { if (typeof n === 'string') { return n; } else { return n(); } }
接口
-
接口可以用来约束对象,函数,类的结构和类型,是一种代码协作的契约,必须遵守,且不能改变;
interface List { id: number, name: string } interface Result { data: List[] } function render(result: Result){ result.data.forEach((value) => { console.log(value.id,value.name) }) } let result = { // 假设result是从后端返回的接口 data: [ {id: 1, name: 'A', sex: 'male'}, // 只要满足要求,传入多余的字段也可以通过类型检查 {id: 2, name: 'B'} ] } render(result) // 没毛病 // 用对象字面量的方式ts会报错 render({ // 假设result是从后端返回的接口 data: [ {id: 1, name: 'A', sex: 'male'}, // 只要满足要求,传入多余的字段也可以通过类型检查 {id: 2, name: 'B'} ] }) 绕过这种检查的方式有三种: 1.将对象字面量赋值给一个变量,如上; 2.使用类型断言, as Result,或在前面加上<Result> - 这两种方法是等效的,建议使用第一种方法,<Result>在React会产生歧义 明确的告诉编译器,传入的对象类型就是Result,这样编译器就会绕过类型检查 render({ // 假设result是从后端返回的接口 data: [ {id: 1, name: 'A', sex: 'male'}, // 只要满足要求,传入多余的字段也可以通过类型检查 {id: 2, name: 'B'} ] } as Result) render(<Result>{ // 假设result是从后端返回的接口 data: [ {id: 1, name: 'A', sex: 'male'}, // 只要满足要求,传入多余的字段也可以通过类型检查 {id: 2, name: 'B'} ] }) 3.使用字符串索引签名 interface List { id: number, name: string, [x: string]: any // 字符串索引签名, 含义是:用任意的字符串去索引List,可以得到任意的结果,这样List可以支持多个属性了 }
接口成员属性
1. 可选属性,属性后加 ?
interface List {
id: number,
name: string,
age?: number // 可有可无
}
2. 只读属性
interface List {
readonly id: number, // id一般是只读属性
name: string
}
// 注意,只读的约束存在于第一次给对象赋值的时候,而不是第一次给只读属性赋值的时候
3. 任意属性
// 有时候我们希望一个接口允许有任意的属性,可以使用如下方式:
interface Person {
name: string;
age?: number;
[propName: string]: any;
}
let tom: Person = {
name: 'Tom',
gender: 'male'
};
// 使用 [propName: string] 定义了任意属性取 string 类型的值。
// 需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集:
interface Person {
name: string;
age?: number;
[propName: string]: string;
}
let tom: Person = {
name: 'Tom',
age: 25,
gender: 'male'
};
// 上例中,任意属性的值允许是 string,但是可选属性 age 的值却是 number,number 不是 string 的子属性,所以报错了。
// 另外,在报错信息中可以看出,此时 { name: 'Tom', age: 25, gender: 'male' }
// 的类型被推断成了 { [x: string]: string | number; name: string; age: number; gender: string; },
// 这是联合类型和接口的结合。
函数类型接口
-
对方法传入的参数,以及返回值进行约束,并且可以进行批量约束;
// 1. 用变量定义函数 let add: (x: number, y: number) => number // 2. 用接口定义,这两种定义方式是等价的 interface Add { (x: number, y: number): number } // 3. 类型别名,type关键字 type Add = (x: number, y: number) => number // 4. 函数定义 function add4(x: number, y: number) { return x + y; } let add: Add = (a, b) => a + b;
混合类型接口
-
就是一个接口,既可以定义函数,又可以像对象一样,拥有属性和方法;
// 混合类型接口定义类库 interface Lib { // 没有参数,也没有返回值 (): void; version: string; do(): void; } function getLib() { let lib: Lib = (() => {}) as Lib // 类型断言 lib.version = '1.0' lib.do = () => {} return lib; } // 放在函数里,不暴露在全局,这样可以创建多个lib了 let lib1 = getLib(); lib1(); lib1.do(); let lib2 = getLib();
类型断言
语法
值 as 类型
或
<类型>值
类与接口
交叉类型和联合类型
交叉类型
- 交叉类型比较适合做对象的混入;
- 组合多个类型组成新的类型,新类型包含了原类型的所有属性
// 交叉类型: 取所有类型的并集
interface DogInterface {
run(): void
}
interface CatInterface {
jump(): void
}
let pet:DogInterface & CatInterface = {
run(): void
jump(): void
}
联合类型
// 联合类型就是声明的类型并不确定,可以为多个类型中的一个
let a: string | number = "1"
// 字面量联合类型
// 有时候我们不仅要限制变量的类型,还需要限制变量的取值在某个特定的范围内
// 字符串联合类型
let c: 'a' | 'b' | 'c'
// 数字联合类型
let b: 1 | 2 | 3
// 对象联合类型
class Dog implements DogInterface{
run(): void
eat(): void
}
class Cat implements CatInterface{
jump(): void
eat(): void
}
enum Master {
Boy,
Girl
}
function getPet(master: Master){
let pet = master === Master.Boy ? new Dog(): new Cat()
// 如果一个对象是联合类型,在类型未确定的情况下,只能访问所有类型的公有成员,这种情况下只能访问所有类型的交集
pet.eat()
return pet;
}
索引类型
-
索引类型可以实现对对象属性的查询和访问,
-
再配合泛型约束,就使我们 能够建立对象,对象属性,以及属性值之间的约束关系
-
索引类型的查询操作符
-
keyof T
-
表示类型 T 的所有公共属性的字面量联合类型;
// key的类型是a和b的字面量联合操作类型 interface Obj { a: number; b: string; } let key: keyof Obj
-
索引访问操作符
-
T[K]
-
表示对象T的属性k所代表的类型
let value: Obj["a"]
泛型约束
-
T extends U
-
表示泛型变量可以通过继承某个类型或某些属性
let obj = { a: 1, b: 2, c: 3 } function getValues(obj: any, keys: string[]){ return keys.map(key => obj[key]) } console.log(getValues(obj, [a, b])) // 1,2 console.log(getValues(obj, [e, f])) // [undefind, undefind] // 此时ts应该报错 // 经过改造后的函数 function getValues<T,K extends keyof T>(obj: T,keys: K[]): T[K][]{ // 先定义个泛型变量T,用它来约束obj // 再定义一个泛型变量K,用它来约束keys数组 // 把 K 增加一个类型约束,让他来继承obj所有属性的联合类型,K extends keyof T; // 函数的返回值是一个数组[],数组的类型就是属性K对应的类型T[K] // 这样就通过一个索引类型把getValues改造完毕了,这时ts类型检查就发挥作用了 // 如果我们指定一个不再obj范围内的属性,ts就会报错 } type Dictionary<T> = { [key: string]: T }
条件类型
T extends U ? X : Y
// 如果类型T,可以被赋值给类型U,结果类型就是X类型,否则Y类型;
// 条件类型使类型具有了不唯一性,同时也增加了语言的灵活性;
type TypeName<T> =
T extends string ? "string" :
T extends number ? "number" :
T extends boolean ? "boolean" :
T extends undefined ? "undefined" :
T extends Function ? "function" :
"object";
type T1 = TypeName<string>
type T2 = TypeName<string[]>
泛型
泛型的概念
首先,我们来实现一个函数 createArray
,它可以创建一个指定长度的数组,同时将每一项都填充一个默认值:
function createArray(length: number, value: any): Array<any> {
let result = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
createArray(3, 'x'); // ['x', 'x', 'x']
这段代码编译不会报错,但是一个显而易见的缺陷是,它并没有准确的定义返回值的类型:
Array<any>
允许数组的每一项都为任意类型。但是我们预期的是,数组中每一项都应该是输入的 value
的类型。
这时候,泛型就派上用场了:
function createArray<T>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
createArray<string>(3, 'x'); // ['x', 'x', 'x']
泛型的好处
类型检查机制
类型推断
类型兼容性
类型保护
命名空间 namespace
-
命名空间一个最明确的目的就是解决重名问题。
-
想要在全局内可见的话,用export 导出
-
可拆分
-
Shape.circle(2)
-
命名空间和模块不要混用
-
不要在一个模块里面使用命名空间,命名空间最好全局的环境中使用
新语法索引
declare 可以为外部变量提供类型声明
-
declare var
声明全局变量 -
declare function
声明全局方法 -
declare class
声明全局类 -
declare enum
声明全局枚举类型 -
declare namespace
声明(含有子属性的)全局对象 -
interface
和type
声明全局类型 -
export
导出变量 -
export namespace
导出(含有子属性的)对象 -
export default
ES6 默认导出 -
export =
commonjs 导出模块 -
export as namespace
UMD 库声明全局变量 -
declare global
扩展全局变量 -
declare module
扩展模块 -
///
三斜线指令-
/// <reference path="..." />
指令是三斜线指令中最常见的一种。 它用于声明文件间的 依赖。三斜线引用告诉编译器在编译过程中要引入的额外的文件。
-
-
装饰器
ts装饰器 https://my.oschina.net/u/3150996/blog/3113969
类装饰器
类装饰器在类声明之前被声明,(紧靠着类声明),类装饰器应用于类构造函数,可以用来监视、修改或替换类定义;
// 类装饰器:普通装饰器(无法传参)
function logClass(params){
// params 就是当前类
console.log(params)
params.prototype.apiUrl = "动态扩展的属性"
params.prototype.fetch = function(){
console.log('我是一个fetch方法')
}
}
@logClass
class HttpClient {
constructor(){
}
getData(){
}
}
let http = new HttpClient()
console.log(http.apiUrl) // 动态扩展的属性
http.fetch() // 我是一个fetch方法
类装饰器:装饰器工厂
// 类装饰器:装饰器工厂(可以传参)
function logClass(params){
console.log(params)
// params调用时的实参
return function(target){
// target 当前类 HttpClient
console.log(target)
params.prototype.apiUrl = params
}
}
@logClass('newbanker')
class HttpClient {
constructor(){
}
getData(){
}
}
let http = new HttpClient()
console.log(http.apiUrl) // newbanker
属性装饰器
属性装饰器会在运行时当做函数被调用,传入下列两个参数:
-
对应静态成员来说是类的构造函数,对于实例成员是类的原型对象;
-
属性成员的名称;
// 类装饰器:装饰器工厂(可以传参) function logClass(params){ console.log(params) // params调用时的实参 return function(target){ // target 当前类 HttpClient console.log(target) } } // 属性装饰器 function logProperty(params){ return function(target, attr){ console.log(target) // console.log(attr) // 当前属性url target.attr = params } } @logClass class HttpClient { @logProperty('https://baidu.com') public url getData(){ } }
方法装饰器
// 方法装饰器 一
function get(params: any) {
return function(target: any, methodName:any, desc: any) {
console.log(target) // 原型对象,可以扩展类的原型属性和方法;
console.log('methodName', methodName)
console.log('desc', desc)
// 可以扩展当前实例的属性和方法
target.apiUrl = 'http://www.sina.cn'
target.run = function() {
console.log('run')
}
}
}
class HttpClient {
public url: any
[propName:string]: any
@get('http://www.baidu.com')
getData(args: any) {
}
}
const http = new HttpClient()
console.log(http.apiUrl) // http://www.sina.cn
http.run()
方法装饰器二
function get(params: any) {
console.log('get params', params)
return function(target: any, methodName:any, desc: any) {
console.log(target) // 原型对象,可以扩展类的原型属性和方法;
console.log('methodName', methodName)
console.log('desc', desc)
// 可以扩展当前实例的属性和方法
target.apiUrl = 'http://www.sina.cn'
target.run = function() {
console.log('run')
}
// console.log(methodName) // 当前方法名 getData
// // 修改装饰器的方法,把装饰器方法里面传入的所有参数改为string类型;
// // 保存当前方法
const save = desc.value
desc.value = function(...args: any[]) {
args = args.map((v) => {
return String(v)
})
// save.apply(this, args) // 把当前方法this传进去,代表在这个方法里面调用save方法;并传入参数;
args.push(params)
console.log('args---', args)
return args
}
// console.log(desc) // 描述信息 { value: f, xxx: xxx }
// // 不加此方法会直接替换实例的方法,加上会修改
}
}
class HttpClient {
public url: any
[propName:string]: any
@get('http://www.baidu.com')
getData(...args: any[]) {
console.log('getData arg', args)
console.log('我是getData的method')
}
}
const http = new HttpClient()
console.log(http.apiUrl) // http://www.sina.cn
http.getData(123, '项布斯')
http.run()
装饰器执行顺序
// 类装饰器:普通装饰器(无法传参)
function logClass1(params: any) {
return function(target: any) {
console.log('类装饰器1111')
}
}
function logClass2(params: any) {
return function(target: any) {
console.log('类装饰器2222')
}
}
function logAttribute(params?: any) {
return function(target: any, attrName: any) {
console.log('属性装饰器333')
}
}
function logMethod(params?: any) {
return function(target: any, attrName: any) {
console.log('方法装饰器4444')
}
}
function logParams1(params?: any) {
return function(target: any, attrName: any) {
console.log('方法参数装饰器555')
}
}
function logParams2(params: any) {
return function(target: any, attrName: any) {
console.log('方法参数装饰器666')
}
}
@logClass1('http:www.baidu.con')
@logClass2('xxxx')
class HttpClient {
@logAttribute()
public apiUrl: string = 'newbanker'
@logMethod()
getData() {
}
// @ts-ignore
setData(@logParams1('logParam1') attr1: any, @logParams2('logParam2') attr2: any) {
}
}
const http: any = new HttpClient()
// 属性装饰器 > 方法装饰器 > 方法参数装饰器2 > 方法参数装饰器1 > 类装饰器2 > 类装饰器1
属性 》 方法 》 方法参数 》 类
统一类型的装饰器,如果有多个,从后往前、从下往上执行
vue项目
第三方包查找: https://microsoft.github.io/TypeSearch/
vue 官网 ts https://cn.vuejs.org/v2/guide/typescript.html#%E5%9F%BA%E6%9C%AC%E7%94%A8%E6%B3%95
- @Prop({ required: true }) private name!: string 非空断言
- get xxMethod 存取器,作为计算属性使用
react项目
ts工程
如何选择ts编译工具
tslint和eslint
ts https://ts.xcatliu.com/engineering/lint.html
typescript和eslint
babel-eslint和typescript-eslint
ts工具体系
来源:oschina
链接:https://my.oschina.net/u/3150996/blog/4460840