前端--typeScript初体现

Ts之初体现 最近公司项目没有那么忙了,有看到微软出手的Ts这个语言大热,很受开发者的热爱,看来这家伙有过人之处,所以啊,就学习一下Ts在Vue里面的应用,未雨绸缪。作为it开发者,一定要保持不断学习的状态,不然很容易咔嚓的。这个行业有个好处,就是可以不断的学习,尝试新的东西,如果用一个技术长达5到10年,写同一句语句或者单词,真的是无聊枯燥。害处是要不断的学习,不喜欢,身体疲惫,也要假装很喜欢的,很有动力的学习。 微软实力宏厚,出品的工具都很优秀,vscode,outlook,办公三大件,window,我都很喜欢。所以同样看好ts这个工具。 image.png Ts中文网--手册指南 TypeScript是JavaScript的一个超集,js是弱类型语音,Ts可以约束变量的类型,弥补了该不足。代码更严谨。我是刚刚开始学习,至于有什么其他优点,还没有体会到。严谨,严谨,严谨。暂时体会到这一点。 一、基础类型 支持布尔,数字,字符串,数组,元祖,枚举,any,Void,Null,Undefined,Never,Object; 感觉any,接口,object,字符串才是使用率最高的吧,其他了解一下就行 let isDone: boolean = false; //布尔 let decLiteral: number = 6; //数字 let name: string = "bob"; //字符串 let list: number[] = [1, 2, 3]; //数组 let x: [string, number] = ['hello', 10]; //元祖 enum Color {Red, Green, Blue} //枚举 let c: Color = Color.Green; let notSure: any = 4; //any 与never相反 function warnUser(): void { //void console.log("This is my warning message"); } let u: undefined = undefined; //undefined与null let n: null = null; function infiniteLoop(): never { //never while (true) { } } declare function create(o: object | null): void; //object create({ prop: 0 }); // OK 二、接口 (Interface) 接口是什么,上面的类型约束变量是一个个的进行,接口是一堆变量的类型约束。支持可选,只读属性。让开发者成套的撸代码。 使用如下: interface fData { carNo: string, carType: string, } export default class extends Vue{ fData: fData | any = { carNo: '', registTime: '', } } 三、函数 无论是传入的参数,还是返回值,都需要约束类型。支持参数设置默认值,其余参数,this参数。 在vue页面中,get开头的函数是computed计算属性,可以不写返回值的约束类型。大佬灯如是说。 setAsOriderSateToVal(v: number): number { if(v === 3){ return 7; }else{ return 0; } } 四、泛型(用的应该比较少,看业务需求吧) 泛型就是传啥类型,它返回啥类型。传入数组返回数组;传入字符串返回字符串之类。有点那个自动识别的意思。Java语言有类似的概念。 //================函数中的泛型================ function itself(a: T):T { return a } //隐式调用 console.log(itself(true)) // true //显示调用 建议初学者用这个,看得懂 console.log(itself(true)) // true //================接口与泛型配合使用================ //有点像,一堆特定类型结构,返回类似该特定的类型结构 interface Human{ name: string, age: number } function create(returnV: T): T{ return returnV; } //使用 create({ name: 'stephenWu5', age: 18 }) //================可以用泛型接口来结束该函数================ interface cfn { (arg: T): T } let myFun: cfn; myFun = function(arg: T):T{ return arg; } myFun('x') // 返回x 五、枚举 枚举几乎没有使用,之前在公司中需要那种键值对,都是写Json对象,遍历该对象拿到需要的值。 官方给的介绍是: 使用枚举我们可以定义一些带名字的常量。 使用枚举可以清晰地表达意图或创建一组有区别的用例。 其实枚举和Json对象都可以实现业务需求。哪个都无所谓。两者在功能上几乎没有区别。你用哪个还不是一样的用。讲道理是有区别的,要不TypeScript也不会给出。 //json的写法 export const Dierection = { // SZZS:"上证指数", SZCZ:"深圳成指", } //枚举的写法 enum Dierection{ SZZS = '上证指数', SZCZ = '深圳成指', } 印象中都是在前端的查询页面中渲染下拉框时使用。使用语法: v-for="(item,key) of Dierection"。 六、 类型兼容性 其实这个用的也是算少的啰,因为平时写代码的时候习惯需要什么类型,就定义什么类型(连内部的子类型都定义的一模一样),写不一样不就是抬石头砸自己的脚吗? 就是TS语言中赋值时,编译器会检查x中每个属性,能否在y中找到对应属性。主要是用于子类型的检查。 interface Named { name: string; } let x: Named; let y = { name: 'Alice', location: 'Seattle' }; //y 能给 x提供对应的属性,所以没有报错 x = y; //这样子就报错了 y = x; //error 官方文档中比较2个函数的例子: let x = (a: number) => 0; let y = (b: number, s: string) => 0; //目前还没有理解为啥是这样,看了官方的解释还是没法理解。也许是参数可以忽略吧。返回值那个例子倒是很容易理解。 y = x; // OK x = y; // Error 枚举类型与数字类型兼容,数字类型与枚举类型兼容。不同枚举类型之间是不能兼容。 下面的短小内容让我们更好的里面泛型的概念。 因为TypeScript是结构性的类型系统,类型参数只影响使用其做为类型一部分的结果类型。 // interface Empty { } let x: Empty; let y: Empty; x = y; // OK, because y matches structure of x //泛型类型使用时就好像不是一个泛型类型了。 interface NotEmpty { data: T; } let x: NotEmpty; let y: NotEmpty; x = y; // Error, because x and y are not compatible 没有指定泛型类型的泛型参数时,默认所有的泛型为any。所以用结果类型比较时,就不会报错。代码如下: let identity = function(x: T): T { // ... } let reverse = function(y: U): U { // ... } identity = reverse; // OK, because (x: any) => any matches (y: any) => any 七、高级类型(躲开) 一般看到那些高级内容,羞涩难懂,项目中用的少,我都是选择躲开。之前在大学时代啥都学学,很多用不到,浪费时间和力气。经一事长一智。要学多用的。 八、Symbols 之前一段时间在地铁里看一本es6的电子书,了解这个概念。就是一个新型的类型。symbol类型的值是通过Symbol构造函数创建的。就算传入一模一样的字符串参数,他们的值也是不一样的,这个特点让我印象深刻。可以作为对象属性的键值对中的键使用,永远不会和其它键名冲突。 这家伙的使用场景还没有想到,几乎没有使用过,哈哈。 九、命名空间 命名空间就是内部模块,ts所说的模块一般是外部模块。命名空间是为了解决变量名污染和代码维护性而来的。当一个web应用需求越来越多,代码越来越多,为了提高代码的维护性,最好是按照功能拆分一个文件成多个文件。拆分后的文件使用同一个命名空间,那么在使用的时候就如同定义在同一个文件。 使用命名空间里面的变量,接口,方法等,需要xxx.。xxx是命名空间的名称。 //还支持别名呢(多套一个namespace而已) namespace Shapes { export namespace Polygons { export class Triangle { } export class Square { } } } import polygons = Shapes.Polygons; let sq = new polygons.Square(); // Same as "new Shapes.Polygons.Square()" 一般上面所说的命名空间用的少,使用的话会增加额外的模块层,可以改名命苦空间了;但是还有一种命名空间叫外部命名空间,它的使用率很高,没有它不行。js的库或者开发者自己写的工具类js,使用普通的javascript写的,如果直接导入到ts文件,它会识别不了,报错。外部命名空间就是出场解决这个问题,让ts编译器识别上述文件变量和方法的类型(不可能用重写jquery等库一遍吧)。 外部命名空间使用主要走两步: 第一步:一般是在原有路径新增一个xxx.d.ts文件; export = pinyin; declare namespace pinyin { export const codefans: (value: string) => any export const arraySearch: (value: string) => any export const ucfirst: (value: string) => any } 第二步: 编辑tsconfig.json文件,添加合适的路径。 "include": [ "src/**/*.ts", ], 十、后续 官网的基础手册后面的内容还有声明合并,jsx,修饰器,Mixins,三斜线指令,文件类型检查等等。粗粗看了一下,声明合并,就是你在声明命名空间,接口,类,枚举时,如果名字重复了,它会帮你做合并处理(当然不是所有的,类不能和其他类,变量合并),你会用的很少的,因为写代码不会写两个重复的名字(编辑器有提示),你不会傻到抬石头砸自己的脚;jsx会用,但是在vue项目中用的很少,除非是需要那个虚拟dom。以后遇到react项目再详细看;修饰器属于高级的东西,以后熟练TS项目(搞一搞项目),再详细看,否则很难用的到。(其实是我懒不想学哈哈);Mixins就是支持把源对象的属性,方法复制到目标对象上,官网上有个很好的例子,需要的时候再来copy吧;三斜线指令,文件类型检查了解一下就行了,没有必要细细研究,用的少。以后需要再过来看,时间宝贵。 Ts中文网--声明文件 就是教你如何写声明文件。(项目中大量用到) image.png 结构 全局库。在全局命名空间下可以访问,暴露出x个全局变量。在它的源码中,可以看到:顶级的var语句或function声明,一个或多个赋值语句到window.someName,DOM原始值像document或window是存在的。现在他妈的全局库都转UMD库了。很少保持全局的姿态。全局库的使用模版是global.d.ts。全局插件使用global-plugin.d.ts,全局修改模块使用global-modifying-module.d.ts。 模块化库。工作在模块加载器的环境下。模块化库的源码可以看到:无条件的调用require或define,像import * as a from 'b'; or export c;这样的声明,赋值给exports或module.exports。很多流行的nodejs库都是这类。模块能够作为函数调用,使用module-function.d.ts;模块能够使用new来构造,module-class.d.ts;模块不能被调用或构造,使用module.d.ts文件;如果是模块插件,就使用module-plugin.d.ts UMD。名字起的很专业,其实就是上面功能的相加。可以模块导入,也可以全局使用。源码里看到了typeof define,typeof window,或typeof module这样的测试,尤其是在文件的顶端,那么它几乎就是一个UMD库。这种库很主流。 举例 这里手把手教你书写。这一块的内容不用记,知道有这个事就行了,以后忘记写了就过来查。都是一些规则而已。重点学会带属性的对象够受用的了。 全局变量 //代码 console.log(foo) //声明 declare var foo: number; declare const foo: number; //只读 declare let foo: number; //块级作用域 全局函数 //代码 greet('hello world') //声明 declare function greet(greeting: string): void; 带属性的对象(使用率最高) //原代码 let result = myLib.makeGreeting("hello, world"); let count = myLib.numberOfGreetings; //声明 declare namespace myLib{ function makeGreeting(greeting: string): void; let numberOfGreetings: number; } 函数重载 //代码 let x: Widget = getWidget(43); let arr: Widget[] = getWidget("all of them"); //声明 declare function getWidget(n: number): Widget; declare function getWidget(s: string): Widget[]; 可重用类型(接口) //代码 greet({ greeting: "hello world", duration: 4000 }); //声明 interface greetO{ greeting: string; duration?: number; } declare function greet(greeting: greetO):void; 可重用类型(类型别名) //代码 function getGreeting() { return "howdy"; } class MyGreeter extends Greeter { } greet("hello"); greet(getGreeting); greet(new MyGreeter()); //声明 type paraType = string || (()=> string) || MyGreeter; declare function greet(g: paraType): void; 组织类型 //代码 const g = new Greeter("Hello"); g.log({ verbose: true }); g.alert({ modal: false, title: "Current Greeting" }); //声明 declare namespace GreetingLib{ interface LogOptions { verbose?: boolean; } interface AlertOptions { modal: boolean; title?: string; } } 类 //代码 const myGreeter = new Greeter("hello, world"); myGreeter.greeting = "howdy"; myGreeter.showGreeting(); class SpecialGreeter extends Greeter { constructor() { super("Very special greetings"); } } //声明 declare class myGreeter{ constructor(){ super(greeting:string) } greeting: string; showGreeting(): void; } 规范 尽量使用number\string\boolean,不要使用Number\String\Boolean; 回调函数的返回值被忽略时返回void而不是any; 尽量写出回调函数的非可选参数;不要因为回调函数参数个数不同而写不同的重载:用最大参数写一个重载就可以; 当不得不重载时,最细分的重载写在前面,大概的写在后面;否则会被覆盖的; 如果仅仅是尾部参数不同,不要写不同的重载,选择可选参数; 仅仅是某个位置的参数类型不同时,可以使用联合类型。例如number|string 除了第一第二点,其他都是优化的,尽量使用这个规则吧(其实不使用也行,多写几行代码。) Ts中文网--项目配置 官网详细点。 Ts在Vue中的使用 使用vue-property-decorator这个插件,利用修饰符可以简化书写。 image.png 1. @Component (使用率很高) 组件,做前端一定要有组件的能力,提高代码的使用率(省无数代码),开发效率大大提高,省事更省心。有一次面试,面试官说了2次:“会不会写组件?写组件的能力一定要有。”of course。上一家公司快离开时,技术大佬成哥说:有时间多写写自己的组件,提高自己的技术。所以咬咬牙结合项目需求写了两个支持sync特性的组件,还挂在博客园(之前都是维护,没写过自己的)。 //js的写法 export default{ components: { CountDown, TcLocpicker, } } //ts的写法 import { Component, Vue, Watch } from "vue-property-decorator"; @Component({ components: { CountDown, TcLocpicker, }, filters: { upper(v: string){ if (!v) return ''; return v.toString().toUpperCase(); } }, }) export default class extends Vue{ } 2. @Prop (常用) 使用过vue的同学都知道,这是父组件传给子组件的参数,使用率很高。它在class里面的写法是 !:是必须要有的意思。 之前我在项目内插入这个ts,有一个关于prop的报错,报错内容不是很详细的,因为是硬加入项目内,其它地方有很多警告。当然项目是可以跑起来的。我找了2小时也找不到,后来发现是Prop的第一个P没有大写。(注意:所有vue-property-decorator的@开头的变量都是大写的)所以啊,写代码细心很重要。还有一个原因我用的是vim编辑器,如果是vscode编辑器,会出现提示内容。所以以后发现报错一定要结合vscode来找才快。 //js的写法 export default{ props:{ mecType: Number } } //ts的写法 export default class extends Vue { import {Vue, Component,Prop} from 'vue-property-decorator'; //props //写法1 @Prop(String) mecType!: string; //写法2 @Prop([String,Number]) mecType: string | number; //写法3 @Prop({ type: Number, default: 0, required: true, validator: (value) => { return [ 'InProcess', 'Settled' ].indexOf(value) !== -1 } }) mecType!: number; } 3. data (常用) 这个data的使用率是最高的了,就连静态页面也是存在的。有一个.vue文件就有它。 //js的写法 export default{ data: function(){ return { loading: false, mec_Type: 'a' } } } //ts的写法 很简单吧哈哈 export default class extends Vue { loading: boolean = false; mec_Type: string = "a"; //选择的机构类型 } 4. methods (常用) methods里面的方法终于可以不用methods包起来,和那些周期函数created,mounted, 平起平坐了,熬出头了。 //js的写法 export default{ methods: { fn1: function(){ } } } //ts的写法 export default class extends Vue { fn1: function(){ } } 5. computed (较多) 计算属性在组件里面很实用,一般是监听几个变量的变化去改变计算属性值。上一家公司的时候我很少用计算属性,后来用了一下,确实少写许多代码。所以啊,想少写代码,一定去多使用它!,计算属性已经成为我的心头好。 computed在class里面的方法的写法很简单,就是在方法名前面加上get。 //js的写法 export default{ computed: { currentRoleId() { return store.getters.roles }, } } //ts的写法 很简单吧哈哈 export default class extends Vue { get currentRoleId() { return store.getters.roles }, } 6. @Watch (较多) 监听一个变量的是否变化,执行一个方法;该方法不能改变这个变量。否则就会没完没了。有一次确实踩过这个坑,页面上的按钮一点,页面卡住了。加上console.log语句,发现打印20000+,真的是流汗。后来周日加班摆平了,血与泪的教训。 //js的写法 export default{ watch: { 'orderSate': { handler: 'setAsOriderSateToVal', immediate: true, deep: true } }, methods: { setAsOriderSateToVal(val, oldVal) { } } } //ts的写法 很简单吧哈哈 export default class extends Vue { @Watch('orderSate') onChangeOriderSate(v: any, oldv: any) { this.setAsOriderSateToVal(v); }; setAsOriderSateToVal(v){ }; } 7. @Emit (算多) 主要作用就是通讯。其实就是写组件的时候,父组件需要拿到子组件里面的值。怎么办啊,用这个概念从子组件里面发射到父组件里面(emit中文就是发射)。印象中用的最多的就是在页面中按一个添加,弹出一个框,这个框选择一个值,同时这个值传递到页面所在的组件中。 //js的写法 export default{ addToCount(n) { this.count += n this.$emit('add-to-count', n) }, resetCount() { this.count = 0 this.$emit('reset') }, } //ts的写法 很简单吧哈哈 export default class extends Vue { @Emit() addToCount(n: number) { this.count += n }; @Emit('reset') resetCount() { this.count = 0 }; } 8. @Provide和@Inject (少见) 作用是通讯。子组件需要拿到父组件的值,但是业务逻辑复杂时,需要组件之间几层的嵌套,他包她,她包它,它又包另外一个组件。这种情况啊,props不容易拿到,得写好几层props,看到你头痛:我在哪里啊?。所以啊,为了简单达到这个目的: 曾曾子组件一次性拿到父组件的变量,就需要Provide和Inject哈。 //js的写法 export default { //==========子组件取值======================= inject: { foo: 'foo', bar: 'bar' }, //或者使用这种方式 --在xx收款项目中确实这样使用过 inject: ['foo','bar'], //===========父组件注入====================== provide () { return { foo: this.foo, bar: this.baz } }, } //ts的写法 很简单吧哈哈 import {Vue,Component,Inject,Provide} from 'vue-property-decorator'; export default class extends Vue { //==========子组件取值======================= @Inject() foo!: string; @Inject('bar') bar!: string; //===========父组件注入====================== @Provide() foo = 'foo' @Provide('bar') baz = 'bar' } 总结一下,项目配置这一块其实就是换个写法而已,什么computed,methods,data,watch这些都是一些老的概念,没有出现什么新的概念点。只要记住新的写法或者说写的时候查一下文档就可以了。最后感谢大佬灯给我提供TypeScript的学习资料。现在我已经有维护和迭代TypeScript项目的能力了。

本文章由javascript技术分享原创和收集

发表评论 (审核通过后显示评论):