Vue3.0 前的 TypeScript 最佳入门实践

前言
其实Vue官方从2.6.X版本开始就部分使用Ts重写了。
我我的对更严格类型限制没有积极的见解,毕竟各种转类型的骚写法写习惯了。
然鹅最近的一个项目中,是TypeScript+ Vue,毛计喇,学之……真香!
注意此篇标题的“前”,本文旨在讲Ts混入框架的使用,不讲Class API前端

  1. 使用官方脚手架构建
    npm install -g @vue/cli
OR

yarn global add @vue/cli
复制代码新的Vue CLI工具容许开发者 使用 TypeScript 集成环境 建立新项目。
只需运行vue create my-app。
而后,命令行会要求选择预设。使用箭头键选择Manually select features。
接下来,只需确保选择了TypeScript和Babel选项,以下图:vue

完成此操做后,它会询问你是否要使用class-style component syntax。
而后配置其他设置,使其看起来以下图所示。python

Vue CLI工具如今将安装全部依赖项并设置项目。ios

接下来就跑项目喇。ajax

总之,先跑起来再说。
2. 项目目录解析
经过tree指令查看目录结构后可发现其结构和正常构建的大有不一样。vuex

这里主要关注shims-tsx.d.ts和 shims-vue.d.ts两个文件
两句话归纳:typescript

shims-tsx.d.ts,容许你以.tsx结尾的文件,在Vue项目中编写jsx代码
shims-vue.d.ts 主要用于 TypeScript 识别.vue 文件,Ts默认并不支持导入 vue 文件,这个文件告诉ts 导入.vue 文件都按VueConstructor处理。npm

此时咱们打开亲切的src/components/HelloWorld.vue,将会发现写法已大有不一样
axios

{{ msg }}

复制代码至此,准备开启新的篇章 TypeScript极速入门 和 vue-property-decorator
3. TypeScript极速入门
3.1 基本类型和扩展类型api

Typescript与Javascript共享相同的基本类型,但有一些额外的类型。

元组 Tuple
枚举 enum
Any 与Void

  1. 基本类型合集
    // 数字,2、8、十六进制都支持
    let decLiteral: number = 6;
    let hexLiteral: number = 0xf00d;

