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
});
}