TypeScript 是 JavaScript 的类型的超集,它可以编译成纯 JavaScript。编译出来的 JavaScript 可以运行在任何浏览器上。TypeScript 编译工具可以运行在任何服务器和任何系统上。
ts
优点
- 静态类型检查
- IDE 智能提示
- 代码重构
- 可读性
基础
let test: boolean = true;
let test: string = 'hello';
let test: number = 1;
let test: number[] = [1,2,3];
let test: Array<number>= [1,2,3];
let test: object = { a:1 };
function test(n:number): string {
return n+'hello';
}
重点
- 类型断言
有时候你会遇到这样的情况,你会比TypeScript更了解某个值的详细信息。 通常这会发生在你清楚地知道一个实体具有比它现有类型更确切的类型。
通过类型断言这种方式可以告诉编译器,“相信我,我知道自己在干什么”。 类型断言好比其它语言里的类型转换,但是不进行特殊的数据检查和解构。 它没有运行时的影响,只是在编译阶段起作用。 TypeScript会假设你,程序员,已经进行了必须的检查。
// “尖括号”语法
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
// as语法
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
- 接口 interface
TypeScript的核心原则之一是对值所具有的结构进行类型检查
// 规范首字母大写以 ”I“ 开头
interface ITest {
readonly x:number;
color?: string;
height: number;
width?: number;
[propName: string]: any;
}
// 对象检查
let test: ITest = {
x: 10,
color: 'red',
height: 100,
myProp: 'hello world'
};
test.x = 10; // 报错,只读属性不能赋值
// 继承
interface ITest2 extends ITest {
other: string
}
type
type 与 interface 的用法很类似interface,type两者的区别
相同点:
都可以描述一个对象
interface IUser {
name: string
age: number
}
// ------
type User = {
name: string
age: number
};
都允许拓展(extends)
interface IName {
name: string;
}
interface User extends IName {
age: number;
}
// ------
type Name = {
name: string;
}
type User = Name & { age: number };
不同点:
type 可以而 interface 不行
// 基本类型别名
type Name = string
// 联合类型
interface Dog {
wang: string
}
interface Cat {
miao: string
}
type Pet = Dog | Cat
interface 可以而 type 不行
interface User {
name: string
age: number
}
interface User {
sex: string
}
/*
同名的接口会被默认合并
User的接口为 {
name: string
age: number
sex: string
}
*/
总结:
如果不清楚什么时候用interface/type,能用 interface 实现,就用 interface , 如果不能就用 type。先考虑用interface。
- 泛型 <T>
// 例子
interface IResult<T> {
code: number;
data: T;
message: string;
}
const {
code,
data,
message
}: IResult<{
list: Array<{ id: number; name: string }>;
}> = await this.$request.get('/getList');
// 这样子ide就会有智能提示了
data.list[0].id
data.list[0].name
- 枚举 enum
// 首字母大写
enum Direction {
Up = 1,
Down,
Left,
Right
}
// 如上,我们定义了一个数字枚举, Up使用初始化为 1。 其余的成员会从 1开始自动增长。 换句话说, Direction.Up的值为 1, Down为 2, Left为 3, Right为 4。
ts 一些符号解释
“&” 表示且
type Test = { id: number } & { name: number };
“|” 表示或
type Test = string | number;
type Test = 'test' | 'per' | 'prod';
“?” 表示可选
interface IUser {
name?: string;
}
“!” 非空断言
// 例子
function fun(str: string | null): number {
return str!.length;
}
// 如果编译器不能够去除 null或 undefined,你可以使用类型断言手动去除。 语法是添加 !后缀: identifier!从 identifier的类型里去除了 null和 undefined
vue + ts
vue-class-component VS vue-property-decorator
vue-class-component 是 vue 的官方库,作用是用类的方式编写组件。
这种编写方式可以让.vue文件的js域结构更扁平,并使vue组件可以使用继承、混入等高级特性。
vue-property-decorator 是一个非官方库,是 vue-class-component 的很好的补充。它可以让vue的某些属性和方法,通过修饰器的写法让它也写到vue组件实例的类里面。
比如 @Prop @Watch @Emit ...
一个较完整的demo
<template>
<div class="test">
<Comp></Comp>
</div>
</template>
<script lang="ts">
// 类型约束定义,规范:写在顶部
interface IUser {
id: number;
name: string;
}
import { Component, Vue, Watch } from 'vue-property-decorator';
import { State, Getter, Action, Mutation, namespace } from 'vuex-class';
// 引入子组件
import Comp from '@/components/FormItem';
/* 接口路径常量定义 */
const GET_LIST = 'xxx/getList';
// vuex 模块定义,命名规范moduleXxx,Xxx为实际的模块名
const moduleTest = namespace('test');
@Component({
// 子组件
components: { Comp }
})
// 类名用大写,与router的name一致,这里的name将作为组件的name
export default class Test extends Vue {
// vuex
@moduleTest.State('user')
user!: IUser;
@moduleTest.Action('getUser')
// by the way,方法返回值为空一般这样子写约束 () => {};
getUser!: () => {};
@moduleTest.Mutation('setUser')
setUser!: (user: IUser) => {};
// prop
@Prop({
type: String,
default: ''
})
value!: string;
@Prop({
type: Boolean,
default: false
})
value2!: boolean;
// data 初始化
test1: boolean = false;
test2: Date = new Date();
test3: string = '';
// watch,方法命名 onXxxChanged,Xxx为需要监听的变量名
@Watch('test')
onTestChanged(val: string, oldVal: string) {}
// computed
get test4() {
return this.test1 + this.test3;
}
// 生命周期
created() {}
mounted() {}
// 方法,不需要methods包裹
test(n: number): string {
return n + 'hello world!!!';
}
}
</script>
<style lang="less" scoped>
.test {
}
</style>
约束怎么写,要写在哪里?(局部约束,全局约束)
- 局部约束,用在一个页面上只有【一处】才用到的约束
// ...
<script lang="ts">
const user: { id: number; name: string } = { id: 1, name: '张三' };
</script>
// ...
- 局部约束,用在一个页面上有【多处】需要用到的约束
// ...
<script lang="ts">
// 类型约束定义,规范:写在<script lang="ts">顶部</script>
interface IUser {
id: number;
name: string;
}
// ...
const user: IUser = { id: 1, name: '张三' };
</script>
- 全局约束
出现好几个处都需要用到的约束,可以定义至全局 global.d.ts
新增global.d.ts 文件放置项目根目录
declare global {
interface Window {
VM: any;
}
type Env = 'test' | 'pre' | 'prod';
interface IRequest {
get: (path: string, data?: object, config?: object) => any;
post: (path: string, data?: object, config?: object) => any;
put: (path: string, data?: object, config?: object) => any;
}
}
- 命名空间
由于写了全局约束,可能需要用命名空间来进行优雅的编写,命名空间也可以让我们更加清楚约束是用来做什么的,[namespace统一用小写驼峰]
// 约束vuex的user模块相关
declare global {
namespace myVuex {
namespace user{
interface IUser {
id?: number;
name?: string;
}
interface IState {
user: IUser;
}
}
}
}
其他地方就可以用
const user: myVuex.user.IUser = { id: 1, name: '张三' };
装饰器 Decorator & AOP编程
- 什么是 AOP
面向切面编程(AOP, Aspect Orient Programming)主要实现的目的是针对业务处理过程中的切面进行提取,所面对的是处理过程中某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。
AOP是对OOP的一个横向的补充,主要作用是把一些业务无关的功能抽离,例如日志打印、统计数据、安全控制、异常处理等。这些功能都与核心业务无关,但又随处可见。将其抽离出来用动态插入的方式嵌入到各业务逻辑中。好处是业务模块可变得比较干净、不受污染,同时功能点能够得到很好的复用,给模块解耦。
这里只举环绕通知
before(前置通知)
场景:提交表单时,我想要先通过一系列的验证,然后再执行表单提交after(后置通知)
around(环绕通知)
场景:修饰方法,提交表单的时候,点击提交按钮。我想要在提交前把按钮禁用掉,然后提交成功后【ajax请求后】把按钮启用。
原生写法
/**
* @desc 按钮节流开关,场景:用在异步提交表单的时候,防止用户重复提交
* 1开 0关
* @param prop
*/
const ThorttleButton = (prop: string) => (
target: object,
name: string,
descriptor: any
): any => {
const origin = descriptor.value;
descriptor.value = async function(...args: any) {
this[prop] = 0;
await origin.apply(this, args);
this[prop] = 1;
};
};
export default ThorttleButton;
用 createDecorator 方法
import { createDecorator } from 'vue-class-component';
const ThorttleButton = (prop: string) =>
createDecorator((options, key) => {
const origin = options.methods![key];
options.methods![key] = async function(...args: any) {
(this as any)[prop] = 0;
await origin.apply(this, args);
(this as any)[prop] = 1;
};
});
export default ThorttleButton;
调用
import ThorttleButton from '@/utils/decorate/ThorttleButton';
// ...
postBtn: number = 1;
// ...
@ThorttleButton('postBtn')
async postForm() {
await this.$request.post('/postData');
}
// ...
同一个方法有多个装饰器,装饰器怎么执行?
比喻:会像剥洋葱一样,先从外到内进入,然后由内向外执行。
具体描述:当多个装饰器应用于一个声明上,它们求值方式与复合函数相似。
在这个模型下,当复合 f 和 g 时,复合的结果(f ∘ g)(x)等同于f(g(x))。
1.由上至下依次对装饰器表达式求值。
2.求值的结果会被当作函数,由下至上依次调用。
function f() {
console.log("f(): evaluated");
return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
console.log("f(): called");
}
}
function g() {
console.log("g(): evaluated");
return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
console.log("g(): called");
}
}
class C {
@f()
@g()
method() {}
}
// f(): evaluated
// g(): evaluated
// g(): called
// f(): called