Skip to content

TS 初体验与TS 语法(Day11 1/3) ​

本页对应 React + TypeScript + 医通项目,共 6 段视频,约 59 分钟。

学习目标 ​

  • 能够为函数参数和返回值添加类型注解,并使用声明式、字面量完整写法和字面量简写形式定义函数。
  • 能够定义并使用可选参数、默认参数和剩余参数,并说明可选参数与默认参数不能同时出现在同一参数上的原因。
  • 能够声明类属性类型、编写构造函数和方法类型,并通过 extends 实现类继承及调用 super。
  • 能够使用 public、protected、private 修饰类成员,并准确判断其在类内部、子类和类外部的可访问性。
  • 能够使用接口限定对象结构,定义可选属性、只读属性和方法,并通过 implements 让类实现一个或多个接口。
  • 能够定义接口继承关系,并使实现该接口的类补齐所有继承来的属性和方法;能够配置 Webpack 打包 TS 文件并验证运行结果。

知识点 ​

先理解“TS 初体验”要解决什么 ​

为什么需要: TypeScript 不能像 JavaScript 那样直接被 Node.js 执行。我们写的 .ts 文件里有类型注解,比如 let str: string、function add(a: number, b: number): number,这些内容需要先由 TypeScript 编译器 tsc 转成 .js,再由 Node.js 运行。
怎么想: 初体验的流程可以固定成四步:初始化配置 → 写一个 .ts 文件 → 用 tsc 编译 → 用 node 运行编译后的 .js。开发时再加 tsc -w 监听变化。
怎么做: 先创建 tsconfig.json,再写 hello.ts,然后编译、运行。
易错点: 不要把 node hello.ts 当成运行 TypeScript 的常规方式;本节流程里 Node.js 执行的是 hello.js。

初始化配置文件:让 tsc 有统一规则 ​

为什么需要: 直接运行 tsc 时,编译器需要知道按什么规则编译、输出到哪里、是否开启严格检查等。tsconfig.json 就是 TypeScript 项目的配置文件。
怎么想: 先让项目有一个“配置中心”,后续命令和编辑器提示都围绕它工作。
怎么做: 在项目目录下执行:

sh
tsc --init

执行后会生成 tsconfig.json。本节笔记只要求完成初始化,没有展开具体配置项,所以先保留默认生成结果即可。
易错点: tsc --init 要在项目根目录执行,否则配置文件可能生成到别处;后续编译和编辑器提示可能对不上。转写稿后面提到过“项目启动配置”和“编辑器识别配置”的差异,调试时要注意命令行与编辑器使用的是不是同一份配置。

创建 hello.ts:写一个能体现类型检查的文件 ​

为什么需要: 只看配置文件没有体感,需要一个最小文件同时包含变量类型、函数参数类型和返回值类型。
怎么想: 代码不要复杂,能看出“写了类型”和“编译后仍是 JS”即可。
怎么做: 创建 hello.ts,保留本节笔记代码:

javascript
let str: string = 'hello world';
console.log(str);

function add(a: number, b:number):number{
  return a + b;
}
console.log(add(1, 100));

这里 str 被限定为 string,add 的两个参数和返回值都被限定为 number。
易错点: 类型注解是给 TypeScript 检查用的,不是运行时逻辑;不要因为 .ts 里写了 : string 就以为 Node.js 能直接识别它。转写稿中多次出现“类型推断”“严格模式”等说法,说明类型检查会受配置影响;初体验阶段先保证代码能编译通过。

用 tsc hello.ts 编译:把 TS 变成 JS ​

为什么需要: Node.js 执行的是 JavaScript,所以要先编译。
怎么想: 编译命令针对单个文件时,直接指定 .ts 后缀;生成结果通常是同目录下的 .js 文件。
怎么做: 在命令行运行,注意后缀是 ts:

sh
tsc hello.ts

执行后得到 hello.js。
易错点: 命令里写的是 hello.ts,不是 hello.js;如果修改了 hello.ts 却忘记重新编译,后面运行的还是旧 hello.js。另外,如果命令行报类型错误,应先按提示修正,再运行生成的 JS,避免把旧结果当成新结果。

