# TypeScript

版本 ^3.8.3

# 1. 开发环境搭建

创建自己的项目文件,并执行一下命令

# 初始化项目(npm初始化项目中共字段比yarn多)
npm init -y

# 下载 typescript 和 tslint
yarn add -D typescript tslint

# 初始化ts(生成tsconfig.json)
tsc --init

# 安装webpack4
yarn add -D webpack webpack-cli webpack-dev-server

# 安装ts解析
yarn add -D ts-loader

#安装插件
yarn add -D clean-webpack-plugin html-webpack-plugin

配置 启动脚本

"dev": "cross-env NODE_ENV=development webpack-dev-server --config ./build/webpack.config.js"

配置 webpack

const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')

module.exports = {
    // 配置入口文件
    entry: './src/index.ts',
    // 输出文件
    output: {
        filename: 'main.js',
    },
    resolve: {
        extensions: ['.ts', '.tsx', '.js'], //配置js ts tsx文件
    },
    module: {
        rules: [
            {
                test: /\.tsx?$/,
                use: 'ts-loader',
                exclude: /node_modules/, //忽略解析node_modules中文件
            },
        ],
    },
    devtool:
        process.env.NODE_ENV === 'production' ? false : 'inline-source-map',
    devServer: {
        contentBase: './dist',
        stats: 'errors-only',
        compress: false,
        host: 'localhost',
        port: 8089,
    },
    plugins: [
        new CleanWebpackPlugin({
            cleanOnceBeforeBuildPatterns: ['./dist'],
        }),
        new HtmlWebpackPlugin({
            template: './src/template/index.html',
        }),
    ],
}

# 2. tsconfig.json 详解

{
  "compileOnSave": true,
  "compilerOptions": {
    "target": "ES2018", // 编译的目标是什么版本的
    "module": "commonjs", // 生成模块代码时指定模块系统
    "moduleResolution": "node", // 指定如何解析模块以进行导入
    "experimentalDecorators": true, // 启用实验性的 ES 装饰器
    "emitDecoratorMetadata": true,
    "inlineSourceMap": true, // 生成目标文件的inline SourceMap,inline SourceMap会包含在生成的js文件中
    "noImplicitThis": true, // 不允许this有隐式的any类型
    "noUnusedLocals": true, // 检查只声明、未使用的局部变量(只提示不报错)
    "stripInternal": true,
    "skipLibCheck": false,
    "pretty": true,
    "declaration": true, // 是否自动创建类型声明文件
    "typeRoots": ["./typings", "./node_modules/@types"],
    "outDir": "dist" // 输出目录
  },
  // 编译器排除文件
  "exclude": ["dist", "node_modules", "test"]
}

# 3. 数据类型

// 布尔值
let bool: boolean = true;

// 数字类型
let num: number = 123;

// 字符串类型
let str: string = "rain";

// 数组类型
let arr1: number[] = [5];
let arr2: Array<number> = [7];
let arr3: (string | number)[] = [1, "a"];

// 元祖类型(数值个数和类型要对应)
let tuple: [string, number, boolean] = ["a", 1, true];

// 枚举类型
enum Roles {
  SUPER_ADMIN = 1,
  ADMIN = 2,
  USER = 3,
}
console.log(Roles.SUPER_ADMIN); // 打印1
console.log(Roles.ADMIN); // 打印2
console.log(Roles.USER); // 打印3
console.log(Roles[2]); // 打印 ADMIN

// any类型:可以是任何类型
let value: any;

// void类型:没有返回值
const consoleText = (text: string): void => {
  console.log("text");
};

// null 和 undefined
let u: undefined = undefined;
let n: null = null;

// never类型 永远不存在的值类型(抛错和死循环)
const errorFunc = (message: string): never => {
  throw new Error(message);
};
const infiniteFunc = (): never => {
  while (true) {}
};

// 对象类型
let obj: object = {
  name: "json",
};

// 类型断言
// 两种形式  (<string>target) (target as string)
// tips: .tsx 中只能使用 (target as string)这种形式的类型断言
const getLength = (target: string | number): number => {
  if ((<string>target).length || (target as string).length === 0) {
    return (<string>target).length;
  } else {
    return target.toString().length;
  }
};

# unknown 和 any 的区别

可以将任何东西赋给 unknown 类型,但在进行类型检查或类型断言之前,不能对 unknown 进行操作。

可以把任何东西分配给 any 类型,也可以对 any 类型进行任何操作

function func(callback: unknown) {
  // 可以将任何东西赋给 `unknown` 类型,
  // 但在进行类型检查或类型断言之前,不能对 `unknown` 进行操作
  if (typeof callback === "function") {
    callback();
  }
}

func(1);

# 4. 接口

# 4.1 基本用法:

接口定义对象类型

interface NameInfo {
  firstName: string;
  lastName: string;
}

const getFullName = ({ firstName, lastName }: NameInfo): string => {
  return `${firstName} ${lastName}`;
};

console.log(getFullName({ firstName: "rain", lastName: "luck" }));

