一、类型推断
隐式类型推断:没有通过类型注解去明确一个变量的类型,那么 typescript 会根据变量的使用情况,来推断这个变量的类型,
- 定义一个变量赋值一个 string 类型的值,typescript会推断这个变量为 string 类型
若将number赋值给name, typescript 就会报错提示
- 如果 typescript 无法推断变量的类型,就会把它当成 any 类型
- 对于函数的参数和返回值, TS 也可以进行类型的推断,只不过参数需要关闭严格;
// num1 和 num2 在严格模式下,会提醒你设置类型注解
// 可以修改 tsconfig.json,"strict": false,
function info(age, name) {
//由于是 any,可以赋值字符串
name = 'Mr.Lee'
return name + age
}
console.log(info(1, 20))
// 严格模式:"strict": true,
function info(age: number, name: string) {
return name + age
}
// 会自行推断成 string 类型的返回值
虽然 Typescript 中支持隐式类型推断,而且这种隐式类型推断可以帮我们简化一部分代码,但是仍然建议为每个变量添加明确的类型,方便以后我们能更好理解我们的代码。
二、类型断言
有时候在某些特殊情况下,typescript 无法推断变量的具体类型,但是我们作为开发者,根据代码的具体情况,是可以明确知道这个变量的类型的,那么我们可以通过类型断言告诉 typescript 这个变量的类型。
const nums = [1,2,3,4,5];
/**
4. 知道nums数组,一定有大于0的数字
5. 但是typeScript不知道
6. typeScript所推断的返回值res,是一个number或者undefined,typeScript会以为有可能找不到
7. 就不能把这个返回值直接当成数字来用
*/
const res = nums.find(i => i > 2);
const square = res * res;
- 使用 as 关键词(推荐)
const nums = [1,2,3,4,5];
/**
* 知道nums数组,一定有大于0的数字
* 但是typeScript不知道
* typeScript所推断的返回值res,是一个number或者undefined,typeScript会以为有可能找不到
* 就不能把这个返回值直接当成数字来用
*/
const res = nums.find(i => i > 2);
const number1 = res as number;
const square = number1 * number1;
- 在变量的前面使用 <类型> 的方式断言
注: 这种方式需要注意的是不能在 jsx 中使用,<> 会跟 jsx 中的标签产生语法上的冲突。
const nums = [1,2,3,4,5];
/**
* 知道nums数组,一定有大于0的数字
* 但是typeScript不知道
* typeScript所推断的返回值res,是一个number或者undefined,typeScript会以为有可能找不到
* 就不能把这个返回值直接当成数字来用
*/
const res = nums.find(i => i > 2);
const number1 = <number>res;
const square = number1 * number1;
注意:类型断言并不是类型转换,因为类型转换是代码在运行时的概念,而类型断言它只是在编译过程当中的概念,当代码编译过后这个断言就不会存在了,所以它跟类型转换是有本质的差异的。
三、模块
typeScript和ECMAScript 2015一样,任何包含顶级 inport 或者export 的文件都被当成一个模块
相反,如果一个文件不带有顶级的import 或者export声明,那么它的内容被视为全局可见
- 导出和导入模块
使用 export 关键字进行导出,使用 import 关键字进行导入
导出
export const widthNum = 20;
export function area(heightNum: number): number {
return widthNum * heightNum;
}
导入
// 导入模块
import { widthNum, area} from "./text";
let num1 = 5;
let circumference = area(radius);
console.log(circumference(num1));
- 默认导出和默认导入
默认导出:是一种更方便的导出方式,而且在导入时可以使用任何名称。
使用 export default 导出
const widthNum = 20;
function area(heightNum: number): number {
return widthNum * heightNum;
}
export defaul area;
默认导入:import 关键字导入了默认导出的模块。注意,我们可以将其重命名为任何我们喜欢的名字
import area from "./text";
let num1 = 4;
let squareArea = area(num1);
console.log(squareArea);
- 模块别名
有时文件路径较长,为了方便,可以使用模块别名简化导入。
使用 @ 作为别名,代替了长路径 ./text,这样可以使导入语句更加清晰和简洁。
import { widthNum, area } from "@/circleModule";
let num1 = 5;
let areaNum = area(num1);
console.log(areaNum, widthNum);
- 模块之间的依赖关系
模块之间可能存在依赖关系,确保这些依赖关系被正确处理是很重要的。
// text1.ts
export const message = "迪西";
// text2.ts
import { message } from "./file1"; // 确保文件之间的相对路径或模块别名正确的
console.log(message);
- 模块循环依赖
循环依赖是指两个或多个模块彼此直接或间接地依赖对方,这可能导致编译错误或运行时错误。
// text1.ts
import { num2 } from "./text2";
export const num1 = "1111";
console.log(num2); // 可能会出现 undefined,取决于编译器如何处理循环依赖
// text2.ts
import { num1 } from "./text1;
export const num2 = "2222";
console.log(num1 ); // 可能会出现 undefined,取决于编译器如何处理循环依赖
要解决循环依赖问题,可以考虑重构代码结构或使用延迟加载等方法。
四、命名空间
命名空间一个最明确的目的就是解决重名问题。
命名空间是一种将代码组织为模块化结构的方式。但自从有了 ES 模块后,已经不推荐使用命名空间了。
命名空间定义了标识符的可见范围,一个标识符可在多个名字空间中定义,它在不同名字空间中的含义是互不相干的。
在一个新的名字空间中可定义任何标识符,它们不会与任何已有的标识符发生冲突,因为已有的定义都处于其他名字空间中。
TypeScript中命名空间使用 namespace来定义
语法格式:
export namespace SomeNameSpaceName {
export interface ISomeInterfaceName { }
export class SomeClassName { }
}
// 通过export导出,
- 命名空间嵌套
命名空间支持嵌套,如果要在外部使用 nestedName ,必须在它前面加上 export 命令。但在实际应用中,推荐使用模块代替命名空间,因为模块提供了更好的封装和组织代码的方式。
export namespace SomeNameSpaceName {
export const name= '迪西';
export namespace nestedName {
export const num1 = 10;
}
}
- 单文件命名空间的使用
同一个文件内的另外一个命名空间调用语法格式为:
SomeNameSpaceName.name;
namespace SomeNameSpaceName {
export const name= '迪西';
}
console.log(SomeNameSpaceName.name);
- 在其他文件中的使用
在某个文件使用命名空间中的内容只需要在使用外部命名空间的地方使用/// 来引入,注意三斜线///开头,然后在 path 属性指定相对于当前文件,这个命名空间文件的路径。
// index.ts
namespace SomeNameSpaceName {
export const name= '迪西';
export const personInfo = (text) => {
return name + text;
}
}
console.log(SomeNameSpaceName.name);
/// <reference path='index.ts' /> // 语法结构不能错的,否则编译不通过
let info = SomeNameSpaceName.personInfo(12);
const reg = SomeNameSpaceName.name;
console.log(info )
console.log(reg )
- 别名
别名是一种简化命名空间的操作方式,其语法是使用import关键字,使用方式如下:
import name = SomeNameSpaceName .name
namespace SomeNameSpaceName {
export const name= '迪西';
export const personInfo = (text) => {
return name + text;
}
}
// 通过 import 的语法将导出的子命名空间重新命名为 polygons
import info = SomeNameSpaceName.personInfo
let infoStr = info(12);
使用import关键字来定义命名空间中某个输出元素的别名,可以减少我们深层次获取属性的成本。
- 文件拆分
执行命令
tsc --outfile index.js a.ts b.ts index.ts
outFile参数是用于将输出文件合并为一个文件
通过outfile命令将a.ts、b.ts、index.ts就可以统一转码打包到一个index.js文件中,然后index.html就只需要引入一个index.js文件就可以了。
//a.ts
namespace MyMath{
export const PI = 3.14;
}
//b.ts
namespace MyMath{
export function sumValue(num1:number, num2:number){
return num1 + num2;
}
}
//index.ts--在这里使用命名空间的内容
console.log(MyMath.PI);
console.log(MyMath.sumValue(2,3));
//index.html引入各个ts转换的js脚本
<script src="./a.js"></script>
<script src="./b.js"></script>
<script src="./index.js"></script>
- 小结
- 命名空间可以更好的组织 TypeScript 代码,通过 namespace 关键字定义
- export 关键字导出声明。import 关键字定义别名
- 多个文件有相同的命名空间最终会合并为一个命名空间
- 通过三斜线指令 /// 和 files 定义文件编译顺序
命名空间和模块的区别
- 命名空间是位于全局命名空间下的一个普通的带有名字的 JavaScript 对象,使用起来十分容易。但就像其它的全局命名空间污染一样,它很难去识别组件之间的依赖关系,尤其是在大型的应用中
- 像命名空间一样,模块可以包含代码和声明。 不同的是模块可以声明它的依赖
- 在正常的TS项目开发过程中并不建议用命名空间,但通常在通过 d.ts 文件标记 js 库类型的时候使用命名空间,主要作用是给编译器编写代码的时候参考使用