用 node hello.js 运行:真正执行的是 JS ​

为什么需要: 编译只是生成 JS,真正输出结果要靠 Node.js。
怎么想: 编译和运行分开:tsc 负责转换,node 负责执行。
怎么做: 运行,注意后缀是 js:

shell
node hello.js

正常情况下会输出:

text
hello world
101

易错点: 不要写成 node hello.ts 来替代本节流程;本节笔记明确区分了 tsc hello.ts 和 node hello.js。如果输出不对,先检查是不是运行的旧 JS 文件。

用 tsc -w 开启自动编译 ​

为什么需要: 开发时频繁改 .ts,如果每次都手动执行 tsc hello.ts 很麻烦。
怎么想: 让编译器监听文件变化,保存后自动生成 JS。
怎么做: 使用笔记中的命令:

sh
tsc -w

开启后修改 hello.ts,编译器会自动重新编译;此时再运行 node hello.js 查看结果。
易错点: -w 是监听模式,不会自动帮你执行 Node.js;它只负责编译。运行结果仍要用 node hello.js 查看。转写稿里出现过“改配置后要重新启动项目,错误才消失”的调试过程,初体验时也要注意:改完配置或开启监听后,确认编译输出确实更新了。

小结:初体验的固定闭环 ​

本节笔记的核心不是函数、类或接口,而是建立一条最小闭环:tsc --init 建配置 → 写 hello.ts → tsc hello.ts 编译 → node hello.js 运行 → 开发时用 tsc -w 自动编译。把这条链路走通,后面再学类型推断、严格模式、函数参数、类与接口时,才知道代码是怎样从 TS 变成可执行 JS 的。

函数:先声明参数和返回值 ​

写 TS 函数时,第一反应不是“会不会推断”,而是“参数和返回值要不要约束”。因为函数是调用最频繁的代码单元,参数传错、返回值用错,后期最难查。转写稿里反复说的“死锥”就是 string,“安迪范”就是 undefined。

声明式写法最直观:参数后面写类型,括号后写返回值类型。返回值必须和声明一致,否则立刻报错。

ts
function add(a: number, b: number): number {
  return a + b;
}

字面量写法分完整写法和简写。完整写法要先声明变量类型,再赋一个函数值,写起来长;简写利用类型推断,给变量赋函数值时,TS 自动推出参数和返回值类型。

ts
let f1: (a: number, b: number) => number = function (a, b) {
  return a + b;
};

const f2 = (a: number, b: number): number => a + b;

易错点:完整写法中“变量类型”和“赋值的函数类型”必须一致;简写不是没类型,而是靠类型推断。转写稿说完整写法“几乎不会用”,实际项目更常用声明式和简写。

可选参数、默认参数与剩余参数 ​

可选参数解决“这个参数可传可不传”的需求。怎么做?在参数名后加 ?。但要注意:加了问号只解决调用时参数数量报错,函数体里它仍可能是 undefined,直接当 number 用可能报错。

ts
function f3(a: number, b?: number): number {
  return b ? a + b : a + 1;
}

转写稿里的调试过程很典型:先定义两个参数,调用时只传一个,TS 报“应有 2 个参数,但获得 1 个”;给 b 加 ? 后调用不报错,但函数体里若直接把 b 当 number 用,仍可能报 undefined 不能赋给 number。这跟 strict 严格模式有关;非严格模式下 undefined、null 可作为任何类型的子类型赋值,但项目启动配置改了要重启才生效。

默认参数解决“不传就用默认值”。写法是 b: number = 0。易错点:可选参数和默认参数不能同时写在一个参数上,转写稿明确说“参数不能包含问号和初始值”,因为二者都能表达“可不传”,同时存在会冲突。

剩余参数用 ...args: number[] 接收剩余实参。求和例子中先累加前两个参数,再用 reduce 累加剩余数组;args 是数组,不是单个数字。易错点:剩余参数必须放最后,类型要写成数组类型。

类的基本写法与继承 ​