# 4.2 可选属性:

color?: string

// 设置可选属性 使用?进行标识(color?: string)
interface Vegetables {
  color?: string;
  type: string;
}
const getVegetables = ({ color, type }: Vegetables) => {
  return `A ${color ? color + "" : ""} ${type}`;
};
console.log(
  getVegetables({
    // color: 'blue',
    type: "apple",
  })
);

# 4.3 多余属性检查

方法一:类型断言

interface Vegetables {
  color?: string;
  type: string;
}
const getVegetables = ({ color, type }: Vegetables) => {
  return `A ${color ? color + "" : ""} ${type}`;
};
getVegetables({
  // color: 'blue',
  type: "apple",
  size: 20,
} as Vegetables);

方法二:索引签名

interface Vegetables {
  color?: string;
  type: string;
  [props: string]: any;
}
const getVegetables = ({ color, type }: Vegetables) => {
  return `A ${color ? color + "" : ""} ${type}`;
};
getVegetables({
  // color: 'blue',
  type: "apple",
  size: 20,
  time: "2020",
});

方法三:利用类型兼容性

interface Vegetables {
  color?: string;
  type: string;
}

const getVegetables = ({ color, type }: Vegetables) => {
  return `A ${color ? color + "" : ""} ${type}`;
};

const vegetableInfo = {
  color: "blue",
  type: "apple",
  size: 20,
};
console.log(getVegetables(vegetableInfo));

# 4.4 只读属性

readonly type: string,

interface Vegetables {
  color?: string;
  readonly type: string;
}
const vegetableObj: Vegetables = {
  type: "tomato",
};
vegetableInfo.type = "carrot"; //报错!!

# 4.5 接口定义函数结构

interface AddFunc {
  (num1: number, num2: number): number;
}

const add: AddFunc = (n1, n2) => {
  return n1 + n2;
};

# 4.6 定义索引类型

interface RoleDic {
  [id: number]: string;
}
const role1: RoleDic = {
  0: "super_admin",
};

# 4.7 接口的继承

interface Vegetables {
  color: string;
}

interface Tomato extends Vegetables {
  radius: number;
}

inteface Carot extends Vegetables {
  length: number;
}

const tomato: Tomato = { color: "red", radius: 12 };

# 4.8 混合类型接口

interface Counter {
  (): void;
  count: number;
}

const getCounter = (): Counter => {
  const c = () => {
    c.count++;
  };
  c.count = 0;
  return c;
};

const counter: Counter = getCounter();

counter();
console.log(counter.count);
counter();
console.log(counter.count);
counter();
console.log(counter.count);
//1 2 3

# 4.9 为对象动态分配属性

interface LooseObject {
  name: string;
  age?: number;
  [key: string]: any;
}

let obj: Developer = { name: "rain" };
obj.age = 30;
obj.city = "BeiJing";

# 5. 函数

为函数定义类型

//  完整的函数类型
function add(arg1: number, arg2: number): number {
  return arg1 + arg2;
}

//  使用类型别名定义
type Add = (x: number, y: number) => number;
let addFunc: Add;
addFunc = (arg1: number, arg2: number): number => {
  return arg1 + arg2;
};

函数重载(只能使用 function 进行重载)

function handleData(x: string): string[];
function handleData(x: number): number[];
function handleData(x: any): any[] {
  if (typeof x === "string") {
    return x.split("");
  } else {
    return x
      .toString()
      .split("")
      .map((item: any) => Number(item));
  }
}

# 6. 泛型

就像传递参数一样,我们传递了我们想要用于特定函数调用的类型。

img

参考上面的图片,当我们调用 identity<Number>(1)Number 类型就像参数 1 一样,它将在出现 T 的任何位置填充该类型。图中 <T> 内部的 T 被称为类型变量,它是我们希望传递给 identity 函数的类型占位符,同时它被分配给 value 参数用来代替它的类型:此时 T 充当的是类型,而不是特定的 Number 类型。

其中 T 代表 Type,在定义泛型时通常用作第一个类型变量名称。但实际上 T 可以用任何有效名称代替。除了 T 之外,以下是常见泛型变量代表的意思:

  • K(Key):表示对象中的键类型;
  • V(Value):表示对象中的值类型;
  • E(Element):表示元素类型。

其实并不是只能定义一个类型变量,我们可以引入希望定义的任何数量的类型变量。比如我们引入一个新的类型变量 U,用于扩展我们定义的 identity 函数:

function identity<T, U>(value: T, message: U): T {
  console.log(message);
  return value;
}

console.log(identity<Number, string>(68, "Semlinker"));

img

除了为类型变量显式设定值之外,一种更常见的做法是使编译器自动选择这些类型,从而使代码更简洁。我们可以完全省略尖括号,比如:

function identity<T, U>(value: T, message: U): T {
  console.log(message);
  return value;
}

console.log(identity(68, "Semlinker"));

对于上述代码,编译器足够聪明,能够知道我们的参数类型,并将它们赋值给 T 和 U,而不需要开发人员显式指定它们。


参考文章