TypeScript教程
TypeScript的由来
以前我们封装组件的时候,复用时传错了数据类型,某些情况下我们就会程序报错,为了避免这种情况,我们会限制某些数据传入的类型,后来想到如果是js中有这么一种限定语法,就会变得简洁许多,后来就推出来了超级js,它简称叫Ts,即TypeScript。
介绍
TypeScript由微软开发的自由和开源的编程语言,设计目标是开发大型应用,是js的扩展(支持现有js代码),通过编译成纯 JavaScript在不同的浏览器中运行。
安装
# 全局安装
npm install -g typescript
#安装后通过tsc命令执行
tsc -v
使用
指定类型
格式:
var [变量名]:[类型] = 值
-
string
-
number
-
boolean
-
null
-
undefined
-
enum(可以是对象中的某一个属性)
-
symbol
-
void
-
any
任意类型:变量如果在声明的时候,未指定其类型,那么它会被识别为any类型
let username:string = 'laoxie';
let age:number = 18;
username = 123456;//在编译时报错
联合类型
表示取值可以为多种类型中的一种
let age:number|string = 18;
let age:number|string = '18';
类型别名|type关键字的作用
作用 : 给已有类型取别名 和 定义一个新的类型 ( 搭配联合类型使用 )
给类型指定一个新的名字
type ageType = number|string
let obj = {
age:ageType
}
类型推论
不指定则以类型推论规则指定变量类型
- 未赋值:推论为any类型(不对类型进行检查)
- 赋值:推论为值所属类型
let username = 'laoxie'
username = 100 //ts中有类型推论,报错推论username为字符串类型,不能赋值为数值类型的值
对象类型检查(对象:接口)
接口可以理解为就是定义对象的数据结构
interface Iuser {
username:string
age:number
}
let laoxie:Iuser = {username:'laoxie',age:18}
数组类型
类型+[]
let arr:number[] = [10,20,30]
let arr:Array<string> = ['laoxie','lemon','jingjing']
//效果跟 类型+[]都是一样的
接口
let arr:number[] = [10,20,30]
let arr:Array<string> = ['laoxie','lemon','jingjing']
interface Istate{
name:string,
price:number
}
interface IArr{
[index:number]:Istate
}
let arr:Istate[] = [{name:'iphone',price:998}]
let arr: { name: string; url: string }[] = [{name:'iphone',url:''}];
let arr:Array<Istate> = [{name:'iphone',price:998}]
let arr:IArr = [{name:'iphone',price:998}]
数组嵌套对象指定类型
interface IGoodslist{
id:number
name: string
readonly price:numberimgurl: string
}
let goodslist:Array<IGoodslis> =[{
id:1,
name: 'goods1',price:998,
imgurl: 'img/goods1.png'
}]
元组Tuple
元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同
let arr:[number,number,string] = [10,20,'h5']
函数类型
- 声明式函数
需要指定参数类型与返回值类型
function getData(url:string,page:number):number{
// ajax请求
return page;
}
getData('/list');//报错,缺少参数
getData('/list',1,'get')// 报错,多余参数
- 表达式函数
函数表达式除了指定function的类型,也需要指定变量的类型
let getData:(url:string,page:number)=>void = function(url:string,page:number):void{
// ajax请求
}
// 使用接口指定类型
interface IgetData{
(url:string,page:number):void
}
let getData:IgetData = function(url:string,page:number):void{
// ajax请求
}
- 可选参数
function getData(url:string,page?:number):void{
// ajax请求
}
- 默认参数
function getData(url:string,page:number=1,type:string='get'):void{
// ajax请求
}
类型断言
泛型编程
在定义函数、接口或类时不指定类型,使用时才指定类型的编程方式,格式:
Array<元素类型>
function identity<T>(arg: T): T {
return arg;
}
let output = identity<string>("myString");
接口
接口(Interfaces)可以用于对「对象的形状(Shape)」进行描述(对象类型判断)
可选属性
marketPrice可写可不写
interface IGoods{
name: string
price:number,
imgurl:string
marketPrice? :number
}
let goods1: IGoods = {
name: 'Huawei Mate40 Pro',
price:5998,
imgurl: 'img/mate40.png',
marketPrice: 6998
}
let goods2: IGoods = {
name: 'Huawei Mate40 Pro',
price:5998,
imgurl : 'img/mate4o.png',
}
只读属性
interface IGoods{
name: string
readonly price:number
imgurl:string
}
let goods2: IGoods = {
name: 'Huawei Mate40 Pro',
price:5998,
imgurl : 'img/mate4o.png'
}
goods.price = 4998
//如果加了readonly修改则会报错,不加则不会报错
任意属性
interface IGoods{
name: string
price:number,
imgurl:string
[propName: string]: any;//对象的每一个属性名都是字符串,数组则是number类型
}
let goods1: IGoods = {
name: 'Huawei Mate40 Pro',
price:5998,
imgurl: 'img/mate40.png',
marketPrice: 6998
}
let goods2: IGoods = {
name: 'Huawei Mate40 Pro',
price:5998,
imgurl : 'img/mate4o.png',
kc: 10,
salePrice: 'abc'//写了一句[propName:string]: any;之后可以写好几行未声明类型的变量
}
声明对象当中需要有一个方法
interface IGoods{
name: string
price:number,
imgurl:string
say();
}
let goods1: IGoods = {
name: 'Huawei Mate40 Pro',
price:5998,
imgurl: 'img/mate40.png',
say: function(){}
//这里没有写这个say方法的话会报错,报定义了却没有写
}
interface Person {
name: string;
// 可选属性
age?:number;
// 只读属性(只能在创建的时候被赋值)
readonly marry:boolean
// 任意属性
[propName: string]: any;
// 方法定义
say();
}
function greeter(person: Person) {
// 传入的参数persion类型必须符合Person接口的描述
return "Hello, " + person.firstName + " " + person.lastName;
}
let user = { name: "laoxie",marry:true};
greeter(user)
类
-
定义
class与ES6一致
-
继承
extends与ES6一致
-
修饰符
- public 修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是 public 的
- private 修饰的属性或方法是私有的,不能在声明它的类的外部访问
- protected 修饰的属性或方法是受保护的,它和 private 类似,区别是它在子类中也是允许被访问的
- readonly 只读属性关键字,只允许属性声明(与其他修饰符一起使用时必须写在最后)
-
类型检查
与接口一致
class Student {
// 实例属性
fullName: string;
// 静态属性
static age = 18;
// public firstName
// 在构造函数参数中使用修饰符,等效于以上写法
constructor(public firstName, public middleInitial, public lastName) {
this.fullName = firstName + " " + middleInitial + " " + lastName;
}
}
interface Person {
firstName: string;
lastName: string;
}
function greeter(person : Person) {
return "Hello, " + person.firstName + " " + person.lastName;
}
let user = new Student("Jane", "M.", "User");
document.body.innerHTML = greeter(user);
命名空间namespce(了解)
> namespce目的就是解决重名问题,可以利用同一个命名空间把代码分散到不同的文件
namespace Validation {
export interface StringValidator {
isAcceptable(s: string): boolean;
}
export interface IGoods{
name: string;
price: number;
imgurl: string;
}
}
// 使用步骤:
// 1. 引用文件(三个斜杠)
/// <reference path="Validation.ts"/>
// 2. 通过点语法调用
Validation.StringValidator;
example
namespace Form {
export interface StringValidator {
isAcceptable(s: string): boolean;
}
export interface IGoods{
name: string;
price: number;
imgurl: string;
}
}
// 使用步骤:
// 1. 引用文件(三个斜杠)
<reference path="./ns/form.ts"/>
// 2. 通过点语法调用
const cartlist:Array<Form.IGoods> = [{name:'goods1',price:988,imgurl:'img/goods1.png'}]
PS:命名空间最终会被生命为全局对象,所以在typescript中已经不推荐使用,建议使用Module来组织代码结构
-
模块
- CommonJS(默认)
- ES Module
-
lambda 函数的箭头语法
与ES Module一致
-
可选参数以及默认参数
与ES Module一致
配置文件
- tsconfig.json
通过
tsc --init生成
有tsconfig.json就不需要<参数>了
tsc <...文件> <参数>,直接tsc <...文件>
{
"compilerOptions": {
// 与 Vue 的浏览器支持保持一致
"target": "es5",
// 这可以对 `this` 上的数据属性进行更严格的推断
"strict": true,
// 如果使用 webpack 2+ 或 rollup,可以利用 tree-shake:
"module": "es2015",
"moduleResolution": "node"
}
}
- webpack加载器:ts-loader
{
//...
module: {
rules: [
{ test: /.tsx?$/, loader: "ts-loader" }
]
}
}
tsc命令
-
格式:
tsc <...文件> <参数> -
参数:
-
--help 显示帮助信息
-
--module 载入扩展模块
-
--target 设置 ECMA 版本
tsc 文件名 --target es2015
- --declaration 额外生成一个 .d.ts 扩展名的文件。
# 以下命令会生成 ts-hw.d.ts、ts-hw.js 两个文件
tsc ts-hw.ts --declaration
-
--removeComments 删除文件的注释
-
--out 编译多个文件并合并到一个输出的文件
-
--sourcemap 生成一个 sourcemap (.map) 文件。
sourcemap 是一个存储源代码与编译代码对应位置映射的信息文件。
-
--module noImplicitAny 在表达式和声明上有隐含的 any 类型时报错
-
--watch 在监视模式下运行编译器。会监视输出文件,在它们改变时重新编译。
PropType
使用TS的时候有没有遇到过,一个组件props 需要自己定义的接口和类型的属性验证,那么该怎么办呢?那么我们就需要用到PropType
假如我有一个todoItem 组件,需要item 信息属性,需要准守 TodoItem interface。
1、 引入
import { PropType } from 'vue'
复制
2、 定义接口
export interface TodoItem {
text: string
done: boolean
}
复制
3、 属性验证
props: {
todo: {
type: Object as PropType<TodoItem>,
default: {
text: '',
done: false
}
}
},
as操作符的使用
let { redirect } = params as { redirect: string };
这里的意思是说params的类型是个object 并且里面有一个redirect的属性是string类型的
let { redirect } = params;前半段是解构赋值取params.redirect
params as { redirect: string };后半段是给params定义类型
类型断言
有时候你会遇到这样的情况,你会比 TypeScrip t更了解某个值的详细信息。
通常这会发生在你清楚地知道一个实体具有比它现有类型更确切的类型。
通过类型断言这种方式可以告诉编译器,“相信我,我知道自己在干什么”。
类型断言好比其它语言里的类型转换,但是不进行特殊的数据检查和解构。
它没有运行时的影响,只是在编译阶段起作用。
TypeScript 会假设你,程序员,已经进行了必须的检查。
类型断言有两种形式。
其一是“尖括号”语法:
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
另一个为as语法:
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
两种形式是等价的。\
使用场景,在new的时候需要断言声明example:
new RateLimit({
//...
})//报错“new”表达式的目标缺少构造签名,它隐式具有“any”类型。
正确写法:
new (RateLimit as any)({
//...
}),
然而,当你在 TypeScript 里使用 JSX 时,只有 as语法断言是被允许的。
declare关键字
此关键字用于声明全局变量的校验。
为什么在 TypeScript 可以正常使用 JSON、Math、Object,这些全局对象而编译器不会报错呢?
比如使用 Math.random()、JSON.parse() 等。
这是因为 TypeScript 内部帮我们完成了全局声明的操作。
如:
declare var JSON: JSON;declare var Math: Math;
declare var Object;
ObjectConstructor;