TS 类接近 ES6 类,差别主要是属性和方法要加类型,并且类里有哪些属性要先声明。为什么?因为 TS 要知道 this.name、this.age 是否存在、是什么类型。

ts
class Person {
  name: string;
  age: number;
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
  say(): void {
    console.log(this.name, this.age);
  }
}

继承用 extends。子类构造器里要先 super(name, age) 调父类构造器,再给自己的属性赋值。易错点:子类没调 super 就使用 this 会报错;父类私有属性子类不能访问。

成员修饰符:public、protected、private ​

修饰符解决“谁能访问这个成员”。public 类内部、子类、类外部都能用;protected 类内部和子类能用,外部不能用;private 只有类自己能用,子类也不行。转写稿演示子类里访问父类 private secret 立刻报错,注释掉才恢复。

ts
class Father {
  public name: string;
  protected money: number;
  private secret: string;
  constructor(name: string, money: number, secret: string) {
    this.name = name;
    this.money = money;
    this.secret = secret;
  }
  show(): void {
    console.log(this.name, this.money, this.secret);
  }
}

易错点:protected 不是“谁都能保护”,而是“子类可用、外部不可用”;private 连子类都不能用。

接口:可选属性与只读属性 ​

接口用来严格限定对象结构:对象多了属性不行,少了也不行。定义用 interface,名字通常首字母大写,转写稿习惯以 I 开头,便于识别。属性之间可用分号、逗号或换行分隔。

ts
interface IPerson {
  name: string;
  age: number;
  sex?: string;
  readonly id: string;
}

sex? 是可选属性,解决同一接口下不同对象属性不完全一样的问题。readonly id 是只读属性,初始化后不能改;转写稿用身份证号类比,改了 ID 就“不再是你”。易错点:可选属性不是“随便写”,写了仍要符合类型;只读属性一旦赋值再修改会报“只读属性不可修改”。

接口限定方法、限定类与继承 ​

接口不仅能限定属性,也能限定方法。方法有两种写法:cook(): void 和 cook: () => void。实现接口的类用 implements,并且必须把接口要求的属性和方法都实现出来,方法要有具体函数体。

ts
interface IBoyfriend {
  name: string;
  sex: string;
  cook(): void;
}

class Boy implements IBoyfriend {
  name: string;
  sex: string;
  constructor(name: string, sex: string) {
    this.name = name;
    this.sex = sex;
  }
  cook(): void {
    console.log("会做饭");
  }
}

一个类可以实现多个接口,用逗号隔开;一个接口也可以 extends 多个接口,子接口就拥有父接口的全部限定。易错点:类实现接口后少一个属性或方法都会报错;接口继承后,用类实现该接口时,父接口的要求也都要补齐。转写稿的 IGoddess extends IHuman, IGirlfriend 就是这个推导:女神接口继承了人的要求和女朋友的要求,实现它的类必须全部满足。

本节目标与目录准备 ​

为什么需要:项目里已经有 .ts 文件,但浏览器不能直接识别 TypeScript,需要先编译成 JavaScript。Webpack 可以统一处理入口、输出、开发服务和 HTML 模板,ts-loader 则把 TS 编译接入 Webpack 流程。

怎么想:先准备最小目录:源码放 src,HTML 模板放 public。入口文件是 src/index.ts,最终由 Webpack 打包到 build 目录。

怎么做:创建如下结构:

- src
    - index.ts
- public
	- index.html

易错点:入口路径、模板路径要和配置文件一致;public/index.html 不存在时,HtmlWebpackPlugin 会报模板找不到。

安装依赖与初始化 ​

为什么需要:Webpack 核心负责打包,webpack-cli 提供命令行,webpack-dev-server 提供开发服务,ts-loader 负责编译 TS,html-webpack-plugin 生成并注入 HTML,typescript 是 ts-loader 的编译基础。

怎么想:这些都是开发期工具,统一用 -D 安装到 devDependencies。先 npm init 生成 package.json,再安装依赖。

怎么做:

sh
npm init
npm i webpack webpack-cli webpack-dev-server ts-loader html-webpack-plugin typescript  -D

