【专项学习】—— Typescript语法进阶
一、TypeScript中的配置文件
①生成tsconfig.json文件
npm install typescript -g tsc init
②tsconfig.json文件的作用是typescript对应的编译配置文件
③运行tsc demo.ts会编译成demo.js文件
④只有单独输入tsc命令,后面不跟任何参数时,才会执行tsconfig里面的配置
ts-node demo.ts也会执行tsconfig里面的配置项
⑤compilerOptions前(或后)添加include指定想要编译的文件
"include": ["./demo.ts"] 同"files":["./demo.ts"]
也可以写exclude指定编译所有内容所要排除不编译的文件
"exclude": ["./demo.ts"]
⑥compilerOptions是编译属性配置项
"removeComments": true, //移除注释 "noImpliciAny": true, //不明确的指定any类型不行 "stictNummChecks": true, //强制检测Null类型,将null赋值给基础类型会报错 "rootDir": "./src", //指定输入文件的地址 "outDir": "./build", //指定编译后生成文件的地址 "incremental": true, //增量编译,以前编译过的内容不会再编译,只编译新增的文件 "allowJs": true, //是否对js文件也进行编译,把ES6语法编译为ES5语法文件 "checkJs": true, //允许对js文件像ts文件一样进行语法检测
二、联合类型和类型保护
interface Bird { fly: boolean; sing: () => {}; } interface Dog { fly:boolean; bark: () => {}; } //联合类型 - 属性只会取共有的属性 function trainAnimal(animal: Bird | Dog ) { //1.类型断言 — 类型保护,规避typescript的潜在错误提示 if(animal.fly) { (animal as Bird).sing(); }else{ (animal as Dog).bark() } } function trainAnimalSecond(animal: Bird | Dog) { //2.in语法 - 类型保护 if('sing' in animal) { animal.sing(); }else{ animal.bark(); } } function add(first: string | number, second: string | number) { //3.typeof 语法 - 类型保护 if(typeof first === 'string' || typeof second === 'string'){ return `${first}${second}`; } return first + second; } //只要class 才能被instanceof操作符使用,interface不能 class NumberObj { count: number } function addSecond(first: object | NumberObj, second: object | NumberObj){ //4.instanceof 语法 - 类型保护 if(first instanceof NumberObj && second instanceof NumberObj){ return first.count + second.count } return 0; }
三、Enum枚举类型
①初始化项目,生成package.json
npm init -y
②安装ts-node依赖
npm install ts-node -D
③package.json中添加命令
"dev": "ts-node ./demo.ts"
④安装typescript
npm install typescript --save
⑤Enum枚举类型应用
enum Status { OFFLINE, ONLINE, DELETED } // const Status = { // OFFLINE: 0, // ONLINE: 1, // DELETED: 2 // } function getResult(status) { if(status === Status.OFFLINE) { return 'offline'; }else if(status === Status.ONLINE){ return 'online'; }else if(status === Status.DELETED){ return 'deleted'; } return 'error' } const result = getResult(Status.OFFLINE); console.log(result); //offline
四、函数泛型
//泛型 generic 泛指的类型 //T可以是任何类型的泛型 function join<T, P>(first: T, second: P) { return `${first}${second}`; } join<number, string>(1, '1');
// T[] function map<T>(params: Array<T>){ return params; } map<string>(['123']);
五、类中的泛型类型以及泛型类型
①类中的泛型类型
interface Item { name: string; } class DataManager<T extends Item> { constructor(private data: T[]) {} getItem(index: number): string { return this.data[index].name; } } // const data = new DataManager<number>([1]); // data.getItem(0); const data = new DataManager([ { name: 'dell' } ])
class DataManager<T extends number | string> { constructor(private data: T[]) {} getItem(index: number): T { return this.data[index] } } // const data = new DataManager<string>([]); const data = new DataManager<number>([]);
②泛型声明的type类型
// const func: <T>() => string = <T>() => { // return '123' // } //如何使用泛型作为一个具体的类型注解 function hello<T>(param: T) { return param } const func: <T>(param: T) => T = hello;
六、命名空间namespace
①tsconfig.json
"outDir": "./dist", "rootDir": "./src",
②index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Document</title> <script src='./dist/page.js'></script> </head> <body> <script> new Home.Page(); </script> </body> </html>
③src->page.ts: Home命名空间中,export暴露出要用的page,index.html中就可以使用new Home.page();其它方法不使用则不暴露
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(); } } }
- 拆分页面和组件多个命名空间
①tsconfig.json
"outFile": "./dist/page.js" , //把所有要输出的文件联合起来输出一个单一的文件
此时会有报错,因为设置了outFile后只支持amd或system的规范,不支持commenjs的规范了
"module": "amd",
②index.html不变
③src->page.ts依赖conponents.ts
//依赖声明 ///<reference path="./components.ts" /> namespace Home { export class Page { user: Components.User = { name: 'dell' } constructor() { new Components.Header(); new Components.Content(); new Components.Footer(); } } }
④src->components.ts暴露给page.ts所需要的方法和子命名空间
namespace Components { //子的命名空间 export namespace SubComponents { export class Test {} } export interface User { name: string; } 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); } } }
⑤问题:page.ts中依赖的Components命名空间必须在打开其它文件后才可以追溯到
七、import对应的模块化
①src->components.ts:export暴露三个变量
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); } }
②src->page.ts:import导入components的变量,export default最终要用到的Page
import { Header, Content, Footer } from './components' export default class Page { constructor() { new Header(); new Content(); new Footer(); } }
③问题:'define' is not defined,原因是现在打包生成的文件是amd规范,浏览器不识别define()语法
④index.html中引入require.js,帮助识别define()语法;
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Document</title> <script src='https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.js'></script> <script src='./dist/page.js'></script> </head> <body> <script> require(['page'], function(page) { new page.default() }) </script> </body> </html>
八、使用parcel打包TS代码
①安装parcel@next打包工具
npm install parcel@next -D
②package.json中修改test命令
"test": "parcel ./index.html"
③运行npm run test,开启一个服务器地址http://localhost:1234
九、描述文件中的全局类型
①index.html中引用jquery的CDN文件
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Document</title> <script src='https://cdn.bootcss.com/jquery/3.4.1/jquery.js'></script> <script src='./page.ts'></script> </head> <body> </body> </html>
②jquey语法在typescript中没有注解,需要写一个类型定义文件,打通ts文件和js文件之间的鸿沟
③src->page.ts:使用jqurey写js语句
// $(function() { // alert(123); // }) $(function() { $('body').html('<div>123</div>') })
④src->jquery.d.ts:类型注解文件
//declare 声明 - 定义全局变量 // declare var $ : (param: () => void) => void; //declare 声明 - 定义全局函数 declare function $(param: () => void): void; //函数重载:参数不同,允许对一个函数名字写多个函数声明 declare function $(param: string): { html: (html: string) => {} }
问题:jquery函数返回的应该也是一个jqury对象,不能指定为{}
//定义全局函数 interface JqueryInstance { html: (html: string) => JqueryInstance; } declare function $(readyFunc: () => void): void; declare function $(selector: string): JqueryInstance;
使用 接口语法 写函数重载
//定义全局函数 interface JqueryInstance { html: (html: string) => JqueryInstance; } //使用interface的语法,实现函数重载 interface JQuery { (readyFunc: () => void): void; (selector: string): JqueryInstance; } declare var $: Jquery;
- 如何对对象进行类型定义,以及对类进行类型定义,以及命名空间的嵌套
①page.ts:在使用$('body')时,相当于执行了new $.fn.init();
$(function() { $('body').html('<div>123</div>'); new $.fn.init(); //构造函数 才能用new语法 })
②jquery.d.ts
//定义全局函数 interface JqueryInstance { html: (html: string) => JqueryInstance; } //函数重载 declare function $(readyFunc: () => void): void; declare function $(selector: string): JqueryInstance; //如何对对象进行类型定义,以及对类进行类型定义,以及命名空间的嵌套 declare namespace $ { //$ 是命名空间 namespace fn { //fn 是命名空间 class init {} //init 是构造函数 } }
十、模块代码的类型描述文件
①安装jquery
npm install jquery --save
②page.ts:import引用jquery,会提示报错,找不到jquery module
import $ from 'jquery'; $(function() { $('body').html('<div>123</div>'); new $.fn.init(); //构造函数 才能用new语法 })
③jquery.d.ts: 通过module定义一个jquery的模块,定义关于$混合类型的内容,最终export导出要使用的$
//ES6 模块化 declare module 'jquery' { //定义全局函数 interface JqueryInstance { html: (html: string) => JqueryInstance; } //$ 混合类型 - 不必再写declare, 直接写function即可 function $(readyFunc: () => void): void; function $(selector: string): JqueryInstance; namespace $ { //$ 是命名空间 namespace fn { //fn 是命名空间 class init {} //init 是构造函数 } } export = $; }
十一、泛型中keyof语法的使用
interface Person { name: string; age: number; gender: string; } class Teacher { constructor(private info: Person) {} getInfo(key: string){ //类型保护 if(key === 'name' || key === 'age' || key === 'gender'){ return this.info[key]; } } } const teacher = new Teacher({ name: 'dell', age: 18, gender: 'male' }) const test = teacher.getInfo('name'); console.log(test)
问题:test的可能类型会是string | number | underfind,是因为传任何值给getInfo方法时,key值不定
typescript不会深度分析,传入的是不是name类型,如果是则值必须是string类型;传入的是age类型,值必须是number类型
解决:通过泛型结合keyof语法
interface Person { name: string; age: number; gender: string; } //第一次循环 //T extends 'name' //type T = 'name' //key: 'name' //Person['name']; //string //第二次循环 //T extends 'age' //type T = 'age' //key: 'age' //Person['age']; //number class Teacher { constructor(private info: Person) {} getInfo<T extends keyof Person>(key: T): Person[T]{ return this.info[key]; } } const teacher = new Teacher({ name: 'dell', age: 18, gender: 'male' }) const test = teacher.getInfo('name'); console.log(test)
注:课程源自慕课网