TypeScript
概念
是微软开发的开源编程语言,是JavaScript的超级,是大型开发应用的基石,提供了丰富的语法提示,在编写阶段能够检查错误
新语法特性:
as断言class类关键字(面向对象的三大特性)
问题:什么是TypeScript?
JavaScript和类型系统的结合
问题:类型系统是什么?
- 在开发过程中找错
- 使用类型注解来分析代码
- 仅存在于开发阶段
- 不会提供性能优化
环境
环境设置
在node环境下配置:
因为在Node环境下只能识别 JS 文件,所以 TS 文件需要编译为 JS 文件
//安装TS编译器包 TS -> JS//TSC -> TypeScript compilernpm i -g typescript//TSC使用tsc index.ts//安装自动编译包//解决:多次执行index.js命令输入npm i -g ts-node@8.5.4//使用 先编译后执行ts-node index.ts
定义
类型注解
按照TypeScript形式进行定义,检测代码进行报错,根据错误信息更正错误
interface定义数据类型,确保内容是否符合定义的类型,否则文本编辑器下波浪线报错
//给变量定义interface Todo{id: number;title: string;completed: boolean;}//声明时使用const todo = result.data as Todo;
//报错提醒const ID = todo.ID;属性“ID”在类型“Todo”上不存在。你是否指的是“id”?const fininshed = todo.finished;类型“Todo”上不存在属性“finished”。ts
给函数定义参数类型,避免传参时顺序搞错时可以自动检测出
//给函数定义参数类型const logTodo = (id: number, title: string, completed: boolean) => {console.log(`todo的id为: ${id},标题为: ${title},是否完成: ${completed}`);};//报错提醒logTodo(id, completed, title);类型“boolean”的参数不能赋给类型“string”的参数。
问题:什么是类型?
一个方便描述一个具有相应的属性和方法的值的东西
问题:什么是值?
是用户能够赋值给变量的东西,在TypeScript中,不同的值有不同的类型
问题:如何描述一个值?
通过字符串/数值/布尔值/对象等类型去描述一个值,也可以通过接口interface去自定义一个新的类型去描述一个值
类型
问题:为什么要用类型?
- 能够帮助
TypeScript编译器分析代码 - 能够帮助其他开发者理解整个代码库里存在的值是什么东西
类型指的是方便去描述一个具有相应属性和方法的值,每一个值都有相应的类型
在TypeScript中,常见的基础类型有:
String:字符串类型Number:数值类型Boolean:布尔值类型- 特殊类型:
Date/自定义类型(建立在接口的前提下)
类型分类:
元类型(primitive types)
- number
- boolean
- undefined
- void
- string
- symbol
- null
对象类型(object types)
- functions
- arrays
- classes
- objects
问题:如何让TypeScript编译器认为不同的值对应着不同的类型?
通过对象类型去完成,而不能通过元类型去完成
问题:使用TypeScript有什么副作用?
无论我们是不是想要,它都会自动帮值设定一个类型,它借助类型检测代码是否存在输入问题
类型系统
在TypeScript中,有两个重要的系统:
- 类型注解系统
- 类型推断系统
系统可以作用在不同的元素上,变量、函数、对象
问题:什么是类型注解?
开发者主动告诉TypeScript某个值的类型是什么
//元类型let score: number = 50;let sports: string = 'basketball';let isHappy: boolean = true;let nothingMuch: null = null;let nothing: undefined = undefined;
//对象类型//数组let balls: string[] = ['basketball', 'football', 'volleyball'];let someNums: number[] = [1, -5, 0];let truths: boolean[] = [true, true, false];//类class Car{};let car: Car = new Car();//对象let person: {name: string; age: number} = {name:'zhangsan', age:20};//函数//void: 返回值为空const logNumber: (num: number) => void = (num) => {};
问题:什么是类型推断?
TypeScript尝试去推断值的类型(被动)
关于变量声明和变量初始化:
//变量声明let score;//变量初始化score = 50;
在创建变量的时候经历了两个过程,变量声明和变量初始化,当两个过程同时存在时(在同一行的时候),类型推断系统才会起作用
问题:什么时候使用类型注解?
- 当一个函数返回 any 类型,但是我们想要明确具体类型
- 当某一行声明变量了之后,在另一行进行初始化
- 当我们想要一个变量拥有一个不能推断出来的类型
手动添加类型注解的 3 种情况:
//1.变量声明和变量初始化不在同一行let score;score = 50;//2.当一个函数返回any类型,但是我们想要明确具体类型const json = '{"name":“zhangsan", "age": 20}';//TS不会深层的推断JSON.parse返回的数据,推断为anylet person = JSON.parse(json);//所以需要手动添加类型let person: {name: string, age: number} = JSON.parse(json);//3.当我们想要一个变量拥有一个不能推断出来的类型let numbers = [-1, 0, 8];//numAboveZero -> any//let numAboveZero;//联合类型let numAboveZero: boolean | number = false;for(let i = 0; i < numbers.length; i++){if(numbers[i] > 0){numAboveZero = numbers[i];}}
如何做函数类型注解?
//TS只会检测类型,而不会检测逻辑const addNums = (a: number, b: number): 返回值number => {return a - b;};function multiply(a: number, b: number): number{return a / b;}const divide = function(a: number, b: number): number{return a / b;}
如何做抛出错误函数类型注解?
//never 永远不结束const throwError = (message: string): never => {throw new Error(message);}
解构写法:
//解构语法const todayWeather = {date: new Date(),weather: '晴天'}//没有加解构,但是做了类型注解const logWeather = (todayWeather: {date: Date, weather: string}): void => {console.log(todayWeather.date);console.log(todayWeather.weather);}logWeather(todayWeather);//解构写法://加了解构,但是没做类型注解const ES6logWeather = ({date, weather}) => {console.log(date);console.log(weather);}//完整写法:const logWeather = ({date, weather}: {date: Date, weather: string}): void => {console.log(todayWeather.date);console.log(todayWeather.weather);}
如何做对象类型注解?
const profile = {name: 'mike',age: 20,coords: {lat: 30,lng: 50},setAge(age: number): void{this.age = age;}}//对象写法:const { age }: { age: number } = profile;//对象嵌套写法:const { coords: {lat, lng} }: { coords: {lat: number; lng: number}} = profile;
类型化数组
在TypeScript里,数组也可以像在JavaScript中使用push等方法来操作数组,但有个比较大的区别是,TS的数组是类型化的数组,在数组里放的元素都是同一种类型的,如果加入不同种类类型的元素,也需要写特殊的类型注解
//默认类型推断为 string[]const basketballPlayers: string[] = ['kobe', 'james', 'pierce'];//如果数组是空数组时,需要加注解const basketballPlayers: string[] = [];
//二维嵌套数组://注解写法:const studentsByClass: string[][]const studentsByClass = [['mike', 'tom'],['lee'],['jack', 'rose']];//提取值的时候帮助推断//player: stringconst player = basketballPlayers[0];//使用数组方法的时候帮助推断//favPlayer: stringconst favPlayer = basketballPlayers.pop();//防止加入不一样类型的值//报错basketballPlayers.push(123);//使用map,forEach,reduce函数时提供帮助basketballPlayers.map((car: string): string => {return car;});//容纳不同类型//添加类型注解const importantDates: (string | Date)[] = [];const importantDates = [new Date(), '2020-10-01'];
问题:什么时候使用类型化数组?
一旦需要记录一些相似类型记录的数据结构
元组
和数组很类似的数据结构,每一个元素代表一个记录的不同属性
//tuple 元组//格式为://[string, boolean, number]const drink = {color: 'brown',carbonated: true,sugar: 35}//元组写法const pepsi: [string, boolean, number] = ['brown', true, 35];//类型别名 Type Aliastype Drink = [string, boolean, number];const sprite: Drink = ['brown', true, 35];
问题:为什么要用元组?
在程序里面,使用元组的数据类型比较少,但特殊场景会用到,跟 CSV 文件打交道时会用到
问题:使用元组会有什么缺点?
会丢失很重要的信息
//除了数值类型不能显示其他类型//难以阅读值的信息代表什么const farm: [number, number] = [6, 8];//侧向说明对象是一个更好的数据结构const farm = {chick: 6,duck: 8}
接口
创建一个新的类型,来描述一个对象的属性名和属性值, 对象的形状进行描述
在TypeScript中,会大量的使用接口和类来实现代码的高度复用,对类的一部分行为的抽象
接口分为:
- 函数类型接口
类类型接口:
- 对类的一部分行为的抽象
- 实现接口中的属性和方法
- 可索引类型接口
- 继承接口
const uncleMike = {name: 'Mike',age: 20,married: false}//普通写法:const printPerson = (person: {name: string; age: number; married: boolean}): void => {console.log(person.name);}//定义接口写法:interface Person {name: string;age: number;married: boolean,//方法简写summary(): string;}const printPerson = (person: Person): void => {console.log(person.name);}
在TypeScript中代码复用的一般策略:
- 定义接收接口,指定类型参数的函数
- 存在对象/类去满足接口的必要条件
问题:interface和type有什么区别?
- 都可以用来定义接口,即定义对象或者函数的形状
- 都可以实现继承,也可以相互继承
type可以实现类型别名
类
定义了一个对象的蓝图,描述了这个对象的属性和方法
在TypeScript中,类具有双重特性:
- 创建实例(值)
- 代表类型(类型)
问题:什么时候使用类?
跟接口一样,在TypeScript中,大量使用接口,为了不同文件里面的类进行一个配合工作
描述方法
//定义一个蓝图class Person {//方法protected scream(): void {console.log('ahhh');}}const person = new Person();person.scream();//继承 inheritance//Men可以做的, Person也可以做class Men extends Person {}const men = new Men();men.scream();
问题:什么叫修饰符?
一些用于封装的关键字public,private,protected,为了限制类里面不同属性和方法的访问
问题:为什么要使用修饰符?
限制访问防止开发者错误的调用方法导致程序的破坏
修饰符:
public:这个方法能够在任何地方被调用(自身,子类,实例)private:这个方法只能在当前这个类的其他方法中被调用(自身)protected:这个方法能够在当前这个类的其他方法或子类的其他方法中被调用(自身,子类)implements:关键字,满足接口,告诉 TS 帮助检测类有没有正确的满足接口的实现,没有时会显示错误abstract:关键字,将当前类定义为抽象类,无法创建实例,配合定义将来会用到的方法和接收的参数还有属性使用
描述属性
class Person {//属性name: string = 'Mike';constructor(public name: string){this.name = name;}}const person = new Person('Tom');console.log(person.name);
泛型
定义一个类型变量/泛型变量,这个变量暂时无法知道类型,等待执行时传入参数才知道类型是什么
写法:
//1. 函数泛型的注解方式let a:<T>(arg: T) => T = identify;//2. 对象字面量的方式来定义泛型类型let a: { <T>(arg: T): T } = identify;//3. 泛型接口的定义方式let a: IdentifyInterface = identify;
问题:泛型类型是什么?
只需要多一个泛型,它的类型都可以称为泛型类型
配置
利用parcel-bundler包简易的让TypeScript代码运行在浏览器中
parcel-bundler包工作原理:将打包后的 ts 文件转为 js 文件注入到<script>标签中
//安装npm i -g parcel-bundler//index.html引入ts文件<script src="./src/index.ts"></script>//开启打包服务器parcel serve index.html//Server running at http://localhost:1234
案例
百度地图
案例:地图应用
实现:
- 百度地图展示在页面
- 在地图上标识地点(用户/公司)
- 给标注地点添加信息窗口
技术:TypeScript类 + faker包
关于:
faker包:随机生成数据供浏览器或 node 使用
//安装fakernpm i -D faker@4.1.0//引入import faker from 'faker';//在类型定义文件中定义帮助TS项目认识faker包//JS库(包) -> 类型定义文件 -> TS代码//如流行的JS库如axios自带类型定义文件不用额外去下载//如果不下载类型定义文件,TS推断会提示需要下载//去npm下载faker的类型定义文件 搜索@types/fakernpm i -D @types/faker
问题:什么是类型定义文件?
通过类型定义文件检测 JS 库中是否含有符合 TS 程序所需要的包文件
类型定义文件存储库
Definitely Typed
保存方式为:@types/{JS库的名字}, 如@types/faker
常见的 TS 文件结构:
- 为了配合类工作的接口定义
- 具体类的定义
百度地图开发平台 JavaScript 开发文档 API:
https://lbsyun.baidu.com/index.php?title=jspopularGL/guide/helloworld
//1.引入标签<script type="text/javascript" src="https://api.map.baidu.com/api?v=1.0&type=webgl&ak=bdmzm8j1GXfW44ABN4D5LRGwXvV1hOOa"></script>//2.插入容器<div id="container"></div>//3.测试是否接入百度API,控制台输入BMapGL//打印:{version: 'gl', _register: Array(9), guid: 1, register: ƒ, getGUID: ƒ, …}//4.接入成功//5.在Map.ts文件中下载引入类型定义文件npm i -S @types/baidumap-web-sdknpm install --save @types/bmapgl//6.创建地图实例//实例化访问BMap底下的Map类//Map类接收两个参数://参数1:HTMLElement挂载容器//参数2:配置对象const map = new BMapGL.Map('container', {//最大放大maxZoom: 1});//7.设置中心点坐标var point = new BMapGL.Point(116.404, 39.915);//地图初始化,同时设置地图展示级别map.centerAndZoom(point, 15);
定义公共类管理复用性高的参数类:

//项目结构:├─index.html├─package.json├─src| ├─Company.ts - 定义公司类/使用公共接口| ├─CustomMap.ts - 定义公共接口/地图类| ├─index.ts - 出口文件| └Users.ts - 定义用户类/使用公共接口
项目总结:
- 实现
private关键字在index.ts中使用 API 的限制 - 实现将两个方法或多个方法合并成一个方法,将新增属性或方法归纳在(配合类工作)定义的接口里
- 实现主动检测提示类中是否含有接口里定义的属性和方法
源码地址:https://gitee.com/kevinleeeee/ts-map-class-demo
案例:排序应用
技术:TS 编译器
实现排序:
- 数组
[9, 4, 20, -5] -> [-5, 4, 9, 20] - 字符串
'OedJA -> AdeJO' - 链表
(9 -> -5 -> 30 -> 2) -> (-5 -> 2 -> 9 -> 30)
冒泡排序算法:
- 两个嵌套的
for循环 - 将会多次遍历我们的数据集合
- 每次遍历都会比较数据集合中的一对元素
- 如数组中
[0, 8, -3, 5],元素0 > 8 ?,如果大于交换位置
//项目结构:├─package.json├─Readme.md├─tsconfig.json - TS项目管理文件├─src| ├─CharacterCollection.ts - 字符串类/逻辑/继承父类Sort| ├─index.ts - 入口文件| ├─LinkedList.ts - 链表类/逻辑/继承父类Sort| ├─NumbersCollection.ts - 数值数组类/逻辑/继承父类Sort| └Sorter.ts - 父类/接口定义/排序器逻辑/进行抽象属性和方法
TS 项目搭建:
TS 编译器项目安装:
//安装tsc --init//命令输入后会创建一个tsconfig.json文件
配置tsconfig.json文件:
//配置入口地址和输出目录"outDir": "./build","rootDir": "./src",
设置完毕后测试:
//终端输入命令tsc//build目录生成ts文件打包后的js文件
实时监控index.ts文件修改变化的命令:
//终端输入命令tsc -w//此时终端监听文件变化Starting compilation in watch mode...
如何运行编译后的结果?
//终端输入命令node ./build/index.js//输出打印内容
那么如何自动运行编译后的结果?
//安装nodemon 检测index.js变化//安装concurrently 可以同时运行两个脚本//在package.json文件中修改运行脚本//编译命令:把ts文件编译到js文件"start:build": "tsc -w"//执行编译后的结果"start:run": "nodemon build/index.js"//将两个脚本命令同时运行起来//检测所有npm脚本,以start开头:xxx的脚本,在执行npm start时依次执行"start": "concurrently npm:start:*"//自动化过程命令:编译和运行npm start
知识点 1:
//简写初始化类的属性//简写前:class Sorter {collection: number[];constructor(collection: number[]) {//初始化this.collection = collection;}}//简写后:class Sorter {constructor(public collection: number[]) {...}}
知识点 2:
联合类型会有弊端:
- 在不同类中,保留相同的属性,不同的属性会自动删除导致访问不到,TS 推断报错
- 如数组类型和字符串类型的联合类型上,只有保留数组和字符都有的原生方法,其他原生方法均被删除,而且连数组索引访问的特性都被删除
如何解决弊端?
可以利用类型保护的机制
//判断该类型是否是数组构造出来的//如果是数组构造出来的证明是数组,TS就会推断为数组,从而不会删除属性方法和特性if(this.collection instanceof Array){//...this.collection 不会报提示错误}//判断数据集合类型是字符串的话if (typeof this.collection === 'string') {//...}
问题:什么时候用typeof? 什么时候用instanceof?
- 原始值像
string/boolean/number/symbol类型用typeof - 引用值用
intanceof
双层for循环调试执行顺序:
sort(): void {const {length} = this.collection;//外层遍历所有元素for (let i = 0; i < length; i++) {//内层遍历除了最后一个元素的所有元素//因为最后的元素已经排列到正确的位置了//为什么 - i? 因为遍历次数就为 length - i - 1for (let j = 0; j < length - i - 1; j++) {//前面一项 > 后面一项时if (this.collection[j] > this.collection[j + 1]) {//左手边的元素(前面一项)const leftHand = this.collection[j];//交换位置//修改前面一项的值this.collection[j] = this.collection[j + 1];//修改后面一项的值this.collection[j + 1] = leftHand;}}}}
//数组[10, 5, -8, 0] -> [ -8, 0, 5, 10 ]<调试> 交换位置之前为[10,5,-8,0]<调试> 外层第1次循环:i数值为0<调试> 需要调换3次位置<调试> 内层第1次循环(j < length-i-1 = 3): j数值为0, 交换位置之后为[5,10,-8,0]<调试> 内层第2次循环(j < length-i-1 = 3): j数值为1, 交换位置之后为[5,-8,10,0]<调试> 内层第3次循环(j < length-i-1 = 3): j数值为2, 交换位置之后为[5,-8,0,10]<调试> 外层第2次循环:i数值为1<调试> 需要调换2次位置<调试> 内层第1次循环(j < length-i-1 = 2): j数值为0, 交换位置之后为[-8,5,0,10]<调试> 内层第2次循环(j < length-i-1 = 2): j数值为1, 交换位置之后为[-8,0,5,10]<调试> 外层第3次循环:i数值为2<调试> 需要调换1次位置<调试> 内层第1次循环(j < length-i-1 = 1): j数值为0, 交换位置之后为[-8,0,5,10]<调试> 外层第4次循环:i数值为3
注意点:
//数组和字符串修改的区别var arr = [1, 2, 3];arr[0] = 10;console.log(arr);//[10, 2, 3]var str = 'red';str[0] = 'Z';console.log(str);//red//这里字符串是不可变的
链表结构图:

Node { data: -50, next: Node { data: 0, next: Node { data: 100, next: [Node] } } }Node { data: 0, next: Node { data: 100, next: Node { data: 300, next: null } } }Node { data: 100, next: Node { data: 300, next: null } }Node { data: 300, next: null }
接口和抽象类的对比:
interface:- 在不同的类之间建立合同
- 几种不同的对象之间要配合的时候
- 松耦合
inheritance/abstract classes:- 在不同的类之间建立合同
- 存在相同类型的对象时
- 强耦合
源码地址:https://gitee.com/kevinleeeee/ts-sort-app-demo
购物车
案例:VueJS + TS 制作购物车实战
技术:
vue类 +ts+ 预编译器scss
实现购物车功能:
- 单选或全选商品
- 单项商品数量和价格的联动
- 单选商品点击删除
- 多选商品总价展示
- 结算按钮跳转至配送地址页面
- 点击相应地址卡片会样式高亮显示
- 点击默认地址以外卡片的地址会有设为默认选项
- 地址卡片都有删除按钮
- 查看更多地址按钮展示全部地址
项目写法:
- 类管理
vue组件(vue-property-decorator包实现)
问题:vue如何跟 TS结合实现数据类型检测?
编程步骤:
- 纯
Vue Class定义组件的方式- 结合
TS
利用 vue脚手架工具创建 ts项目:
//1.用vueCli创建项目名称vue create vue-ts-shopping-card-project//2.选择Babel和TypeScript和Router和CSS Pre-processors//3.是否选择类样式组件语法Use class-style component syntax? (Y/n) Yes//4.是否选择BabelUse Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? Yes//5.history mode? Yes//6.配置文件 In dedicated config files//7.配置完成创建项目
问题:在 vue项目中,有什么 ts的特性?
//1.main.js -> main.ts//2.<script lang="ts"></script>//3.引入vue-property-decorator装饰器包import {Vue, Component} from 'vue-property-decorator';//4.装饰器@Component(参数: 组件对象)//5.导出对象的写法://旧: exported default{name:'xxxx',data(){},... }//新: exported default class App extends Vue{}
问题:装饰器有什么作用?
vue文档:
@Component修饰符注明了此类为一个Vue组件@Component(参数: 组件对象的所有选项)
告诉整个 vue程序,所添加装饰器的类不是一个普通的类,而是一个组件,不写时,vue并不能识别当前类为一个组件
在 vue项目中如何使用装饰器?
//安装依赖npm i -S vue-property-decorator@8.3.0//组件中添加@Component装饰器@Componentclass App extends Vue{...}
问题:如何在 vue中添加接口?
//定义一个接口文件types.tsexport interface ICat {...}//在数据文件cats.ts中导入接口import {ICat} from './types';export const cats: ICat[] = [...];//在vue组件中引入接口//在类内部得属性或方法中补充接口//如: get cat(): ICat{...}
问题:methods里的逻辑方法在哪里定义?
//在组件的类里面定义export default class CartBoard extends Vue {//业务逻辑方法, 如点击函数等}
问题:过滤器在哪里定义和使用?
//在组件文件中的装饰器函数里新增filters过滤器对象@Component({name: "CartBoard",//过滤器filters: {currency: function (value: number) {if (!value) {return "0.00";}value = value * 1;//保留2位return "¥" + value.toFixed(2);},},})//视图模板使用<div class="cart-item-total">{{ 123 | currency }}</div>
问题:计算属性在哪里定义和使用?
//在组件的类里面定义export default class CartBoard extends Vue {//get关键字 + 计算函数get totalPrice(){...}}//视图模板使用总价: <span class="price-num">{{ totalPrice }}</span>
项目总结:
TS可以带来一些好处,单词写错时会自动提醒报错,自动检测数据类型
源码地址:https://gitee.com/kevinleeeee/vue-ts-shopping-card-project
新闻头条
案例:新闻头条
这是一个移动版新闻头条
备注:该案例后端有保存新闻头条
mock数据
技术:
vue3TypeScriptEggJSvuex模块写法- 聚合数据
API
搭建:
问题:如何启动Eggjs脚手架?
1. 全局安装npm install create-egg@latest -g2. 创建后端项目脚手架npm init egg --type=ts3.安装依赖npm i4.添加允许跨域npm i egg-cors@2.2.3 -S5.配置 ./api/config/plugin.ts//启动跨域const plugin: EggPlugin = {//启动跨域和引入包cors: {enable: true,package: 'egg-cors'}};6.配置 ./api/config/plugin.ts//设置跨域规则config.cors = {origin: () => '*',allowMethods: 'GET,POST,PUT,DELETE,HEAD,PATCH',credentials: true};//CSRF攻击config.security = {csrf: {enable: false}};//用户和API配置config.userConfig = {//聚合新闻头条数据请求KEYAPP_KEY: '5f2a6f48f30c9ba3ec8df73793458b0f',API: {GET_NEWS_LIST: 'http://v.juhe.cn/toutiao/index'}};
请求接口:
根据新闻类型和页码和每页条数来请求新闻列表数据:
- 请求地址:
http://127.0.0.1:7001/api/news_list - 请求方式:
POST - 参数 1:
type=junshi - 参数 2:
pageNum=1 - 参数 3:
count=10
- 请求地址:
Egg后端框架返回数据的流程:
router(挂载接口):管理请求路径时决定使用控制器controller的哪个apicontroller(外界访问接口):管理等待异步请求(service)返回的数据作为响应数据传给前端service:一个类里面定义的一些异步方法,一般对接数据库操作,或数据请求
vue3环境搭建:
1. 脚手架创建应用vue create news-mobile2. 选择 Babel + TypeScript + Router + Vuex + CSS pre-processors3. 选择3.0版本4. 创建完成npm run serve
项目启动:
//后端npm run dev//前端npm run serve
后端目录:
├─api| ├─.autod.conf.js| ├─.eslintignore| ├─.eslintrc| ├─.gitignore| ├─.travis.yml| ├─appveyor.yml| ├─package-lock.json| ├─package.json| ├─tsconfig.json| ├─typings| | ├─index.d.ts - 定义接口类型的文件| ├─mock - 请求API接口后缓存的模拟数据| | ├─newsData| ├─config - egg框架配置文件| | ├─config.default.ts - 聚合数据Key/API baseUrl/跨域| | ├─config.local.ts| | ├─config.prod.ts| | └plugin.ts - 插件配置/启动跨域插件| ├─app| | ├─router.ts - 路由地址分配controller控制器| | ├─service - 接口服务| | | └api.ts - 请求聚合数据| | ├─public| | ├─lib - 工具| | | └utils.ts - typeof封装/路径参数格式化/分割页码列表数据| | ├─extend| | | └context.ts| | ├─controller - 控制器/对请求service的方法得到的数据响应给前端| | | └api.ts
前端目录:
├─news-mobile| ├─babel.config.js| ├─package-lock.json| ├─package.json| ├─tsconfig.json| ├─src| | ├─App.vue - 应用根组件| | ├─main.ts - 入口文件/导入静态资源文件| | ├─shims-vue.d.ts| | ├─views - 页面组件/列表数据请求| | | ├─Detail.vue - url绑定iframe视图| | | ├─Home.vue - 更改新闻类型并重新请求新闻列表数据/监听加载骨架屏| | | └MyNews.vue - 收藏列表加载| | ├─typings - 定义和管理接口类型的文件| | | ├─common.ts| | | ├─home.ts| | | ├─index.ts - 统一管理| | | └store.ts| | ├─store - vuex状态管理 module模块的写法| | | ├─index.ts - 统一管理合并模块| | | ├─home - home状态| | | | ├─actions.ts - 定义actions(负责commit)| | | | ├─actionTypes.ts - store里的方法名称变量文件| | | | ├─index.ts - 出口文件| | | | ├─mutations.ts - 接收actions命令操作更改state数据| | | | └state.ts - 初始化数据| | | ├─detail - detail状态| | | | ├─actions.ts| | | | ├─actionTypes.ts| | | | ├─index.ts| | | | ├─mutations.ts| | | | └state.ts| | ├─services - 前端请求API封装| | | └index.ts - 请求新闻列表| | ├─router - 前端路由对象定义| | | └index.ts - 页面header信息/路由规则| | ├─lib| | | └http.ts - 拦截器写法封装axios| | ├─directives - 自定义指令| | | ├─index.ts| | | └navCurrent.ts - v-nav-current对视图绑定更新样式| | ├─data| | | └nav.ts - navbar静态数据| | ├─compositions - 自定义hook| | | ├─common.ts - 公共hook/获取header对应的路由信息/图片淡入/触底加载更多| | | ├─detail.ts - 获取详情页当前新闻信息/切换收藏/检查收藏状态| | | ├─home.ts - 获取首页新闻列表/更改新闻类型并重新请求新闻列表数据| | | ├─index.ts - 统一管理hook| | | └mynews.ts - localStorage中获取收藏的新闻信息| | ├─components - 组件| | | ├─Skeleton - 静态骨架屏| | | | └index.vue| | | ├─NoMore| | | | └index.vue| | | ├─NoData| | | | └index.vue| | | ├─NewsList - 新闻列表模板/根据图片数量找对于模板绑定显示/列表触底加载| | | | ├─index.vue| | | | ├─Item0.vue| | | | ├─Item1.vue| | | | ├─Item2.vue| | | | └Item3.vue| | | ├─NavBar - 点击nav-item修改state数据/切换| | | | ├─index.vue| | | | └NavItem.vue| | | ├─Loading| | | | └index.vue| | | ├─Header - 路由监听/路由跳转/点击切换收藏| | | | └index.vue| | ├─assets - 静态资源文件目录| | | ├─js| | | | ├─common.js| | | | └fastclick.js| | | ├─css| | | | ├─border.css| | | | ├─common.css| | | | ├─iconfont.css| | | | └resets.css| ├─public| | ├─favicon.ico| | └index.html
项目总结:
- 后端需要熟悉
MVC的开发模式 前端
- 组件化
vue3中对业务逻辑方法提取到compositionAPI里vue3自定义指令写法- 对请求
axios进行封装时用了拦截器的写法 - 对
service接口目录做了请求方法的封装 vue3中的vuex里运用了模块的方式编写
TypeScript- 对
typings目录文件的管理和接口(interface,enum写法)的定义 - 对所有的变量和方法都进行了类型注解
- 类型注解对于日后维护带来便利
- 对
项目地址: https://gitee.com/kevinleeeee/vue3-typescript-egg-news-mobile-demo
驾照考题
案例:驾照考题
这是一个移动端的前后端集成的项目
技术:
- 前端:
React Hooks+Redux+TypeScript - 后端:
EggJs+TypeScript
接口:
redux类型定义过程:
actionType定义变量类型actions使用类型定义多个action对象{type, payload}reduces里根据action.type去进行更改相应的数据
项目目录:
├─tsconfig.json├─src| ├─App.tsx - 路由分支视图| ├─index.tsx - 入口文件/资源引入/路由引入/仓库引入| ├─views| | ├─Home.tsx - 首页/科目选择/驾照类型选择/开始考试按钮| | ├─Result.tsx - 答题结果页面| | └Test.tsx - 考试中页面/保存用户答题信息并显示下一题信息| ├─typings - 接口和枚举定义文件| | └index.ts| ├─store - 仓库| | ├─actions.ts - 定义action方法并返回action对象| | ├─actionTypes.ts - 变量名管理| | ├─index.ts - 出口文件| | ├─reducers.ts - 根据action对象的类型进行state数据更改| | └state.ts - 数据容器| ├─services| | └index.ts - 客户端请求函数封装| ├─lib| | ├─http.ts - axios函数封装/拦截器方式封装| | └utils.ts - 工具/生成一个组装后的userAnswer对象| ├─hooks - 自定义hooks| | └index.ts - 做题答题数据过程的hook| ├─data - 静态数据| | └index.ts - 科目/驾照类型| ├─components| | ├─TestPanel - 考试中组件| | | ├─index.tsx| | | ├─Question.tsx| | | ├─Selector.tsx| | | ├─styles| | | | ├─index.scss| | | | ├─question.scss| | | | └selector.scss| | ├─Subject - 科目选择按钮组件| | | ├─index.scss| | | └index.tsx| | ├─ResultPanel - 考试结果组件| | | ├─index.scss| | | └index.tsx| | ├─Model - 驾照类型选择按钮组件| | | ├─index.scss| | | └index.tsx| | ├─Header - 标题组件| | | ├─index.scss| | | └index.tsx| | ├─Button - 按钮组件(可复用)| | | ├─index.scss| | | └index.tsx| ├─assets - 静态资源文件| | ├─js| | | ├─common.js| | | └fastclick.js| | ├─css| | | ├─border.css| | | ├─common.css| | | ├─iconfont.css| | | └resets.css├─public
源码地址: https://gitee.com/kevinleeeee/reacthook-typescript-egg-license-mobile-demo
页面切换
案例:页面切换
面向对象插件化开发一个·nav切换page页面
技术:
vite+ts+ 订阅发布模式
功能:
- 直接切换
fade - 滚动切换
slide
类划分:
功能类:
Fade:自身setPage方法的实现fade切换,实例化时执行收集方法传入setPage方法Slide:自身setPage方法的实现slide切换,实例化时执行收集方法传入setPage方法
抽象类:
Base管理公共的功能- 定义容器装载函数方法
- 订阅模式:收集方法
protected getMethod,将继承类的方法放入容器 - 发布通知模式:
protected notify,该方法在点击切换时触发,遍历容器里所有的方法并执行同时传参
源码地址: https://gitee.com/kevinleeeee/ts-plugin-tablist-demo
todolist
案例:todolist
在开发外观模式下做一个待办事项的案例
技术:
ts+vite+ 外观模式
组件划分:
TodoList组件:Input+List,实例化子组件并传入节点容器和配置信息TodoList组件是一个中介,集中管理Input和List的功能,组装子组件最后挂载到todoList容器TodoList组件最终交给index视图(子组件实例创建,渲染,事件绑定)
关于外观模式:
外观模式的基本用法,入口模块直接接收一个TodoList外观模块接口,去实现相应的功能,而不会去对子组件产生任何的依赖
组件化 -> 组件结构 -> 外观模式
项目目录:
├─index.html├─package.json├─src| ├─app.ts - 实例化后初始化todolist插件/根节点和静态数据传入实例| ├─typings| | └index.ts - 接口| ├─components| | ├─TodoList - 根组件/管理子组件的渲染和事件绑定/共享初始化函数| | | ├─index.ts - 自身实现实例化创建子组件| | | ├─Subs| | | | ├─Component.ts - 抽象类/管理组件相关的公共方法/视图模板方法| | | | ├─Input.ts - 输入框组件类| | | | └List.ts - 列表组件类
源码地址: https://gitee.com/kevinleeeee/ts-todolist-appearance-demo
案例:todolist
在开发程序设计模式(横向切割)下做一个待办事项的案例
希望将dom的问题交由后续处理,外层专门来操作数据,数据操作进而变更dom,由类的继承时驱动其他方法的加载,数据的处理和dom的处理是完全分开的
加入后端设计模式的应用,发挥最大的优势
技术:
ts+vite+ 程序设计 + 后端 + 装饰器
传统写法:
绑定事件处理函数,数据的更改:
- 增加项,列表数据增加一项
{id,content,completed},每一项的视图增加到列表中 - 删除项,根据
id去删除一项,将对应项的视图从列表中删除 - 改变完成状态,根据
id去改变,将对应项的完成状态更改成是否完成
接口:
- 列表请求数据(
get):/todolist - 切换是否完成的状态(
post):/toggle - 移除某一项(
post):/remove - 新增某一项(
post):/add
问题:如何不通过数据响应式,虚拟节点等底层数据绑定机制方法去实现程序设计方案?
- 面向对象的方式
- 需要类的继承方式
- 需要横向切割程序方式
问题:如何设计?
将程序进行分类:
app.ts外层:浏览器的事件去调用方法,事件处理函数的绑定TodoEvent.ts操作数据(子类继承DOM类):抛出公共实例方法addTodo,removeTodo,toggleComplete,以便于数据的专门维护TodoDom.ts操作dom(父类同时继承模板类):抛出公共实例方法addItem,removeItem,changeCompleted,以便于dom的专门维护TodoTemplate.ts管理模板(祖先类):todoView,模板可以接收参数,以便于模板的专门维护
问题:为什么要使用装饰器?
在es6写法的类里可以定义一个装饰器用来做副作用的程序加载,实现一个简单句柄的写法多次复用该函数里的副作用程序(如数据请求)
问题:如何使用装饰器?
1. 在使用装饰器的方法上面加入 句柄 @getTodoList2. 定义一个装饰器函数getTodoList参数1:使用该函数装饰器的类(包含类里面的所有方法不管共有还是私有)参数2:使用该函数装饰器的方法名称 _init参数3:是一个修饰符的配置对象3. 保存原有descriptor.value里的函数4. 重写descriptor.value里的函数,等待数据请求成功后去执行原有的函数
使用的装饰器:
@getTodoList:该装饰器添加请求列表数据@removeTodo:该装饰器添加请求删除某一项数据@toggleTodo:该装饰器添加请求修改某一项数据@addTodo:该装饰器添加请求新增列表数据
项目目录:
├─src| ├─app.ts - 节点获取/静态数据/实例化数据类/事件处理函数绑定| ├─typings.ts| | └index.ts| ├─js| | ├─TodoDom.ts - 专门操作处理dom的类| | ├─TodoEvent.ts - 专门操作数据的类/装饰器使用| | ├─TodoService.ts - 专门管理和定义装饰器的函数| | ├─TodoTemplate.ts - 专门管理模板的类| | └utils.ts - 找到父节点/创建一项节点方法
继承关系:
项目总结:
这种设计适用于很多场景,原生或底层项目时,横向切割程序进行分类,有助于单独对某程序进行维护和更新
源码地址: https://gitee.com/kevinleeeee/ts-programming-todolist-demo
案例:todolist
用react-hook的useReducer方式做一个待办事项的案例
技术:
ts+react脚手架 +hooks
问题:useReducer有什么存在意义?
它比useState的写法更为高级,也是一个更高级的解决方案,当使用多个方法操作一个状态数据的变化时,而且这些方法内部有相对比较复杂的逻辑时,可以用useReducer来处理
并且,使用useReducer还能给那些触发更新的组件做性能优化,因为开发者项子组件传递的是dispatch而不是回调函数
项目目录:
├─tsconfig.json├─src| ├─App.tsx| ├─index.tsx| ├─typings| | └index.ts| ├─components| | ├─TodoList| | | ├─index.tsx - todoList组件/定义useEffect/定义dispatch操作某项todo的方法并传子组件| | | ├─reducer.ts - 定义根据action对象的类型操作的具体方法| | | ├─List| | | | ├─index.tsx| | | | └Item.tsx| | | ├─Input| | | | └index.tsx├─public
源码地址: https://gitee.com/kevinleeeee/ts-react-hooks-todolist
插件开发
案例:插件开发
面向对象的方式去开发一个插件库
技术:vite + ts + 面向对象 + 原生
插件:
MyAlert:弹出一个警告模态框
继承关系:
源码地址: https://gitee.com/kevinleeeee/ts-myplugin-demo
购物车
案例:购物车
移动版的购物车
技术:
TS + ReactHook + Express + redux
功能:
- 拦截器封装
axios - 商品列表
- 商品详情颜色,版本,数量选择
- 计算当前页面金额
- 加入购物车
问题:减少http请求的使用分页方法的前后端选择?
后端分页的机制是前端发起请求,携带页码参数,后端接收请求和请求参数,根据页码从数据库获取相应页码数据,最后携带数据响应给客户端,前端拿到数据进行视图渲染
关于前端分页的必要性:
- 对数据量较大时渲染少数的数据在一页中展示,如一些管理系统的管理员账号信息
key - 假如开发业务需求的页面仅需要30条数据,而后端返回90条数据的情况时也需要做前端分页
后端接口:
- 请求列表:
/phones
问题:为什么在redux和vuex中加入action这个过程?
一般在编程过程中,事件触发一个行为,行为去响应一个函数,且action是可以用进行更多的副作用操作,但不推荐组件直接操作mutation和reducer,因为它们是纯函数,且害怕开发者容易在纯函数里写副作用,所以在中间加了一层action,让action去调用reducer或mutation
redux执行过程::
component -> store.dispatch -> action -> ctx.commit -> reducer methods -> state
redux编写顺序:
state仓库初始化和定义类型export default { phoneList: []} as IState;actionTypes定义变量和该变量的类型并导出actions定义函数,该函数返回一个action对象(需定义类型)reducers定义函数,定义state数据默认值,根据action对象的type更改数据,返回一个新的state对象覆盖原有state数据index出口文件定义store方法
关于useEffect和vue3的watchEffect函数:
useEffect依赖外部数据,依赖数据一旦更改就会重新执行该函数内部的程序watchEffect也是依赖外部数据,但内部本身实现了依赖自动收集,不需要手动写入,依赖数据一旦更改就会重新执行该函数内部的程序
问题:其他页面组件如何获取路由参数信息?
在react项目中,通过react-router里的useParams方法去获取
//引入import { useParams } from 'react-router';//参数接口定义interface IUrlParams{id: string | undefined;cid: string | undefined;vid: string | undefined;count: string | undefined;}//解构获取参数const { id, cid, vid, count }: IUrlParams = useParams();
项目目录:
├─tsconfig.json├─src| ├─App.tsx - 根组件/定义路由规则切换页面| ├─index.tsx - 入口组件/引入store/引入路由| ├─views - 页面组件| | ├─Cart.tsx - 购物车| | ├─Detail.tsx - 商品详情/获取符合条件的商品信息| | └Home.tsx - 首页/请求商品列表数据| ├─typings - 类型定义| | ├─index.ts| | ├─phone.ts| | └store.ts| ├─store - 仓库| | ├─actions.ts| | ├─actionTypes.ts| | ├─index.ts| | ├─reducers.ts| | └state.ts| ├─services - 请求函数TS封装| | └index.ts| ├─libs| | └http.ts - axios函数TS封装| ├─hooks - 公共自定义hooks| | ├─index.ts| | ├─useFlatPhoneList.ts - 数据扁平化| | ├─usePhoneDetail.ts - 请求详情列表| | ├─usePhoneList.ts - 请求商品列表| | └useSelectorCount.ts - 根据限制数量决定点击购买数量| ├─components| | ├─Selector - 数量选择器| | | ├─index.scss| | | └index.tsx| | ├─PhoneList - 商品列表| | | ├─index.scss| | | ├─index.tsx| | | └Item.tsx| | ├─Header - 标题| | | ├─index.scss| | | └index.tsx| | ├─Detail - 商品详情| | | ├─hooks.ts| | | ├─index.scss| | | ├─index.tsx| | | ├─VersionGroup - 版本按钮组件| | | | ├─index.scss| | | | └index.tsx| | | ├─Title - 详情标题| | | | └index.tsx| | | ├─Price - 价格| | | | └index.tsx| | | ├─ColorGroup - 颜色按钮组件| | | | ├─index.scss| | | | └index.tsx| | | ├─Bottom - 底部加入购物车| | | | ├─index.scss| | | | └index.tsx| | | ├─Banner - Banner| | | | └index.tsx
源码地址: https://gitee.com/kevinleeeee/ts-react-hook-express-shoppingcart-demo