易错点:不要漏装 typescript,否则 ts-loader 无法正常编译;命令要在项目根目录执行,确保生成 package.json 和 node_modules。

配置 webpack.config.js ​

为什么需要:Webpack 默认不认识 .ts,也不知道入口、输出目录、HTML 模板和开发服务端口,因此要用配置文件把这些规则写清楚。

怎么想:入口用 entry,输出用 output;.ts/.tsx 交给 ts-loader;resolve.extensions 让导入时可省略扩展名;HtmlWebpackPlugin 负责以 public/index.html 为模板生成页面;devServer 设置端口和自动打开。

怎么做:在 src 同级目录创建 webpack.config.js:

js
const {resolve} = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
    entry: './src/index.ts',
    output: {
        path: resolve(__dirname, 'build'),
        filename: 'js/bundle.js' 
    },
    mode: 'development',
    module: {
        rules: [
            {
                test: /\.tsx?$/,
                use: 'ts-loader'
            }
        ]
    },
    resolve: {
        extensions: ['.ts','.tsx','.js']
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: './public/index.html'
        })
    ],
    devServer: {
        port: 3000,
        open: true
    }
}

易错点:output.path 要用绝对路径,所以这里用 resolve(__dirname, 'build');test: /\.tsx?$/ 同时匹配 .ts 和 .tsx;resolve.extensions 不写 .ts 时,导入 TS 模块可能解析失败;mode 建议明确写,避免默认模式带来的提示差异。

配置 tsconfig.json ​

为什么需要:ts-loader 编译 TS 时会读取 tsconfig.json。没有它,TS 的编译目标、模块规范、严格模式等就没有项目级约定。

怎么想:用 tsc --init 生成基础配置,再按项目需要调整。转写稿里提到过一个调试现象:改了 tsconfig.json 的严格模式后,编辑器已经识别,但正在运行的开发服务仍可能按旧配置工作,需要重启服务。这正说明 ts-loader 与 tsconfig.json 是联动的。

怎么做:项目根目录执行:

tsc --init

易错点:tsconfig.json 要放在项目根目录或 Webpack 能识别的范围内;如果开启严格模式,undefined、null 赋给 number 等类型会直接报错,先检查 strict 相关配置;修改配置后若开发服务行为未变,重启 webpack-dev-server 再验证。

启动开发服务与打包 ​

为什么需要:开发时希望改代码后自动编译、自动刷新;上线前则需要产出静态文件。

怎么想:开发阶段用 webpack-dev-server,它把打包结果放在内存中并提供服务;需要落盘产物时用 webpack。

怎么做:

npx webpack-dev-server          # 启动开发服务
npx webpack                     # 打包代码

易错点:webpack-dev-server 默认不会在项目里生成 build 实体文件,真正落盘要执行 npx webpack;端口被占用时改 devServer.port;如果 TS 类型检查报错,先看终端第一条错误,再回到对应 .ts 文件和 tsconfig.json 排查,不要只盯着浏览器页面。

课堂画面 ​

课堂画面 1

课堂画面 2

课堂画面 3

课堂画面 4

课堂画面 5

课堂画面 6

代码示例 ​

示例一:TS 初体验 + 函数(hello.ts) ​

ts
// 运行:tsc hello.ts && node hello.js
let str: string = 'hello world';
console.log(str);

// 1. 声明式定义:参数类型 + 返回值类型
function add(a: number, b: number): number {
  return a + b;
}
console.log(add(1, 100));

// 2. 字面量完整写法:先声明变量的函数类型,再赋值
let f1: (a: number, b: number) => number = (a: number, b: number): number => {
  return a + b;
};

// 3. 字面量简写:靠类型推断
const f2 = (a: number, b: number): number => {
  return a + b;
};
console.log(f1(1, 2), f2(3, 4));

// 4. 可选参数:b 可传可不传(问号)
function f3(a: number, b?: number): number {
  return a + 1;
}
console.log(f3(1));

// 5. 默认参数
function f4(a: number, b: number = 10): number {
  return a + b;
}
console.log(f4(1), f4(1, 2));

