共计 11947 个字符,预计需要花费 30 分钟才能阅读完成。
1. 基本类型合集 | |
let decLiteral:number = 6; // 数字,二、八、十六进制都支持 | |
let hexLiteral:number = 0xf00d; | |
let name:string = 'bob'; // 字符串,单双引都行 | |
let sentence:string = `Hello, my name is ${name}` | |
// 数组,第二种方式是使用数组泛型,Array< 元素类型 >:let list:number[] = [1,2,3]; | |
let list:Array<number> = [1,2,3] | |
let u:undefined = undefined; | |
let n:null = null; |
Typescript 与 Javascript 共享相同的基本类型,但有一些额外的类型: | |
①元组 Tuple | |
②枚举 enum | |
③Any 与 Void【2. 特殊类型】【A. 元组 Tuple】想象 元组 作为有组织的数组,你需要以正确的顺序预定义数据类型。const messyArray = ['something', 2, true, undefined, null] | |
const tuple: [number, string, string] = [24, "Indrek" , "Lasn"] | |
如果不遵循 为元组 预设排序的索引规则,那么 Typescript 会警告。【B. 枚举 enum】enum 类型是对 JavaScript 标准数据类型的一个补充。像 C# 等其它语言一样,使用枚举类型可以为一组数值赋予友好的名字。// 默认情况从 0 开始为元素编号,也可手动为 1 开始 | |
enum Color{Red=1,Green=2,Blue=4} | |
let c:Color = Color.Green; | |
let colorName:string = Color[2]; | |
console.log(colorName); // 输出 'Green' 因为上面代码里它的值是 2【C.Void】在 Typescript 中,你必须在函数中定义返回类型。像这样:function sayMyName(name: string): string{return name} | |
console.log(sayMyName("indrek")); // 输出结果是 indrek | |
若没有返回值,则会报错:我们可以将其返回值 sayMyName 定义为 void, 则会报错: | |
function sayMyName(name: string): void{return name}【D. Any】就是什么类型都行,当你无法确认在处理什么类型时可以用这个。但要慎重使用,用多了就失去使用 Ts 的意义。let person:any = '机器人'; | |
person = 24; | |
person = true; | |
主要应用场景有:1. 接入第三方库 2.Ts 菜逼前期都用【E. Never】用很粗浅的话来描述就是:"Never 是你永远得不到的爸爸。" | |
具体的行为是:① throw new Error(message) | |
② return error("Something failed") | |
③ while (true) {} // 存在无法达到的终点 | |
const error = (message: string): never => {throw new Error(message) | |
} | |
const showError = () => error('generic error message'); | |
showError() |
【3. 类型断言】简略的定义是:可以用来手动指定一个值的类型。有两种写法,尖括号和 as: | |
let someValue:any = "this is a string"; | |
let strLength: number = (<string>someValue).length; | |
let strLength: number = (someValue as string).length; | |
使用例子有:当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法. | |
例如:function getLength(something: string | number):number{return something.length} | |
报错: | |
// index.ts(2,22): error TS2339: Property 'length' does not exist on type 'string | number'. | |
// Property 'length' does not exist on type 'number'. | |
如果你访问长度将会报错,而有时候,我们确实需要在还不确定类型的时候就访问其中一个类型的属性或方法,此时需要断言才不会报错:修改为: | |
function getLength(something: string | number):number{if((<string>something).length){return((<string>something).length) | |
} else {return something.toString().length | |
} | |
} |
【4. 泛型:Generics】软件工程的一个主要部分就是构建组件,构建的组件不仅需要具有明确的定义和统一的接口,同时也需要组件可复用。支持现有的数据类型和将来添加的数据类型的组件为大型软件系统的开发过程提供很好的灵活性。在 C# 和 Java 中,可以使用 "泛型" 来创建可复用的组件,并且组件可支持多种数据类型。这样便可以让用户根据自己的数据类型来使用组件。【A. 泛型方法】在 TypeScript 里,声明泛型方法有以下两种方式:function gen_func1<T>(arg: T): T{return arg} | |
// 或者 | |
let gen_func2:<T>(arg: T) => T = function (arg){return arg;} | |
调用方式也有两种:gen_func1<string>('Hello world'); | |
gen_func2('Hello world'); // 第二种调用方式可省略类型参数,因为编译器会根据传入参数来自动识别对应的类型。【B. 泛型与 Any】Ts 的特殊类型 Any 在具体使用时,可以代替任意类型,咋一看两者好像没啥区别,其实不然:// 方法一: 带有 any 参数的方法 | |
functoin any_func(arg: any): any{return arg;} | |
// 方法二: Array 泛型方法 | |
function array_func<T>(arg: Array<T>): Array<T>{return arg;} | |
方法一,打印了 arg 参数的 length 属性。因为 any 可以代替任意类型,所以该方法在传入参数不是数组或者带有 length 属性对象时,会抛出异常。方法二,定义了参数类型是 Array 的泛型类型,肯定会有 length 属性,所以不会抛出异常。【C. 泛型类型】泛型接口例子:interface Generice_interface<T>{(arg: T): T | |
} | |
function func_demo<T>(arg: T): T{return arg;} | |
let func1: Generice_interface<number> = func_demo; | |
func1(123); // 正确类型的实际参数 | |
func1('123'); // 错误类型的实际参数 |
【5. 自定义类型:Interface vs Type alias】Interface,国内翻译成接口。Type alias,类型别名。Typescript 中的 interface 和 type 到底有什么区别【1: 相同点】【A: 都可以用来描述一个对象或函数】interface User{ | |
name: string, | |
age: number | |
} | |
type User = { | |
name: string, | |
age: number | |
} | |
interface setUser {(name: string, age: number):void | |
} | |
type setUser = (name: string, age:number):void【B: 都允许拓展(extends 继承)】//// interface extends interface | |
interface Name{name: string;} | |
interface User extends Name{age:number} | |
//// type extends type | |
type Name = {number:string} | |
type User = Name & {age: number} | |
//// interface extends type | |
type Name = {name: string} | |
interface User extends Name {age: number} | |
//// type extends interface | |
interface Name{name: string} | |
type User = Name & {age: number}【2. 不同点】【A: type 可以而 interface 不行】type 可以声明基本类型别名,联合类型,元组等类型 | |
// 基本类型别名 | |
type Name = string | |
// 联合类型 | |
interface Dog{wong() | |
} | |
interface Cat{miao() | |
} | |
type Pet = Dog | Cat | |
// 具体定义数据每个位置的类型 | |
type PetList = [Dog, Pet]【B: interface 可以而 type 不行】【一: interface 能够声明合并】interface User{ | |
name: string, | |
age: number | |
} | |
interface User{sex: string} | |
/* | |
User 接口为 { | |
name: string | |
age: number | |
sex: string | |
} */【二: interface 有可选属性和只读属性】1. 可选属性 | |
接口里的属性不全都是必需的。有些是只在某些条件下存在,或者根本不存在。例如给函数传入的参数对象中只有部分属性赋值了。带有可选属性的接口与普通的接口定义差不多,只是在可选属性名字定义的后面加一个? 符号。如下所示:interface Person{ | |
name: string, | |
age?:number, | |
gender?:number | |
} | |
2. 可读属性 | |
顾名思义就是这个属性是不可写的,对象属性只能在对象刚刚创建的时候修改其值。你可以在属性名前用 readonly 来指定只读属性。如下所示:interface User{ | |
readonly loginName: string; | |
password: string | |
} | |
上面的例子说明,当完成 User 对象的初始化后 loginName 就不可以修改了。【C: type 语句中还可以使用 typeof 获取实例的 类型进行赋值】// 当你想获取一个变量的类型时,使用 typeof | |
let div = document.createElement("div") | |
type B = typeof div | |
其他骚操作 | |
type StringOrNumber = string | number; | |
type Text = string | {text:string}; | |
type NameLookup = Dictionary<string, Persion>; | |
type Callback<T> = (data: T) => void; | |
type Pair<T> = [T,T]; | |
type Coordinates = Pair<number>; | |
type Tree<T> = T | {left:Tree<T>, right:Tree<T>}; |
【6. 实现与继承:implementsvsextends】extends 很明显就是 ES6 里面的类继承,那么 implement 又是做什么的呢?它和 extends 有什么不同?implement,实现。与 C# 或 Java 里接口的基本作用一样,TypeScript 也能够用它来明确的强制一个类去符合某种契约 | |
implement 基本用法:interface IDeveloper{ | |
name: string, | |
age?:number | |
} | |
// 正确用法一 | |
class dev implements IDeveloper{ | |
name = "Alex", | |
age = 20 | |
} | |
// 正确用法二 | |
class dev2 implements IDeveloper{name = "Alex"} | |
// 错误 | |
class dev3 implements IDeveloper{ | |
name = "Alex", | |
age = "23" | |
} | |
而 extends 是继承父类,两者其实可以混着用:class A extends B implements C,D,E | |
示例:interface Shape{area(): number | |
} | |
type Perimenter = {perimiter(): number | |
} | |
class Rectangle implements PointType, Perimenter, Shape{ | |
x = 2, | |
y = 3, | |
area(){return this.x * this.y} | |
perimiter(){return 2 * (this.x + this.y) | |
} | |
} |
【7. 声明文件与命名空间:declare 和 namespace】前面我们讲到 Vue 项目中的 shims-tsx.d.ts 和 shims-vue.d.ts,其初始内容是这样的:// shims-tsx.d.ts | |
import Vue, {VNode} from 'vue'; | |
declare global { | |
namespace JSX { | |
// tslint:disable no-empty-interface | |
interface Element extends VNode {} | |
// tslint:disable no-empty-interface | |
interface ElementClass extends Vue {} | |
interface IntrinsicElements {[elem: string]: any; | |
} | |
} | |
} | |
// shims-vue.d.ts | |
declare module '*.vue' { | |
import Vue from 'vue'; | |
export default Vue; | |
} | |
declare:当使用第三方库时,我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能。这里列举出几个常用的:declare var 声明全局变量 | |
declare function 声明全局方法 | |
declare class 声明全局类 | |
declare enum 声明全局枚举类型 | |
declare global 扩展全局变量 | |
declare module 扩展模块 | |
namespace:“内部模块”现在称做“命名空间”module X (相当于现在推荐的写法 namespace X) | |
跟其他 JS 库协同 | |
类似模块,同样也可以通过为其他 JS 库使用了命名空间的库创建 .d.ts 文件的声明文件。如为 D3 JS 库,可以创建这样的声明文件:declare namespace D3{export interface Selectors{...} | |
} | |
declare var d3: D3.Base; | |
所以上述两个文件:shims-tsx.d.ts,在全局变量 global 中批量命名了数个内部模块。shims-vue.d.ts,意思是告诉 TypeScript *.vue 后缀的文件可以交给 vue 模块来处理。 |
【8. 访问修饰符:private(公有)、public(私有)、protected(子女)】其实很好理解:1. 默认为 public(公有) | |
2. 当成员被标记为 private 时,它就不能在声明它的类的外部访问,比如:class Animal { | |
private name: string; | |
constructor(theName: string) {this.name = theName;} | |
} | |
let a = new Animal('Cat').name; // 错误,‘name’是私有的 | |
3. protected 和 private 类似,但是,protected 成员在派生类中可以访问 | |
class Animal { | |
protected name: string; | |
constructor(theName: string) {this.name = theName;} | |
} | |
class Rhino extends Animal {constructor() {super('Rhino'); | |
} | |
getName() {console.log(this.name) // 此处的 name 就是 Animal 类中的 name | |
} | |
} |
【9. 可选参数 (?:) 和非空断言操作符(!.)】可选参数: | |
function buildName(firstName: string, lastName ?: string){return firstName + " " + lastName} | |
// 错误演示 | |
buildName("firstName", "lastName", "lastName") | |
buildName("firstName", true) | |
// 正确演示 | |
buildName("firstName") | |
// 正确演示 | |
buildName("firstName", "lastName") | |
非空断言操作符:能确定变量值一定不为空时使用。与可选参数 不同的是,非空断言操作符不会防止出现 null 或 undefined。let s = e!.name; // 断言 e 是非空并访问 name 属性 |
【10. Vue 组件的 Ts 写法】从 vue2.5 之后,vue 对 ts 有更好的支持。根据官方文档,vue 结合 typescript,有两种书写方式:** Vue.extend ** | |
import Vue from 'vue' | |
const Component = Vue.extend({// type inference enabled}) | |
** vue-class-component ** | |
import {Component, Vue, Prop} from 'vue-property-decorator' | |
@Component | |
export default class Test extends Vue{@Prop({ type: Object}) | |
private test: {value: string} | |
} | |
理想情况下,Vue.extend 的书写方式,是学习成本最低的。在现有写法的基础上,几乎 0 成本的迁移。但是 Vue.extend 模式,需要与 mixins 结合使用。在 mixin 中定义的方法,不会被 typescript 识别到,这就意味着会出现丢失代码提示、类型检查、编译报错等问题。 |
【11.vue-class-component】我们回到 src/components/HelloWorld.vue | |
<template> | |
<div class="hello"> | |
<h1>{{msg}}</h1> | |
</div> | |
</template> | |
<script lang="ts"> | |
import {Component, Prop, Vue} from 'vue-property-decorator'; | |
@Component | |
export default class HelloWorld extends Vue {@Prop() private msg!: string; | |
} | |
</script> | |
有写过 python 的同学应该会发现似曾相识:vue-property-decorator 这个官方支持的库里,提供了函数 ** 装饰器(修饰符)** 语法【1. 函数修饰符 @】@”,与其说是修饰函数倒不如说是引用、调用它修饰的函数。或者用句大白话描述:@: "下面的被我包围了。" | |
举个例子,下面的一段代码,里面两个函数,没有被调用,也会有输出结果:test(f){console.log("before...") | |
f() | |
console.log("after...") | |
} | |
@test | |
func(){console.log("func was called") | |
} | |
直接运行,输出结果:before ... | |
func was called | |
after ... | |
上面代码可以看出来: | |
① 只定义了两个函数:test 和 func,没有调用它们。② 如果没有“@test”,运行应该是没有任何输出的。但是,解释器读到函数修饰符“@”的时候,后面步骤会是这样:① 去调用 test 函数,test 函数的入口参数就是那个叫“func”的函数;② test 函数被执行,入口参数的(也就是 func 函数)会被调用(执行);换言之,修饰符带的那个函数的入口参数,就是下面的那个整个的函数。有点儿类似 JavaScript 里面的 function a (function () {...});【2. vue-property-decorator 和 vuex-class 提供的装饰器】vue-property-decorator 的装饰器:@Prop(父子组件之间传值) | |
@PropSync | |
@Model(数据双向绑定) | |
@Watch(监听数据变化) | |
@Provide(提供) | |
@Inject(注入) | |
@Emit(子组件向父组件传递) | |
@Component (provided by vue-class-component) | |
Mixins(公用块) | |
vuex-class 的装饰器:@State(state) | |
@Getter(getter) | |
@Action(action) | |
@Mutation(mutation) | |
我们拿原始 Vue 组件模版来看:import {componentA,componentB} from '@/components'; | |
export default {components: { componentA, componentB}, | |
props: {propA: { type: Number}, | |
propB: {default: 'default value'}, | |
propC: {type: [String, Boolean] } | |
} | |
// 组件数据 | |
data () { | |
return {message: 'Hello'} | |
}, | |
// 计算属性 | |
computed: {reversedMessage () {return this.message.split('').reverse().join('') | |
} | |
// Vuex 数据 | |
step() {return this.$store.state.count} | |
}, | |
methods: {changeMessage () {this.message = "Good bye"}, | |
getName() {let name = this.$store.getters['person/name'] | |
return name | |
} | |
}, | |
// 生命周期 | |
created () {}, | |
mounted () {}, | |
updated () {}, | |
destroyed () {} | |
} | |
以上模版替换成修饰符写法则是:import {Component, Vue, Prop} from 'vue-property-decorator' | |
import {State, Getter} from "vuex-class" | |
import {count, name} from "@/person" | |
import {componentA, componentB} from "@/components" | |
@Component({components: { componentA, componentB} | |
}) | |
export default class HelloWord extends Vue{@Prop(Number) readonly propA!: number | undefined | |
@Prop({default: "default value"}) readonly propB!:string | |
@Prop([String, Boolean]) readonly propC!: string | boolean | undefined | |
// 原 data | |
message = "Hello" | |
// 计算属性 | |
private get reversedMessage (): string[]{return this.message.split('').reverse().join('') | |
} | |
// Vuex 数据 | |
@State ((state: IPootState) => state.booking.currentStep) step!:number | |
@Getter("person/name") name!:name | |
//methods | |
public changeMessage():void{this.message = "Good bye"} | |
public getName(): string{ | |
let storeName = name | |
return storeName | |
} | |
// 生命周期 | |
private created():void{}, | |
private mounted():void{}, | |
private updated():void{}, | |
private destroyed():void{} | |
} | |
正如你所看到的,我们在生命周期 列表那都添加 private XXXX 方法,因为这不应该公开给其他组件。而不对 method 做私有约束的原因是,可能会用到 @Emit 来向父组件传递信息。 |
【12. 添加全局工具】引入全局模块,需要改 main.ts: | |
import Vue from 'vue'; | |
import App from './App.vue'; | |
import router from './router'; | |
import store from './store'; | |
Vue.config.productionTip = false; | |
new Vue({ | |
router, | |
store, | |
render: (h) => h(App), | |
}).$mount('#app'); | |
npm i VueI18n | |
import Vue from 'vue'; | |
import App from './App.vue'; | |
import router from './router'; | |
import store from './store'; | |
// 新模块 | |
import i18n from './i18n'; | |
Vue.config.productionTip = false; | |
new Vue({ | |
router, | |
store, | |
i18n, // 新模块 | |
render: (h) => h(App), | |
}).$mount('#app'); | |
但仅仅这样,还不够。你需要动 src/vue-shim.d.ts:// 声明全局方法 | |
declare module 'vue/types/vue' { | |
interface Vue { | |
readonly $i18n: VueI18Next; | |
$t: TranslationFunction; | |
} | |
} | |
之后使用 this.$i18n() 的话就不会报错了。 |
【13.Axios 使用与封装】如果只是想简单在 Ts 里体验使用 Axios,可以安装 vue-axios 简单使用 Axios | |
$ npm i axios vue-axios | |
main.ts 添加:import Vue from 'vue' | |
import axios from 'axios' | |
import VueAxios from 'vue-axios' | |
Vue.use(VueAxios, axios) | |
然后在组件内使用:Vue.axios.get(api).then((response) => {console.log(response.data) | |
}) | |
this.axios.get(api).then((response) => {console.log(response.data) | |
}) | |
this.$http.get(api).then((response) => {console.log(response.data) | |
}) | |
1. 新建文件 request.ts | |
文件目录: | |
-api | |
- main.ts // 实际调用 | |
-utils | |
- request.ts // 接口封装 | |
2. request.ts 文件解析 | |
import * as axios from 'axios'; | |
import store from '@/store'; | |
// 这里可根据具体使用的 UI 组件库进行替换 | |
import {Toast} from 'vant'; | |
import {AxiosResponse, AxiosRequestConfig} from 'axios'; | |
/* baseURL 按实际项目来定义 */ | |
const baseURL = process.env.VUE_APP_URL; | |
/* 创建 axios 实例 */ | |
const service = axios.default.create({ | |
baseURL, | |
timeout: 0, // 请求超时时间 | |
maxContentLength: 4000, | |
}); | |
service.interceptors.request.use((config: AxiosRequestConfig) => {return config;}, (error: any) => {Promise.reject(error); | |
}); | |
service.interceptors.response.use((response: AxiosResponse) => {if (response.status !== 200) {Toast.fail('请求错误!'); | |
} else {return response.data;} | |
}, | |
(error: any) => {return Promise.reject(error); | |
}); | |
export default service; | |
为了方便,我们还需要定义一套固定的 axios 返回的格式,新建 ajax.ts:export interface AjaxResponse { | |
code: number; | |
data: any; | |
message: string; | |
} | |
3. api.ts 接口调用:// api/api.ts | |
import request from '../utils/request'; | |
// get | |
export function getSomeThings(params:any) { | |
return request({url: '/api/getSomethings',}); | |
} | |
// post | |
export function postSomeThings(params:any) { | |
return request({ | |
url: '/api/postSomethings', | |
methods: 'post', | |
data: params | |
}); | |
} |
正文完
发表至:无分类
2019-07-18