学习源:up主'技术胖'
TypeScript 是由微软公司在 2012 年正式发布,现在也有 8 年的不断更新和维护了,TypeScript 的成长速度是非常快的,现在已经变成了前端必会的一门技能。TypeScript 其实就是 JavaScript 的超集,也就是说 TypeScript 是建立在 JavaScript 之上的,最后都会转变成 JavaScript。
npm i typescript -g
或
yarn global add typescript
npm i ts-node -g
之后就可以使用ts-node demo.ts
来运行ts文件
TypeScript 的一个最主要特点就是可以定义静态类型,英文是 Static Typing。那到底是什么意思呢?你可以简单的理解“静态类型”为,就是你一旦定义了该类型,就不可以再改变了。
基础静态类型非常简单,只要在声明变量的后边加一个:号,然后加上对应的类型哦。比如下面的代码,就是声明了一个数字类型的变量,叫做count;
这样定义后count这个变量在程序中就永远都是数字类型,不可以改变了。比如我们这时候给count复制一个字符串,它就报错了。
let count: number = 123
let num:number
num = 45
类似这样常用的基础类型还有: null
,undefined
,symbol
,boolean
,void
const animal: {
name: string
age: number
} = {
name: 'dog',
age: 3
}
movies必须是一个数组,数组里的内容必须为string类型
const movies: string[] = ['海上钢琴师', '集结号', '复联']
用类的形式,来定义变量
class Song {}
const ChineseSong: Song = new Song()
返回值类型为string
const fn: () => string = () => {
return '我是字符串'
// return 10 // 报错
}
意思是显示的告诉代码,我们的count变量就是一个数字类型,这就叫做类型注解
let count: number;
count = 123;
let countInference = 123;
这时候我并没有显示的告诉你变量countInference是一个数字类型,但是如果你把鼠标放到变量上时,你会发现 TypeScript 自动把变量注释为了number(数字)类型,也就是说它是有某种推断能力的,通过你的代码 TS 会自动的去尝试分析变量的类型。
工作使用问题(潜规则):
const one = 1;
const two = 2;
const three = one + two;
function getTotal(one, two) {
return one + two;
}
const total = getTotal(1, 2);
这种形式,就需要用到类型注释了,因为这里的one和two会显示为any类型。这时候如果你传字符串,你的业务逻辑就是错误的,所以你必须加一个类型注解,把上面的代码写成下面的样子。
function getTotal(one: number, two: number) {
return one + two;
}
const total = getTotal(1, 2)
这里有的一个问题是,为什么total这个变量不需要加类型注解,因为当one和two两个变量加上注解后,TypeScript 就可以自动通过类型推断,分析出变量的类型。
TypeScript 也可以推断出对象中属性的类型
const man = {
name: "坤哥",
age: 18,
}
写完后你把鼠标放在XiaoJieJie对象上面,就会提示出他里边的属性:
const man: {
name: string;
age: number;
}
这表明 TypeScript 也分析出了对象的属性的类型。
在写 TypeScript 代码的一个重要宗旨就是每个变量,每个对象的属性类型都应该是固定的,如果你推断就让它推断,推断不出来的时候你要进行注释。
上节写过这么一个函数:
function getTotal(one: number, two: number) {
return one + two;
}
const total = getTotal(1, 2)
这个代码其实是有一个小坑的,就是我们并没有定义getTotal的返回值类型,虽然TypeScript可以自己推断出返回值是number类型。 但是如果这时候我们的代码写错了,比如写程了下面这个样子:
function getTotal(one: number, two: number) {
return one + two + "";
}
const total = getTotal(1, 2)
这时候total的值就不是number类型了,但是不会报错
可以直接给total一个类型注解:
const total: number = getTotal(1, 2)
这样写虽然可以让编辑器报错,但是这还不是很高明的算法,因为你没有找到错误的根本,这时错误的根本是getTotal()函数的错误,所以合适的做法是给函数的返回值加上类型注解,代码如下:
function getTotal(one: number, two: number): number {
return one + two;
}
const total = getTotal(1, 2)
有时候函数是没有返回值的,比如现在定义一个sayHello
的函数,这个函数只是简单的terminal
打印,并没有返回值。没有返回值的函数,我们就可以给他一个类型注解void
,代表没有任何返回值。
function sayHello(): void {
console.log("hello world")
}
如果这样定义后,你再加入任何返回值,程序都会报错。
如果一个函数是永远也执行不完的,就可以定义返回值为never,那什么样的函数是永远也执行不完的那?我们先来写一个这样的函数(比如执行的时候,抛出了异常,这时候就无法执行完了)
function errorFunction(): never {
throw new Error()
console.log("Hello World")
}
还有一种是死循环,这样也运行不完,比如下面的代码:
function forNever(): never {
while (true) {}
console.log("Hello mom")
}
当一个函数的参数是对象时,我们如何定义参数对象的属性类型?先写个一般javaScript的写法:
function add({ one, two }) {
return one + two
}
const total = add({ one: 1, two: 2 })
在浏览器中你会看到直接报错了,意思是total有可能会是任何类型,那我们要如何给这样的参数加类型注解呢?正确的写法:
function add({ one, two }: { one: number; two: number }) {
return one + two
}
const total = add({ one: 1, two: 2 })
现在我们可以定义一个最简单的数组类型,比如就是数字类型,那么就可以这么写:
const numberArr = [1, 2, 3]
这时候你把鼠标放在numberArr上面可以看出,这个数组的类型就是 number 类型。这是 TypeScript 通过类型推断自己推断出来的。如果你要写类型注解,可以写成下面的形式,之前有写过:
const numberArr: number[] = [1, 2, 3]
string/boolean/undefined等一般数组类型同理
数组中含有多种类型,只要加个(),然后在里边加上|就可以了:
const arr: (number | string)[] = [1, 'string', 2]
数组包含对象:
const objArr: { name: string; age: number }[] = [
{ name: '张三', age: 45 },
{ name: '李四', age: 19 },
{ name: '坤坤', age: 24 }
]
这种形式看起来比较麻烦,而且如果有同样类型的数组,写代码也比较麻烦,TypeScript 为我们准备了一个概念,叫做类型别名(type alias)。比如刚才的代码,就可以定义一个类型别名,定义别名的时候要以type关键字开始。现在定义一个Lady的别名:
type Person = { name: string; age: number }
const objArr: Person[] = [
{ name: '张三', age: 45 },
{ name: '李四', age: 19 },
{ name: '坤坤', age: 24 }
]
TypeScript 中提供了元组的概念,这个概念是JavaScript中没有的。其实元组在开发中并不常用,一般只在数据源是CVS这种文件的时候,会使用元组。其实你可以把元组看成数组的一个加强,它可以更好的控制或者说规范里边的类型。
我们先来看一个数组和这个数组注解的缺点,比如我们有一个person数组,数组中有姓名、职业和年龄,代码如下:
const person = ['李华', '学生', 20]
这时候把鼠标放到person变量上面,可以看出推断出来的类型。我们就用类型注解的形式给他作一个注解,代码如下:
const person: (string | number)[] = ['李华', '学生', 20]
但是这并不能很好的限制,比如我们把代码改成下面的样子,TypeScript依然不会报错:
const person: (string | number)[] = ['李华', 20, '学生']
我们只是简单的把数组中的位置调换了一下,但是TypeScript并不能发现问题,这时候我们需要一个更强大的类型,来解决这个问题,这就是元组。
元组和数组类似,但是类型注解时会不一样:
const person: [string, string, number] = ['李华', '学生', 20]
元组的使用
工作中不经常使用元组,因为如果要使用元组,完全可以使用对象的形式来代替,但是如果你维护老系统,你会发现有一种数据源时CSV,这种文件提供的就是用逗号隔开的,如果要严谨的编程就需要用到元组了。例如我们有这样一组由CSV提供的(注意这里只是模拟数据):
'李华', '学生', 20
'陈晨', '老师', 27
'张瓜呱', '砖家', 50
如果数据源得到的数据时这样的,你就可以使用元组了:
const persons: [string, string, number][] = [
['李华', '学生', 20],
['陈晨', '老师', 27],
['张瓜呱', '砖家', 50]
]
基本使用如下:
interface Person {
name: string
age: number
gender: string
height?: number // ?表示该属性可有可无
}
const jack: Person = {
name: 'jack',
age: 10,
gender: 'man'
}
const lucy: Person = {
name: 'jack',
age: 10,
gender: 'woman',
height: 166
}
**接口和类型别名的区别:**这两个语法和用处好像一样,确实用起来基本一样,但是也有少许的不同。类型别名可以直接给类型,比如string,而接口必须代表对象。
比如我们的类型别名可以写出下面的代码:
type user = string
但是接口就不能这样写,它必须代表的是一个对象,也就是说,你初始化user1的时候,必须写出下面的形式.
interface user2 {
name: string
age: number
}
[propName: string]: any
表示还可以增添任意类型属性:interface Person {
name: string
age: number
gender: string
height?: number
[propName: string]: any // 表示还可以增添任意类型属性
}
const jack: Person = {
name: 'jack',
age: 10,
gender: 'man',
weight:70// 新增添的其他属性
}
sayHello: string
表示必须要有一个返回值为string的sayHello方法:interface Person {
name: string
age: number
gender: string
height?: number
[propName: string]: any
sayHello(): string // 方法返回值为string
}
const jack: Person = {
name: 'jack',
age: 10,
gender: 'man',
weight: 70,
sayHello() {
return '你好'// 返回string
}
}
假如方法没有返回值,则为void
implements
class Man implements Person {
name = '废物'
age = 48
gender = '女'
sayHello() {
return '你好啊'
}
}
const tom = new Man()
extends
interface Person {
name: string
age: number
gender: string
height?: number
[propName: string]: any
sayHello(): string // 函数返回值为string
}
interface Teacher extends Person {
subject: string// teacher特有的属性
}
const cuiHua: Teacher = {
name: '翠花',
age: 41,
gender: '女',
subject: '语文',// teacher特有的属性
sayHello() {
return '你好啊'
}
}
例子:
class Student {
private name: string = '坤坤'// 私有
protected age: number = 11// 保护
public sayHi() {// 公共
console.log(`hello,我是${this.name},今年${this.age}岁了`)
}
}
class GradeOne extends Student {
public practiceTime: number = this.age - 8.5// 通过继承可以访问保护的属性
public fn() {
console.log(this.name)// 报错,不可以访问Student中的私有属性
}
}
const ikun = new Student()
ikun.sayHi() // 可以访问
console.log(ikun.name) // 报错
console.log(ikun.age) // 报错
const ikun2 = new GradeOne()
ikun.sayHi() // 可以访问
console.log(ikun2.name) // 报错
console.log(ikun2.age) // 报错
console.log(ikun2.practiceTime) // 可以访问
构造函数就是在类被初始化的时候,自动执行的一个方法。
最常规和容易理解的写法:
class Animal {
public name: string
constructor(name: string) {
this.name = name
}
}
const dog = new Animal('狗')
下面写法就相当于你定义了一个name,然后在构造函数里进行了赋值,这是一种简化的语法,在工作中我们使用这种语法的时候会更多一些:
class Animal {
constructor(public name: string) {}
}
const dog = new Animal('狗')
类继承中的构造器写法:
在子类中使用构造函数需要用super()调用父类的构造函数:
class Animal {
constructor(public name: string) {}
}
class Bird extends Animal {
constructor(public name: string, public age: number) {
super(name)
}
}
const parrot = new Bird('鹦鹉', 2)// { name: '鹦鹉', age: 2 }
在子类里写构造函数时,必须用super()调用父类的构造函数,如果需要传值,也必须进行传值操作。就是是父类没有构造函数,子类也要使用super()进行调用,否则就会报错:
class Animal {}
class Bird extends Animal {
constructor(public age: number) {
super()
}
}
const parrot = new Bird(2)// { age: 2 }
只读属性的属性值不可修改,否则报错
class Animal {
constructor(public readonly name: string) {}
}
const dog = new Animal('狗')
console.log(dog.name)
dog.name = '猫'// 报错,无法分配到 "name" ,因为它是只读属性
abstract class Animal {
abstract name: string // 抽象一个name属性,但是name属性不允许有值,也不允许被 constructor 赋值
abstract eat(): void // 抽象一个方法,方法不允许有内容,只允许标注返回值类型
}
class Dog extends Animal {
// 方法属性具体化,只能这样赋值,不允许用constructor
name: string = '狗'
eat(): void {
console.log('狗吃骨头')
}
constructor(public gender: string) {
super() // 此处super中也不允许有父类的抽象属性
}
}
const dog = new Dog('公')
dog.eat()// 狗吃骨头
console.log(dog)// { gender: '公', name: '狗' }
在需要编译的ts文件夹打开终端,输入tsc -init
生成tsconfig.json文件
在tsconfig.json的compilerOptions
上方加上如下配置项,表示要编译包含的文件:
"include": ["02.ts"]
或者使用exclude表示不包含的文件:
"exclude": ["02.ts"]
完成后在终端输入tsc
就可以根据配置项编译生成js文件
tsc fileName 是没办法遵循tsconfig.js文件的,但是ts-node遵循
compilerOptions
removeComments
为true则ts编译成js时会将所有注释删除
strict
为true则启用所有严格的类型检查选项,它下方的所有配置项都将开启为true;想要更改下方的配置项需要置为false
noImplicitAny
为true则为隐含的’any’类型的表达式和声明启用错误报告strictNullChecks
为true则不允许null值noUnusedLocals
为true则局部变量定义未使用时报错rootDir
指定源文件中的根文件夹
outDir
为所有编译的文件指定一个输出文件夹
sourceMap
编译后的文件和源文件的映射关系,编译后的代码报错后可以通过错误信息定位到源文件对应的行数
更多编译配置项详解:https://www.tslang.cn/docs/handbook/compiler-options.html
所谓联合类型,可以认为一个变量可能有两种或两种以上的类型。
比如下面这个参数person的类型可能为Man或Woman
interface Man {
isMan: boolean
smoke: () => {}
}
interface Woman {
isMan: boolean
makeup: () => {}
}
const judge = (person: Man | Woman) => {}
但这时候问题来了,如果我直接写一个这样的方法,就会报错,因为judge不能准确的判断联合类型具体的实例是什么:
const judge = (person: Man | Woman) => {
person.smoke()
}
这时候就需要再引出一个概念叫做类型保护,类型保护有很多种方法,这里讲几个最常使用的。
类型断言就是通过断言的方式确定传递过来的准确值
interface Man {
isMan: boolean
smoke: () => {}
}
interface Woman {
isMan: boolean
makeup: () => {}
}
const judge = (person: Man | Woman) => {
if(person.isMan){
(person as Man).smoke()// 断言
}else{
(person as Woman).makeup()// 断言
}
}
我们还经常使用in语法来作类型保护
interface Man {
isMan: boolean
smoke: () => {}
}
interface Woman {
isMan: boolean
makeup: () => {}
}
const judge = (person: Man | Woman) => {
if ('smoke' in person) {
person.smoke()
} else {
person.makeup()
}
}
先来写一个新的add方法,方法接收两个参数,这两个参数可以是数字number也可以是字符串string,如果我们不做任何的类型保护,只是相加,这时候就会报错。代码如下:
function add(first: string | number, second: string | number) {
return first + second
}
可以直接使用typeof来进行解决:
function add(first: string | number, second: string | number) {
if (typeof first === "string" || typeof second === "string") {
return `${first}${second}`
}
return first + second
}
class NumberObj {
count: number
}
然后我们再写一个addObj的方法,这时候传递过来的参数,可以是任意的object,也可以是NumberObj的实例,然后我们返回相加值,不进行类型保护,这段代码一定是错误的:
function addObj(first: object | NumberObj, second: object | NumberObj) {
return first.count + second.count
}
直接使用instanceof语法进行判断一下,就可以解决问题:
function addObj(first: object | NumberObj, second: object | NumberObj) {
if (first instanceof NumberObj && second instanceof NumberObj) {
return first.count + second.count
}
return 0
}
枚举的作用是列举类型中包含的各个值,一般用它来管理多个相同系列的常量(即不能被修改的变量),用于状态的判断。
// 枚举类型中的每项属性依次对应着数字0、1、2……
enum Status {
dog,
cat,
lion
}
const getAnimal = (status: number) => {
if (status === Status.dog) return 'dog'
if (status === Status.cat) return 'cat'
if (status === Status.lion) return 'lion'
}
console.log(getAnimal(0)) // dog
console.log(getAnimal(1)) // cat
console.log(getAnimal(2)) // lion
上述代码中,第一项属性对应的下标从0开始,假如想要从1开始,只需要做如下修改:
// 枚举类型中的每项属性依次对应着数字1、2、3……
enum Status {
dog = 1,
cat,
lion
}
const getAnimal = (status: number) => {
if (status === Status.dog) return 'dog'
if (status === Status.cat) return 'cat'
if (status === Status.lion) return 'lion'
}
console.log(getAnimal(0)) // undefined
console.log(getAnimal(1)) // dog
console.log(getAnimal(2)) // cat
console.log(getAnimal(3)) // lion
我们这里能打印出枚举的值(也有叫下标的),那如果我们知道下标后,也可以通过反查的方法,得到枚举的值:
// 枚举下标
console.log(Status.dog) // 1
// 枚举反查
console.log(Status[1]) // dog
下面有一个简单的join方法,方法接受两个参数first和second,参数有可能是字符串类型,也有可能是数字类型。方法里为了保证都可以使用,所以我们只作了字符串的基本拼接。
const join = (first: string | number, second: string | number) => {
return `${first}${second}`
}
console.log(join('baidu', '.com'))
现在有这样一个需求,就是first参数如果传的是字符串类型,要求second也传字符串类型.同理,如果是number类型,就都是number类型。
那现在所学的知识就完成不了啦,所以需要学习泛型来解决这个问题。
泛型的定义使用<>(尖角号)进行定义的,比如现在给join方法一个泛型,名字就叫做T(起这个名字的意思,就是你可以随便起一个名字,但工作中要进行语义化),后边的参数,这时候他也使用刚定义的泛型名称。然后在正式调用这个方法时,就需要具体指明泛型的类型.
const join = <T>(first: T, second: T) => {
return `${first}${second}`
}
console.log(join<string>('baidu', '.com')) // 两个参数类型必须同为string
console.log(join<number>(1, 2)) // 两个参数类型必须同为number
// 数组,写法一
const arrFn = <T>(arr: T[]) => {
return arr
}
console.log(arrFn<number>([0, 1, 2, 3, 4]))// 数组中每一项必须为number
// 数组,写法二
const arrFn1 = <T>(arr: Array<T>) => {
return arr
}
console.log(arrFn1<number>([0, 1, 2, 3, 4]))// 数组中每一项必须为number
// 多个泛型定义
const join1 = <T, P>(first: T, second: P) => {
return `${first}${second}`
}
console.log(join1<string, number>('你真', 6)) // 第一个参数为string,第二个为number
泛型也是支持类型推断的,比如下面的代码并没有报错,这就是类型推断的功劳。
const join1 = <T, P>(first: T, second: P) => {
return `${first}${second}`
}
console.log(join1<string, number>('你真', 6)) // 第一个参数为string,第二个为number
console.log(join1('祝', 999)) // 类型推断,第一个参数为string,第二个为number
但个人不建议大量使用类型推断,这会让你的代码易读和健壮性都会下降,所以这个知识点,大家做一个了解就可以了。
一个类的基本例子:
class People {
constructor(private lady: string[]) {}
getName(index: number): string {
return this.lady[index]
}
}
const ladies = new People(['Linda', 'Anna', 'luna'])
console.log(ladies.getName(1))
使用泛型改造上述例子:
class People<T> {
constructor(private lady: T[]) {}
getName(index: number): T {
return this.lady[index]
}
}
const ladies = new People<string>(['Linda', 'Anna', 'luna'])
console.log(ladies.getName(1))
定义一个interface接口Lady,并让泛型T继承Lady:
// 泛型中的继承
interface Lady {
name: string
}
class People<T extends Lady> {
constructor(private lady: T[]) {}
getName(index: number): string {
return this.lady[index].name
}
}
const ladies = new People([{ name: 'Linda' }, { name: 'Anna' }, { name: 'luna' }])// 参数必须是数组包含对象形式
console.log(ladies.getName(1))
// 泛型的约束,泛型只能为string或number
class People<T extends string | number> {
constructor(private lady: T[]) {}
getName(index: number): T {
return this.lady[index]
}
}
const ladies = new People<string>(['Linda', 'Anna', 'luna'])// 正常
const ladies1 = new People<boolean>([true, true, false])// 报错:类型“boolean”不满足约束“string | number”
新建一个项目TSWeb,终端输入:
npm init -y
初始化package,-y表示使用默认配置tsc -init
初始化ts配置文件创建基本目录:
修改tsconfig.json:
"rootDir": "./src",
"outDir": "./build",
index.html中代码如下:
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="./build/pages.js">script>
<title>Documenttitle>
head>
<body>
body>
html>
在pages.ts中书写测试用例如下:
class Header {
constructor() {
const elem = document.createElement('div')
elem.innerText = 'This is Header'
document.body.appendChild(elem)
}
}
class Content {
constructor() {
const elem = document.createElement('div')
elem.innerText = 'This is Content'
document.body.appendChild(elem)
}
}
class Footer {
constructor() {
const elem = document.createElement('div')
elem.innerText = 'This is Footer'
document.body.appendChild(elem)
}
}
class Page {
constructor() {
new Header()
new Content()
new Footer()
}
}
在终端输入tsc
,随后在build文件夹下便生成了pages.js
可以终端输入tsc -w
来监视ts文件的变化,有变化则重新编译
在index.html的body标签中:
<body>
<script> new Page()script>
body>
这时候再到浏览器进行预览,就可以看到对应的页面被展现出来了。看起来没有什么问题,但是有经验的程序员就会发现,这样写全部都是全局变量(通过查看./build/page.js文件可以看出全部都是var声明的变量)。过多的全局变量会让我们代码变的不可维护。
其实理想的是,只要有Page这个全局变量就足够了,剩下的可以模块化封装起来,不暴露到全局。
命名空间这个语法,很类似编程中常说的模块化思想,比如webpack打包时,每个模块有自己的环境,不会污染其他模块,不会有全局变量产生。命名空间就跟这个很类似,注意这里是类似,而不是相同。
命名空间声明的关键词是namespace 比如声明一个namespace Home,需要暴露出去的类,可以使用export关键词,这样只有暴漏出去的类是全局的,其他的不会再生成全局污染了。修改后的代码如下:
namespace Home {
class Header {
constructor() {
const elem = document.createElement("div");
elem.innerText = "This is Header";
document.body.appendChild(elem);
}
}
class Content {
constructor() {
const elem = document.createElement("div");
elem.innerText = "This is Content";
document.body.appendChild(elem);
}
}
class Footer {
constructor() {
const elem = document.createElement("div");
elem.innerText = "This is Footer";
document.body.appendChild(elem);
}
}
export class Page {
constructor() {
new Header();
new Content();
new Footer();
}
}
}
TS 代码写完后,再到index.html文件中进行修改,用命名空间的形式进行调用,就可以正常了。
现在再到浏览器中进行查看,可以看到现在就只有Home.Page是在控制台可以得到的,其他的Home.Header…这些都是得不到的,说明只有Home.Page是全局的,其他的都是模块化私有的。
这就是 TypeScript 给我们提供的类似模块化开发的语法,它的好处就是让全局变量减少了很多,实现了基本的封装,减少了全局变量的污染。
在src目录下新建一个文件components.ts,编写代码如下:
namespace Components {
export class Header {
constructor() {
const elem = document.createElement("div");
elem.innerText = "This is Header";
document.body.appendChild(elem);
}
}
export class Content {
constructor() {
const elem = document.createElement("div");
elem.innerText = "This is Content";
document.body.appendChild(elem);
}
}
export class Footer {
constructor() {
const elem = document.createElement("div");
elem.innerText = "This is Footer";
document.body.appendChild(elem);
}
}
}
这里需要注意的是,我每个类(class)都使用了export导出,导出后就可以在page.ts中使用这些组件了。比如这样使用-代码如下。
namespace Home {
export class Page {
constructor() {
new Components.Header();
new Components.Content();
new Components.Footer();
}
}
}
这时候你可以使用tsc进行重新编译,但在预览时,你会发现还是会报错,找不到Components,想解决这个问题,我们必须要在index.html里进行引入components.js文件。
<script src="./build/page.js">script>
<script src="./build/components.js">script>
这样才可以正常的出现效果。但这样引入太麻烦了,可不可以像webpack一样,只生成一个文件那?那答案是肯定的。
直接打开tsconfig.json文件,然后找到outFile配置项,这个就是用来生成一个文件的设置,但是如果设置了它,就不再支持"module":“commonjs"设置了,我们需要把它改成"module”:“amd”,然后在去掉对应的outFile注释,设置成下面的样子。
{
"outFile": "./build/page.js"
}
配置好后,删除掉build下的js文件,然后用tsc进行再次编译。
然后删掉index.html文件中的component.js,在浏览器里还是可以正常运行的。
也就是说在命名空间里,再写一个命名空间,比如在Components.ts文件下修改代码如下。
namespace Components {
export namespace SubComponents {
export class Test {}
}
//something ...
}
写完后在控制台再次编辑tsc,然后你在浏览器中也是可以查到这个命名空间的Components.SubComponents.Test(需要刷新页面后才会显示)。