// 6. 剩余参数:3、4、5、6、7 全部进入 args
function sum(a: number, b: number, ...args: number[]): number {
  let total: number = 0;
  total += a;
  total += b;
  total = args.reduce((pre, cur) => pre + cur, total);
  return total;
}
console.log(sum(1, 2, 3, 4, 5, 6, 7));

关键行与易错点

  • tsc hello.ts 编译产出 hello.js,node 只认 .js,所以运行 node hello.js;开发时可用 tsc -w 自动编译。
  • 字面量完整写法里“冒号后面的 (a: number, b: number) => number”是变量的类型,等号后面的箭头函数是值,两处类型必须一致,否则立刻报错;简写形式 f2 之所以有类型,靠的是类型推断(赋值给它的函数长什么样,它就被推断成什么样)。
  • 可选参数 b?: number 的类型实际是 number | undefined,所以函数体里直接 return a + b 会报“不能把 undefined 赋给 number”——示例中用 return a + 1 绕开;要么就写 if (b) {...} 判空。
  • 可选参数必须放在参数列表末尾;且问号和默认值不能同时写(b?: number = 10 会报 Parameter cannot have question mark and initializer)。
  • 剩余参数 ...args: number[] 是数组,reduce 的初始值传 total 才能把前面已累加的 a、b 一起带上。

示例二:类、继承与成员修饰符(class.ts) ​

ts
// 运行:tsc class.ts && node class.js
class Father {
  public name: string;      // 公开:类内部、子类、外部都能用
  protected money: number;  // 受保护:类内部 + 子类可用
  private secret: string;   // 私有:只有自己类内部可用

  constructor(name: string, money: number, secret: string) {
    this.name = name;
    this.money = money;
    this.secret = secret;
  }

  say(): void {
    // 类内部:三种属性都能访问
    console.log(this.name, this.money, this.secret);
  }
}

class Son extends Father {
  constructor(name: string, money: number, secret: string) {
    super(name, money, secret); // 必须先调父类构造器
  }

  hello(): void {
    console.log(this.name);  // ✅ public,子类可用
    console.log(this.money); // ✅ protected,子类可用
    // console.log(this.secret); // ❌ private,子类不可访问
  }
}

const s1 = new Son('小S', 20, '暗语');
s1.hello();
console.log(s1.name);        // ✅ 类外部可用
// console.log(s1.money);    // ❌ protected,外部不可用
// console.log(s1.secret);   // ❌ private,外部不可用
s1.say();

关键行与易错点

  • 与 ES6 唯一的大差别:属性必须先声明类型(name: string; 等),构造函数里再 this.xxx = xxx 赋值;ES6 里可以直接在 constructor 里赋值。
  • 子类构造函数里 super(...) 必须在访问 this 之前调用,否则报错。
  • 三个修饰符的记忆口诀:public 全通;protected 自己 + 子类,外部不行;private 只有自己类内部,子类和外部都不行——示例中被注释掉的几行就是会报错的写法。
  • 构造函数参数同样可以加可选(?)或默认值,但二者不能同时使用,因为“可传可不传”和“不传给默认值”语义冲突。
  • 方法没有返回值时写 : void,别漏。

示例三:接口(可选、只读、限定方法、限定类、接口继承)(interface.ts) ​

ts
// 运行:tsc interface.ts && node interface.js

// 1. 接口限定对象结构
interface IPerson {
  name: string;
  age: number;
  sex?: string;         // 可选属性
  readonly id: string;  // 只读属性
}

const p1: IPerson = { name: '艾硅谷', age: 10, id: 'xxx-1' };
const p2: IPerson = { name: '张三', age: 20, sex: 'boy', id: 'xxx-2' };

p1.age = 11;        // ✅ 普通属性可改
// p1.id = 'xxx-9'; // ❌ 只读属性不可修改
console.log(p1.name, p1.age, p2.sex);

// 2. 接口限定属性 + 方法(两种方法写法)
interface IBoyFriend {
  name: string;
  sex: string;
  car: string;
  cook: () => void; // 写法一:函数类型
  speak(): void;    // 写法二:方法签名
}

