写在前面
ts是拥有类型系统的js的超集,近年来非常火热。可以这么说,ts才是真正意义上的js。虽然ts的官方文档非常全面,但是对于原来没有接触过ts的同学来说,全篇通读下来需要耗掉不少时间,这篇文章旨在为尝试入门ts的同学使用。
本文将从以下几部分进行总结:
-
TypeScript的优势
-
强类型与弱类型的区别
-
动态类型与静态类型的区别
-
基础类型
-
接口类型
-
函数
-
类
-
泛型
TypeScript的优势
1.帮助更好地重构代码
一个好的代码习惯是常常对自己写的代码进行小的重构,使得代码可维护性更强。但是对于很多线上运行的代码,代码测试覆盖率往往不是很高,有时候哪怕一个变量名的改动,都会牵一发而动全身。而对于使用ts编写的项目就不会有这种担心。ts的静态检查特性会帮助找出代码中有错误的部分。
2.vscode等IDE的提示更加智能
js是一门动态弱类型解释语言,变量声明后可以改变类型,而且类型需要在运行时才能确定。而ts的报错提示是在编译时,不是在运行时。所以使用ts带来的静态类型检查等特性将使得IDE的提示更加完善。
3.类型声明本身就是非常好的文档
当你接手一个有历史包袱的项目时,肯定会头疼于文档和代码注释的缺失,而对于ts来说,是可以做到代码即文档的,通过声明文件可以知道哪些字段的含义以及哪些字段是必填和选填的。举个简单例子,当封装一个button的组件时:
export interface ButtonProps {
style?: React.CSSProperties
className?: string
label?: React.ReactNode
type?: 'primary' | 'default' | 'search'
size?: 'sm' | 'md' | 'lg' | 'mini'
disabled?: boolean
title?: string
onClick?: ((e: React.MouseEvent<HTMLButtonElement>) => void)
}
通过这些声明文件可以知道,当使用这个button文件时,style是一个可选值,表示一个可以自定义样式的style字段。type也是一个可选值,表示按钮的颜色类型,可以选择'primary','default','mini'其中的一种。disabled也是一个可选值,传入的值必须是boolean类型。所以就可以看出类型声明本身就是非常好的文档。
强类型与弱类型的区别
强类型语言: 强类型语言不允许改变变量的数据类型,除非进行强制类型转换。
例如:如果定义了一个字符串变量str,如果没有进行强制类型转换,是把str不能当作布尔值,整型等非字符型进行处理的。c,c++,Java等都是强类型语言。
弱类型语言: 定义与强类型语言相反,一个变量可以被赋予不同数据类型的值。
var a = '111';
var b = 222;
a = b;
console.log(a) // 222
如以上的js代码所示,a是一个字符串变量,b是一个整型变量,但是却可以把b赋值给a,把a打印出来的值是222。
强类型的严谨性能有效地避免很多错误。
动态类型与静态类型的区别
动态类型语言: 在执行阶段才做类型检查。
例如:js/python等就是属于动态类型语言,对类型检查非常宽松,bug可能隐藏很久才被发现。
静态类型语言: 在编译阶段就做类型检查
例如: c++/Java等属于静态类型语言,对类型检查非常严格,bug在编译阶段就会被发现。能做到代码即文档。
基础类型
ES6的类型可以分为Boolean,Number,String,Array,Function,Object,Symbol,undefined,null。而TypeScript的数据类型则在ES6的基础上加上void,any,never,元组,枚举,高级类型。
基本语法
: type
TypeScript的基本类型语法是在变量之后使用冒号进行类型标识,这种语法也揭示了TypeScript的类型声明实际上是可选的。
boolean
boolean是最基础的数据类型,在ts中,使用boolean来定义布尔值
let isDone: boolean = false;
number
在ts中,使用number来定义数值类型
let num: number = 123
string
在ts中,使用string来定义字符串类型
let name: string = 'jarod'
array
在ts中,定义数组方式有两种: 一种是在可以在元素类型后面接上[],表示由此元素组成的一个数组:
let arr1: number[] = [1, 2, 3]
还有一种是使用数组泛型,Array<元素类型> :
let arr2: Array<number> = [1, 2, 3]
元组
如果想在数组内表示不同元素怎么办?这时候就需要使用元组类型了。元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。比如,你可以定义一对值分别为number和string类型的元组。
let hello: [number, string] = [0, 'hello']
枚举
enum类型是对JavaScript标准数据类型的一个补充。像C#等其它语言一样,使用枚举类型可以为一组数值赋予友好的名字。
enum Month {
Jan,
Feb,
Mar
}
let month = [Month.Jan, Month.Feb, Month.Mar]
never
如果一个函数永远没有返回值时,我们可以声明其为void类型:
function example(): never {
throw new Error('never');
}
any
any是ts的一个特殊类型,一旦声明为any,则意味着关闭来ts的类型检查,
let x: any = 0;
x = [];
x = false;
x = '';
对于any类型的变量,可以赋予任何类型的值。使用any对迁移js的项目是很友好的。但是在真正开发中,尽量还是少用any为好。
void
在ts中,void表示函数没有返回值。
function example(): void {
console.log('this is void type');
}
undefined和null
在TypeScript里,undefined和null两者各自有自己的类型分别叫做undefined和null。和 void相似,它们的本身的类型用处不是很大:
let u: undefined = undefined;
let n: null = null;
readonly
一些对象属性只能在对象刚刚创建的时候修改其值。你可以在属性名前用 readonly来指定只读属性,在结合react使用的过程中的例子:
interface Props {
readonly name: string;
}
interface State {
readonly color: string;
}
export class Child extends React.Component<Props,State> {
childMethod() {
this.props.name = 'jarod'; // ERROR: (props are immutable)
this.state.color = 'red'; // ERROR: (one should use this.setState)
}
}
接口类型
在 TypeScript 中,我们使用接口(Interfaces)来定义对象的类型。
对象接口
赋值的时候,变量的形状必须和接口的形状保持一致。
interface Name {
first: string;
second: string;
}
var personName:Name = {
first: '张三'
} // Property 'second' is missing in type '{ first: string; }' but required in type 'Name'
ts会对每一个字段做检查,如果没有对接口中声明的字段进行定义(非可选),可以看出,定义的变量比接口少一些属性则会抛出错误。
函数接口
接口能够描述JavaScript中对象拥有的各种各样的外形。除了描述带有属性的普通对象外,接口也可以描述函数类型
interface Lib {
(): void;
version: string;
doSomething(): void;
}
function getLib() {
let lib = (() => {}) as Lib
lib.version = '1.0.0'
lib.doSomething = () => {}
return lib;
}
函数
函数声明
function sum(x: number, y: number) {
return x + y
}
函数表达式
let sum = (x: number, y: number): number => x + y
可选参数
对于参数,我们可以声明其为可选参数,即在参数后面加"?"
function buildName(firstName: string, lastName?: string) {
// ...
}
函数重载
重载允许一个函数接受不同数量或类型的参数时,作出不同的处理。
function reverse(x: number): number;
function reverse(x: string): string;
function reverse(x: number | string): number | string {
if (typeof x === 'number') {
return Number(x.toString().split('').reverse().join(''));
} else if (typeof x === 'string') {
return x.split('').reverse().join('');
}
}
类
抽象类
抽象类只能在实例中使用,不能直接被实例化。
abstract class Animal {
eat() {
console.log('eat')
}
abstract sleep(): void
}
class Dog extends Animal {
constructor() {
super()
}
sleep() {
console.log('Dog sleep')
} // 在子类中实现父类中的抽象方法
}
public, private和protected
TypeScript 可以使用三种访问修饰符(Access Modifiers),分别是 public、private 和 protected。
public
public 修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是 public 的
class Animal {
public name;
public constructor(name) {
this.name = name;
}
}
let a = new Animal('Jack');
console.log(a.name); // Jack
a.name = 'Tom';
console.log(a.name); // Tom
private
private 修饰的属性或方法是私有的,不能在声明它的类的外部访问
class Animal {
private name;
public constructor(name) {
this.name = name;
}
}
let a = new Animal('Jack');
console.log(a.name); // Jack
a.name = 'Tom';
// index.ts(9,13): error TS2341: Property 'name' is private and only accessible within class 'Animal'.
// index.ts(10,1): error TS2341: Property 'name' is private and only accessible within class 'Animal'.
protected
protected 修饰的属性或方法是受保护的,它和 private 类似,区别是它在子类中也是允许被访问的
class Animal {
protected name;
public constructor(name) {
this.name = name;
}
}
class Cat extends Animal {
constructor(name) {
super(name);
console.log(this.name);
}
}
泛型
定义:不预先确定的数据类型,具体的类型需要在使用的时候才能确定
例子:
声明一个打印函数,实现把传入的字符串打印出来:
function log(value: string): string {
console.log(value)
return value
}
但是这时,加一个需求,要实现能把字符串数组也打印出来:
function log(value: string): string
function log(value: string[]): string[]
function log(value: any): {
console.log(value)
return value
}
如上所示,可以用之前的函数重载来实现。
如果这时,再加一个需求,要实现能把任何类型的参数打印出来。泛型就派上用场了:
function log<T>(value: T): T {
console.log(value);
return value;
}
软件工程中,我们不仅要创建一致的定义良好的API,同时也要考虑可重用性。组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时为你提供了十分灵活的功能。