TypeScript 知识整理

236 阅读10分钟

产生原因:JavaScript是一门动态弱类型语言;

一、入门案例

npm init -y		=>package.json
npm install -g typescript

tsc -v
tsc --init		=>tsconfig.json
// 新建一个 test.ts 的文件
var message:string = "Hello World" 
// 执行以下命令将 TypeScript 转换为 JavaScript 代码:
tsc test.ts
// 此时候在当前目录下(与 test.ts 同一目录)就会生成一个 test.js 文件,代码如下:
var message = "Hello World";

二、基本类型

类型注解

  • 作用:相当于强类型语言中的类型声明;

  • 语法:(变量/函数): type

// 原始类型;
let bool: boolean = true
let num: number = 123
// 数组;
let arr1: number[] = [1,2,3]
let arr2: Array<number> = [1,2,3]  // 使用数组泛型
// undefined,null
let un: undefined=undefined
let nu: null=null
// 函数;
let add =(x: number,y: number)=>x+y
// 对象;
let obj: {x: number,y: number}={x:1,y:2}
obj.x=3

// 元组; ——元组类型用来表示已知元素数量和类型的数组,各元素的类型不必相同,对应位置的类型需要相同。
let tuple: [number,string] = [0,'1']
// symbol;唯一的值
let s1: symbol = Symbol()
let s2=Symbol()
//console.log(s1===s2)   false
// void ——用于标识方法返回值的类型,表示该方法没有返回值。
function warnUser(): void {
    console.log("This is my warning message");
}
// any  ——声明为 any 的变量可以赋予任意类型的值。
let x;
// never ——never 是其它类型(包括 null 和 undefined)的子类型,代表从不会出现的值。
let error=()=>{
    throw new Error('error')
}
let endless=()=>{
    while(true){}
}

断言

1. 类型断言

类型断言用来手动指定一个值的类型。(即知道该对象是某种类型,使用后编译器可绕过类型检查)

语法格式:

// <类型>值 或
// 值 as 类型

let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
        
let strLength: number = (someValue as string).length;


// 类型断言,断言bar变量是foo类型的。
var foo = <foo>bar;		
var foo = bar as foo;

🔴 as操作符

TypeScript在.tsx文件里禁用了使用尖括号的类型断言。

2. 非空断言

在上下⽂中当类型检查器⽆法断定类型时,⼀个新的后缀表达式操作符 ! 可以⽤于断⾔操作对象是⾮ null 和⾮ undefined 类型。具体⽽⾔,x! 将从 x 值域中排除 null 和 undefined 。

三、接口

接口是用来约束对象、函数以及类的结构和类型。

// 接口属性格式:
// 接口定义的对象:
interface list { 
   readonly id : number; // readonly表示该属性只读;
   name ?: string;  // ?表示该属性可有可无
} 

let add : (x:number,y:number)=>number
// 接口定义的函数:
// 等同于一:
interface Add{
    (x:number,y:number):number
}
// 等同于二:(使用类型别名,更简洁)
type Add = (x:number,y:number) => number
let add : Add = (a,b) => a+b

联合类型和接口

interface RunOptions { 
    commandline:string[]|string|(()=>string); 
} 
 
var options:RunOptions = {
    commandline:"Hello"  // commandline 是字符串
}; 
console.log(options.commandline)  // Hello

options = {
    commandline:["Hello","World"]  // commandline 是字符串数组
}; 
console.log(options.commandline[0]);  // Hello
console.log(options.commandline[1]);  // World

options = {
    commandline:()=>{return "Hello World";} // commandline 是一个函数表达式
}; 
 
var fn:any = options.commandline; 
console.log(fn()); // Hello World

接口和数组

TypeScript支持两种索引签名:字符串和数字。

可以将数组的索引值和元素设置为不同类型,索引值可以是数字或字符串。

interface namelist { 
   [index:number]:string // 用数字索引;
} 
var list2:namelist = ["John",1,"Bran"]  // 错误元素 1 不是 string 类型

interface ages { 
   [index:string]:number  // 用字符串索引;
} 
var agelist:ages; 
agelist["John"] = 15   // 正确 
agelist[2] = "nine"   // 错误

接口继承

接口继承就是说接口可以使用关键字 extends通过其他接口来扩展自己。

类型别名

类型别名会给一个类型起个新名字。

type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver): Name {
    if (typeof n === 'string') {
        return n;
    }
    else {
        return n();
    }
}