const b1: IBoyFriend = {
  name: '张三',
  sex: '男',
  car: '共享单车',
  cook() {
    console.log('会做西红柿炒鸡蛋');
  },
  speak() {
    console.log('我不是哑巴');
  }
};
b1.cook();
b1.speak();

// 3. 接口限定类(implements)、一个类实现多个接口、接口继承
interface IHuman {
  name: string;
  sex: string;
  age: number;
}

interface IGirlfriend {
  money: number;
  cook(): void;
}

interface IGoddess extends IHuman, IGirlfriend {} // 接口继承多个接口

class Person implements IHuman, IGirlfriend {
  name: string;
  sex: string;
  age: number;
  money: number;

  constructor(name: string, sex: string, age: number, money: number) {
    this.name = name;
    this.sex = sex;
    this.age = age;
    this.money = money;
  }

  cook(): void {
    console.log(`${this.name} 会做饭`);
  }
}

const g = new Person('女神', '女', 18, 100);
console.log(g.name, g.age, g.money);
g.cook();

// 用继承后的接口再限定一次(等价于要求 IHuman + IGirlfriend 的全部成员)
const g2: IGoddess = { name: '小美', sex: '女', age: 20, money: 999, cook() {} };
console.log(g2.name);

关键行与易错点

  • 接口名首字母大写是规范,习惯加 I 前缀(IPerson),便于和类名、组件名区分。
  • 属性之间用分号、逗号或直接换行都可以,但同一个项目里最好统一。
  • 用接口限定对象时多了不行、少了也不行:sex 加问号才能不写;id 是 readonly,赋值即报错。
  • cook: () => void 与 speak(): void 等价,都是限定方法,注意前者是属性写法(冒号),后者是方法写法(括号)。
  • 接口只声明结构,没有实现;类用 implements 时必须把接口里所有属性和方法全部实现,方法体要自己写,否则报错。
  • 一个类可以 implements A, B(逗号分隔多个接口),那么所有接口的成员都要具备;接口之间用 extends A, B 继承,继承后的接口就拥有了父接口的全部限定。
  • 编译时若想放宽 undefined/null 的检查(如笔记里“非严格模式下 undefined、null 是任何类型的子类型”),可把 tsconfig.json 的 strict 关掉;但初学阶段建议开着,错误提示更早暴露。

常见错误 ​

  1. tsc hello.ts 后执行 node hello.ts 报 SyntaxError: Unexpected token

    • 现象:编译通过,但 node 直接跑 ts 文件报错。
    • 原因:tsc 只做「编译」产出 js,node 不认识 ts 语法与类型标注。
    • 修法:跑 node hello.js;想一步到位就装 ts-node 或走 webpack-dev-server。
  2. 改了 ts 源码,页面/node 输出还是老结果

    • 现象:逻辑改了没生效,怀疑类型写错。
    • 原因:tsc 是编译产物,你改的是 ts,跑的是上一次的 js。
    • 修法:tsc -w 开监听,或使用 webpack-dev-server 热更新;养成「先编译再运行」的顺序。
  3. 单文件编译时 tsconfig.json 不生效

    • 现象:编辑器里没问题,命令行一跑就报 null/undefined 不能赋给 number。
    • 原因:tsc 文件名.ts 是单文件模式,不读取 tsconfig.json,严格模式等配置没参与。
    • 修法:项目内用 tsc(不带文件名)走配置编译,或先把 tsconfig 里相关开关调好再统一编译。
  4. webpack 打包报 Module parse failed / 找不到入口

    • 现象:npx webpack 直接失败,提示无法解析 .ts。
    • 原因:module.rules 里没配 ts-loader,或 resolve.extensions 漏了 .ts,或根目录没生成 tsconfig.json。
    • 修法:补齐 loader 规则与 extensions,先 tsc --init 再打包。
  5. 可选参数写 b?: number 后又给默认值 b = 0,编译报错

    • 现象:问号和初始值同时出现直接标红。
    • 原因:两者语义冲突——有默认值就不必再标可选。
    • 修法:二选一,需要「不传就用默认」只留默认值即可。
  6. 类里声明了属性却没赋值,报「属性没有初始化」

    • 现象:class Person { name: string } 直接报错。
    • 原因:严格初始化检查要求每个声明的属性在构造函数中被赋值。
    • 修法:在 constructor 里 this.name = name;或在声明处给初始值;或确有外部赋值需求时用断言 !。
  7. 子类访问父类 private 属性编译失败

    • 现象:this.secret 在子类中报「私有属性只能在本类访问」。
    • 原因:private 作用域仅限本类内部,子类和外部都不行。
    • 修法:需要子类可用就改 protected;确实私密就提供 public 方法间接访问。
  8. 类 implements 接口后,属性少了报错、多了也报错

    • 现象:少写属性提示「缺少属性」,多写属性提示「对象字面量只能指定已知属性」。
    • 原因:接口是严格结构约束,多退少补都不允许。
    • 修法:确实可能不存在的属性在接口里加 ?;不该被改的加 readonly;接口里声明的方法在类中必须写出完整实现。