// 字符串,单双引都行
let name: string = “bob”;
let sentence: string = `Hello, my name is ${ name }.

// 数组,第二种方式是使用数组泛型,Array

let u: undefined = undefined;
let n: null = null;

复制代码2. 特殊类型

  1. 元组 Tuple
    想象 元组 做为有组织的数组,你须要以正确的顺序预约义数据类型。
    const messyArray = [’ something’, 2, true, undefined, null];
    const tuple: [number, string, string] = [24, “Indrek” , “Lasn”]
    复制代码若是不遵循 为元组 预设排序的索引规则,那么Typescript会警告。

(tuple第一项应为number类型)
2. 枚举 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
复制代码另外一个很好的例子是使用枚举来存储应用程序状态。

  1. Void

在Typescript中,你必须在函数中定义返回类型。像这样:

若没有返回值,则会报错:

咱们能够将其返回值定义为void:

此时将没法 return
4. Any

Emmm…就是什么类型都行,当你没法确认在处理什么类型时能够用这个。
但要慎重使用,用多了就失去使用Ts的意义。
let person: any = “前端劝退师”
person = 25
person = true
复制代码主要应用场景有:

接入第三方库
Ts菜逼前期都用

  1. Never

用很粗浅的话来描述就是:“Never是你永远得不到的爸爸。”
具体的行为是:

throw new Error(message)
return error(“Something failed”)
while (true) {} // 存在没法达到的终点

  1. 类型断言

简略的定义是:能够用来手动指定一个值的类型。
有两种写法,尖括号和as:
let someValue: any = “this is a string”;

let strLength: number = (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 ((something).length) {
return (something).length;
} else {
return something.toString().length;
}
}
复制代码3.2 泛型:Generics
软件工程的一个主要部分就是构建组件,构建的组件不只须要具备明确的定义和统一的接口,同时也须要组件可复用。支持现有的数据类型和未来添加的数据类型的组件为大型软件系统的开发过程提供很好的灵活性。
在C#和Java中,可使用"泛型"来建立可复用的组件,而且组件可支持多种数据类型。这样即可以让用户根据本身的数据类型来使用组件。

  1. 泛型方法
    在TypeScript里,声明泛型方法有如下两种方式:
    function gen_func1(arg: T): T {
    return arg;
    }
    // 或者
    let gen_func2: (arg: T) => T = function (arg) {
    return arg;
    }
    复制代码调用方式也有两种:
    gen_func1(‘Hello world’);
    gen_func2(‘Hello world’);
    // 第二种调用方式可省略类型参数,由于编译器会根据传入参数来自动识别对应的类型。
    复制代码2. 泛型与Any
    Ts 的特殊类型 Any 在具体使用时,能够代替任意类型,咋一看二者好像没啥区别,其实否则:
    // 方法一:带有any参数的方法
    function any_func(arg: any): any {
    console.log(arg.length);
    return arg;
    }

// 方法二:Array泛型方法
function array_func(arg: Array): Array {
console.log(arg.length);
return arg;
}
复制代码
方法一,打印了arg参数的length属性。由于any能够代替任意类型,因此该方法在传入参数不是数组或者带有length属性对象时,会抛出异常。
方法二,定义了参数类型是Array的泛型类型,确定会有length属性,因此不会抛出异常。

  1. 泛型类型
    泛型接口:
    interface Generics_interface {
    (arg: T): T;
    }

function func_demo(arg: T): T {
return arg;
}

let func1: Generics_interface = func_demo;
func1(123); // 正确类型的实际参数
func1(‘123’); // 错误类型的实际参数
复制代码3.3 自定义类型:Interface vs Type alias
Interface,国内翻译成接口。
Type alias,类型别名。

如下内容来自:

Typescript 中的 interface 和 type 到底有什么区别

  1. 相同点
    均可以用来描述一个对象或函数:
    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;

复制代码都容许拓展(extends):
interface 和 type 均可以拓展,而且二者并非相互独立的,也就是说interface能够 extends type, type 也能够 extends interface 。 虽然效果差很少,可是二者语法不一样。
interface extends interface
interface Name {
name: string;
}
interface User extends Name {
age: number;
}
复制代码type extends type
type Name = {
name: 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. 不一样点
type 能够而 interface 不行

type 能够声明基本类型别名,联合类型,元组等类型

// 基本类型别名
type Name = string

// 联合类型
interface Dog {
wong();
}
interface Cat {
miao();
}

type Pet = Dog | Cat

// 具体定义数组每一个位置的类型
type PetList = [Dog, Pet]
复制代码
type 语句中还可使用 typeof获取实例的 类型进行赋值

// 当你想获取一个变量的类型时,使用 typeof
let div = document.createElement(‘div’);
type B = typeof div
复制代码
其余骚操做

type StringOrNumber = string | number;
type Text = string | { text: string };
type NameLookup = Dictionary

interface User {
sex: string
}

/*
User 接口为 {
name: string
age: number
sex: string
}
*/

复制代码interface 有可选属性和只读属性

可选属性
接口里的属性不全都是必需的。 有些是只在某些条件下存在,或者根本不存在。 例如给函数传入的参数对象中只有部分属性赋值了。带有可选属性的接口与普通的接口定义差很少,只是在可选属性名字定义的后面加一个?符号。以下所示

interface Person {
name: string;
age?: number;
gender?: number;
}
复制代码

只读属性
顾名思义就是这个属性是不可写的,对象属性只能在对象刚刚建立的时候修改其值。 你能够在属性名前用 readonly来指定只读属性,以下所示:

interface User {
readonly loginName: string;
password: string;
}
复制代码上面的例子说明,当完成User对象的初始化后loginName就不能够修改了。
3.4 实现与继承:implementsvsextends
extends很明显就是ES6里面的类继承,那么implement又是作什么的呢?它和extends有什么不一样?
implement,实现。与C#或Java里接口的基本做用同样,TypeScript也可以用它来明确的强制一个类去符合某种契约
implement基本用法:
interface IDeveloper {
name: string;
age?: number;
}
// OK
class dev implements IDeveloper {
name = ‘Alex’;
age = 20;
}
// OK
class dev2 implements IDeveloper {
name = ‘Alex’;
}
// Error
class dev3 implements IDeveloper {
name = ‘Alex’;
age = ‘9’;
}
复制代码而extends是继承父类,二者其实能够混着用:
class A extends B implements C,D,E
复制代码搭配 interface和type的用法有:

3.5 声明文件与命名空间: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 模块来处理。

3.6 访问修饰符:private、public、protected
其实很好理解:

默认为public

当成员被标记为private时,它就不能在声明它的类的外部访问,好比:

class Animal {
private name: string;
constructor(theName: string) {
this.name = theName;
}
}

let a = new Animal(‘Cat’).name; //错误,‘name’是私有的
复制代码
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
}
}
复制代码3.7 可选参数 ( ?: )和非空断言操做符(!.)
可选参数
function buildName(firstName: string, lastName?: string) {
return firstName + ’ ’ + lastName
}

// 错误演示
buildName(“firstName”, “lastName”, “lastName”)
// 正确演示
buildName(“firstName”)
// 正确演示
buildName(“firstName”, “lastName”)
复制代码非空断言操做符:
能肯定变量值必定不为空时使用。
与可选参数 不一样的是,非空断言操做符不会防止出现 null 或 undefined。
let s = e!.name; // 断言e是非空并访问name属性
复制代码4. 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 识别到
,这就意味着会出现丢失代码提示、类型检查、编译报错等问题。
菜鸟才作选择,大佬都挑最好的。直接讲第二种吧:
4.1 vue-class-component

咱们回到src/components/HelloWorld.vue

{{ msg }}

复制代码有写过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 () { … });

  1. vue-property-decorator和vuex-class提供的装饰器
    vue-property-decorator的装饰器:

@Prop
@PropSync
@Provide
@Model
@Watch
@Inject
@Provide
@Emit
@Component (provided by vue-class-component)
Mixins (the helper function named mixins provided by vue-class-component)

vuex-class的装饰器:

@State
@Getter
@Action
@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.KaTeX parse error: Expected 'EOF', got '}' at position 23: …tate.count }̲ }, methods…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 HelloWorld 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: IRootState) => state . booking. currentStep) step!: number
@Getter( ‘person/name’) name!: name

// method
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来向父组件传递信息。
4.2 添加全局工具
引入全局模块,须要改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;
KaTeX parse error: Expected 'EOF', got '}' at position 29: …nFunction; }̲ } 复制代码以后使用this…i18n()的话就不会报错了。
4.3 Axios 使用与封装
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. main.ts接口调用:
// api/main.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
});
}
复制代码5. 编写一个组件
为了减小时间,咱们来替换掉src/components/HelloWorld.vue,作一个博客帖子组件:


{{ post.title }}


{{ post.body }}


Written by {{ post.author }} on {{ date }}




复制代码而后在Home.vue中使用:

Vue logo

复制代码这时候运行项目:

这就是简单的父子组件

  1. 参考文章
    TypeScript — JavaScript with superpowers — Part II
    VUE WITH TYPESCRIPT
    TypeScript + 大型项目实战
    Python修饰符 (一)—— 函数修饰符 “@”
    Typescript 中的 interface 和 type到底有什么区别
  2. 总结

而关于Class API撤销,其实仍是挺舒服的。
用class 来编写 Vue组件确实太奇怪了。
(因此我这篇Ts入门压根没写Class API)

❤️

做者:前端劝退师 连接:https://juejin.im/post/5d0259f2518825405d15ae62 来源:掘金 著做权归做者全部。商业转载请联系做者得到受权,非商业转载请注明出处。

相关文章
相关标签/搜索