接口 vs 类型别名

  1. type (类型别名)不能二次编辑,而 interface 可以随时扩展( extendsimplements
  2. 如果你无法通过接口来描述一个类型并且需要使用联合类型或元组类型,这时通常会使用类型别名。
// 实例来源: ./pages/form/basic-form/model.ts
import { AnyAction } from 'redux';
import { EffectsCommandMap } from 'dva';

export type Effect = (
  action: AnyAction,
  effects: EffectsCommandMap & { select: <T>(func: (state: {}) => T) => T },
) => void;
export interface ModelType {
  namespace: string;
  state: {};
  effects: {
    submitForm: Effect;
  };
}

const Model: ModelType = {
  namespace: 'basicForm',
  state: {},
  effects: {
      *submitForm({ payload }, { call }) {	// 正常写法...	}
  },
};
export default Model;

四、类

类描述了所创建的对象共同的属性和方法。

类的继承

  • 在创建类的时候继承一个已存在的类,这个已存在的类称为父类,继承它的类称为子类。

  • 子类不能继承父类的私有成员(方法和属性)和构造函数。

  • 一次只能继承一个类,不支持继承多个类,但 TypeScript 支持多重继承(A 继承 B,B 继承 C)。

static 关键字

static 关键字用于定义类的数据成员(属性和方法)为静态的,静态成员可以直接通过类名调用。

访问控制修饰符

  • public(默认) : 公有,可以在任何地方被访问。
  • protected : 受保护,可以被其自身以及其子类和父类访问。
  • private : 私有,只能被其定义所在的类访问。

类和接口的关系:

类可以使用关键字 implements实现接口,并将 interest 字段作为类的属性使用。

interface ILoan { 
   interest:number 
} 
 
class AgriLoan implements ILoan { 
   interest:number 
   rebate:number 
   
   constructor(interest:number,rebate:number) { 
      this.interest = interest 
      this.rebate = rebate 
   } 
} 
 
var obj = new AgriLoan(10,1) 
console.log("利润为 : "+obj.interest+",抽成为 : "+obj.rebate )
// 利润为 : 10,抽成为 : 1

五、函数

// 形式一:
function add(x: number, y: number): number {
    return x + y;
}
// 形式二:
let myAdd = function(x: number, y: number): number { return x + y; };
// 给函数每个参数和函数的返回值添加类型
// 注意:返回值类型是函数类型的必要部分,如果函数没有返回任何值,必须指定返回值类型为void而不能留空。

六、泛型

不预先确定的数据类型,具体的类型在使用的时候才能确定。

function component(value:any):any{
   return value
}
// 使用any类型会导致这个函数可以接收任何类型的arg参数,注意:传入的类型与返回的类型应该是相同的。
// 改为泛型函数:(类型T不需要预先知道,另外也保证了输入和输出的类型一致)
function component<T>(value:T):T{
    return value
}
component<string[]>(['a','b']);  // 调用方式一;
component(['a','b'])	// 调用方式二,使用了类型推断

// 使用泛型定义一个函数类型;
type Component = <T>(value:T) => T
let myCom:Component=com1

// 泛型接口;泛型变量 <T>(把泛型变量和函数参数等同看待,泛型变量是代表类型的参数,而不是代表值)
interface Com<T> {
    (value:T) : T
}

// 泛型类;
class Demo<T> {
    // 把泛型变量放在类的名称后面,这样它就可以约束所有类的成员了。注意:泛型不能应用于类的静态成员
    eat(value:T){
        return value;
    }
}

泛型约束

function loggingIdentity<T>(arg: T): T {
    console.log(arg.length);  // Error: T doesn't have .length
    return arg;
}

// 1.先定义一个接口来描述约束条件,即创建一个包含 .length属性的接口;
// 2.使用这个接口和extends关键字来实现约束
interface Lengthwise {
    length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);  // Now no error
    return arg;
}

七、高级类型

交叉类型

交叉类型是将多个类型合并为一个类型。这让我们可以把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性。( &

联合类型

联合类型表示一个值可以是几种类型之一。 使用竖线( |)分隔每个类型。

interface Bird {
    fly();
    layEggs();
}
interface Fish {
    swim();
    layEggs();
}
// 注意:如果一个值是联合类型,我们只能访问此联合类型的所有类型里共有的成员。
function getSmallPet(): Fish | Bird {
    // ...
}

let pet = getSmallPet();
pet.layEggs(); // okay
pet.swim();    // errors

declare

declare是用于声明形式存在的。

  • declare var/let/const用来声明全局的变量。
  • declare function 用来声明全局方法(函数)
  • declare class 用来声明全局类
  • declare namespace 用来声明命名空间
  • declare module 用来声明模块

工具类型

Partial

Partial 可以快速把某个接口类型中定义的属性变成可选的

type Partial<T> = {
    [P in keyof T]?: T[P];
};

八、使用TS开发React组件

// 函数组件:(代码来源:./pages/list/table-list/index.tsx)
import React, { useState, useRef } from 'react';
import ProTable, { ProColumns, ActionType } from '@ant-design/pro-table';

interface TableListProps {
  ...
}
// const TableList = (props : TableListProps) => {  等价于下面的写法 
const TableList: React.FC<TableListProps> = (props) => {
  const [createModalVisible, handleModalVisible] = useState<boolean>(false);
  const [stepFormValues, setStepFormValues] = useState({});
  const actionRef = useRef<ActionType>();
/*注意:
	1.React.FC指react的函数组件; TableListProps 指约束的类型;
	2.使用React.FC 的优点:在函数的参数(props)中隐含了children;
	3.使用React.FC 时它的默认属性必须是可选属性
*/
// 类组件:(代码来源:./pages/account/center/index.tsx)
import { RouteChildrenProps } from 'react-router';

interface CenterProps extends RouteChildrenProps {
  dispatch: Dispatch<any>;
  currentUser: Partial<CurrentUser>;
  currentUserLoading: boolean;
}
interface CenterState {
  newTags: TagType[];
  tabKey?: 'articles' | 'applications' | 'projects';
  inputVisible?: boolean;
  inputValue?: string;
}

class Center extends Component<CenterProps, CenterState> {
  // 初始化状态,使用上面CenterState泛型变量约束state
  state: CenterState = {
    newTags: [],
    inputVisible: false,
    inputValue: '',
    tabKey: 'articles',
  };
/*注意:
	1.Component<P = {}, S = {}, SS = any>  其中:P-属性类型,默认为空对象;S-状态类型;
*/
// HelloClass.tsx
import React, { Component } from 'react';
import { Button } from 'antd';

interface Greeting {
    name: string;
    firstName?: string;
    lastName?: string;
}
interface HelloState {
    count: number
}

class HelloClass extends Component<Greeting, HelloState> {
    state: HelloState = {
        count: 0
    }
    static defaultProps = {
        firstName: '',
        lastName: ''
    }
    render() {
        return (
          <>
            <p>你点击了 {this.state.count} 次</p>
            <Button onClick={() => {this.setState({count: this.state.count + 1})}}>
                Hello {this.props.name}
            </Button>
          </>
        )
    }
}
export default HelloClass;

// HelloHOC.tsx
/**	引入TS泛型约束高阶组件——该高阶组件的功能是添加loading状态;
 * 1.指定被包装组件(WrappedComponent)的类型为react预定义的引用类型(React.ComponentType);
 * 2.泛型接口需要传入泛型变量;React.ComponentType<P>,p-表示被包装组件的属性的类型;
 * 3.把高阶函数改造为泛型函数;
 * 4.用泛型函数约束Component的类型,并约束这个组件的属性类型;
 */
import React, { Component } from 'react';
import HelloClass from './HelloClass';

interface Loading {
    loading: boolean
}

function HelloHOC<P>(WrappedComponent: React.ComponentType<P>) {
    return class extends Component<P & Loading> {
      // 运用交叉类型使被包装组件同时拥有Loading 属性
        render() {
            const { loading, ...props } = this.props;
            return loading ? <div>Loading...</div> : <WrappedComponent { ...props as P } />;
        }
    }
}
export default HelloHOC(HelloClass);

// index.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import HelloHOC from './components/HelloHOC';

ReactDOM.render(
    <HelloHOC name="TypeScript" loading={true} />,
    document.querySelectorAll('.app')[0]
);
// 注意:HelloClass组件被包装之后,它的默认属性是传递不到高阶组件的,需要将它属性设置为可选;
import React, { useState, useEffect } from 'react';
import { Button } from 'antd';

interface Greeting {
    name: string;
    firstName: string;
    lastName: string;
}

const HelloHooks = (props: Greeting) => {
    const [count, setCount] = useState(0);
    // useState的实现方式是一个泛型函数,可以给它添加泛型参数来约束状态的类型,使用联合类型
    const [text, setText] = useState<string | null>(null);

    useEffect(() => {
        if (count > 5) {
            setText('休息一下');
        }
    }, [count]);

    return (
        <>
            <p>你点击了 {count} 次 {text}</p>
            <Button onClick={() => {setCount(count + 1)}}>
                Hello {props.name}
            </Button>
        </>
    )
}

HelloHooks.defaultProps = {
    firstName: '',
    lastName: ''
}

export default HelloHooks;

类型检查机制

TypeScript 编译器在做类型检查时,所秉承的原则,以及表现出的一些行为。

作用:辅助开发,提高开发效率。

  • 类型推断
    • 不需要指定变量的类型(函数的返回值类型),TypeScript可以根据某些规则自动的为其推断出类型。
    • 当你清楚知道上下环境,不需要TS的类型推断时,可使用类型断言。
  • 类型兼容性
    • 当一个类型Y可以被赋值给另一个类型X时,我们就可以说类型X兼容类型Y;
    • X兼容Y:X(目标类型)=Y(源类型)
  • 类型保护

附录

1. TypeScript 与 JS 的区别

  • TypeScript 是 JS 的超集,扩展了 JS 的语法,因此现有的 JavaScript 代码可与 TypeScript 一起工作无需任何修改,TypeScript 通过类型注解提供编译时的静态类型检查。

  • TypeScript 可处理已有的 JS 代码,并只对其中的 TypeScript 代码进行编译。

2. TypeScript 与面向对象

面向对象是一种对现实世界理解和抽象的方法。

TypeScript 是一种面向对象的编程语言。

面向对象主要有两个概念:对象和类。

  • 对象:对象是类的一个实例,有状态和行为。例如,一条狗是一个对象,它的状态有:颜色、名字、品种;行为有:摇尾巴、叫、吃等。
  • :类是一个模板,它描述一类对象的行为和状态。
  • 方法:方法是类的操作的实现步骤。

参考文章

  1. 优雅的在 react 中使用 TypeScript
  2. React + TypeScript实践
  3. 总结TypeScript在项目开发中的应用实践体会