练习 ​

  1. 两种函数定义对照:分别用声明式、字面量简写定义同一个 add(a: number, b: number): number,把鼠标悬停在变量上看类型是否一致;然后故意把返回值改成 a + 'b',观察报错位置,再改回。

    • 验收:能说出「简写形式没写类型却不报错」依赖的是什么机制。
  2. 参数三件套:写一个 sum(a: number, b?: number, ...rest: number[]): number 的求和函数。分别调用 sum(1)、sum(1, 2)、sum(1, 2, 3, 4, 5),并打印 rest 的实际内容。

    • 验收:三种调用都不报错,且能解释 b 未传时函数内 b 的真实值。
  3. 修饰符实验:定义 Father(public name / protected money / private secret)和继承它的 Son。在 Son 内部依次访问三个属性,在类外部再依次访问,把每处报错记录下来;然后只把 secret 改成 protected,看哪些报错消失。

    • 验收:能画出一张「public / protected / private × 类内部 / 子类 / 类外部」的可用性表格。
  4. 接口综合:定义 IHuman(name、age)与 IGirlfriend(money、cook()),再用接口继承合成一个 IGoddess,最后写一个类 implements IGoddess。

    • 验收:故意先少实现 cook() 看报错文案,补齐后编译通过;再给某个属性加 readonly 并尝试修改,确认报错。
  5. 工程化跑通:按笔记搭好 webpack + ts-loader + HtmlWebpackPlugin,启动 devServer,改动 index.ts 里的 console.log 内容,确认浏览器自动刷新且输出更新。

    • 验收:npx webpack 与 npx webpack-dev-server 两条命令都能用,build/js/bundle.js 正常产出。

原视频对照 ​

#视频时长
1ts-函数17:01
2ts-class-0107:42
3ts-class-类的修饰符09:14
4ts-接口-可选-只读属性09:46
5ts-接口-限定方法03:54
6ts-接口限定类11:49

小结 ​

  1. tsc 是编译工具不是运行工具,.ts → .js 这层转换必须显式发生,命令行与 node 之间永远隔着一次编译。
  2. tsc 文件.ts 和 tsc 是两种模式,前者不读 tsconfig,配置类问题优先怀疑这一点。
  3. 函数的三种参数修饰(可选 ?、默认值、剩余 ...)互有约束,可选与默认值不能并存。
  4. 类型推断是省事的关键:字面量简写之所以不用手写类型,是因为赋的值已经把类型交代清楚了。
  5. 类的写法与 ES6 几乎一致,唯一新增的硬性动作是「先声明属性类型,再在构造函数中赋值」。
  6. 三个修饰符的差别只在作用域:public 全开、protected 到子类为止、private 止于本类。
  7. 接口是「结构契约」:可选 ? 放宽、readonly 收紧,多写少写都会被拒。
  8. implements 是类对接口的履约,extends 是接口对接口的继承;一个类可履约多个接口,一个接口可继承多个接口,但最终所有约束都要在类里落地实现。