Vue2 + TypeScript实践准备

基于Vue2.5版本,Vue3.0可能有所改变.

知识准备

  • 扎实的 JavaScript / HTML / CSS 基本功

  • 通读Vue官方教程 (guide) 的基础篇

  • 熟悉vue-router 和 vuex

  • 熟悉ES6 modules

  • 熟悉Node.js 基础

  • 了解如何使用 / 配置 Babel 来将 ES2015 编译到 ES5 用于浏览器环境

  • 熟悉 Webpack

  • vue-cli 来搭建Vue项目

Node.js

Node.js是什么?

Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。
Node.js 使用了一个事件驱动、非阻塞式I/O 的模型,使其轻量又高效。

npm模块管理器

npm有两层含义。一层含义是Node的开放式模块登记和管理系统,网址为npmjs.org。另一层含义是Node默认的模块管理器,是一个命令行下的软件,用来安装和管理Node模块。

1
2
3
4
5
6
7
npm init
npm install lodash-es
npm uninstall lodash-es
npm update lodash-es

npm install lodash-es --save
npm install lodash-es --save-dev

npm run

npm不仅可以用于模块管理,还可以用于执行脚本。package.json文件有一个scripts字段,可以用于指定脚本命令,供npm直接调用。

1
2
3
4
5
6
7
8
9
// package.json
{
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"test:unit": "vue-cli-service test:unit"
}
}
1
npm run serve

package.json文件

每个项目的根目录下面,一般都有一个package.json文件,定义了这个项目所需要的各种模块,以及项目的配置信息(比如名称、版本、许可证等元数据)。npm install命令根据这个配置文件,自动下载所需的模块,也就是配置项目所需的运行和开发环境。

  • name:描述的是模块的名称

  • version: 描述模块的版本号(遵循语义化版本)

  • main: 指定入口文件,默认为index.js

  • dependencies:字段指定了项目运行所依赖的模块

  • devDependencies:指定项目开发所需要的模块。

  • scripts: npm run 执行的命令就是这里指定的。

CommonJS规范

Node 应用由模块组成,采用 CommonJS 模块规范。

每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。

1
2
3
4
5
6
7
8
9
// example.js
var x = 5;
var addX = function (value) {
return value + x;
};

// 导出之后才可见
module.exports.x = x;
module.exports.addX = addX;
1
2
3
4
5
// 使用example模块
var example = require('./example.js');

console.log(example.x); // 5
console.log(example.addX(1)); // 6

TypeScript

TypeScript具有类型系统,且是JavaScript的超集。 它可以编译成普通的JavaScript代码。 TypeScript支持任意浏览器,任意环境,任意系统并且是开源的。

基础类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// boolean
let isDone: boolean = false;

// 数字
let decLiteral: number = 6;
let hexLiteral: number = 0xf00d;
let binaryLiteral: number = 0b1010;
let octalLiteral: number = 0o744;

// 字符串
let name: string = "bob";

// 数组
let list: number[] = [1, 2, 3];
let list: Array<number> = [1, 2, 3];

// 元组
let x: [string, number] = ['hello', 10]; // OK
x[3] = 'world'; // OK, 字符串可以赋值给(string | number)类型

// 枚举
enum Color {
Red,
Green,
Blue
}

let c: Color = Color.Green;

任意值any

但是 Object 类型的变量只是允许你给它赋任意值 - 但是却不能够在
它上面调用任意的方法 .

1
2
3
4
5
6
7
8
let notSure: any = 4;
notSure.ifItExists(); // okay, ifItExists might exist at runtime
notSure.toFixed(); // okay, toFixed exists (but the compiler doe
// sn't check)

let prettySure: Object = 4;
prettySure.toFixed(); // Error: Property 'toFixed' doesn't exist
// on type 'Object'.

空值void

void 类型像是与 any 类型相反,它表示没有任何类型

1
2
3
4
5
6
function warnUser(): void {
alert("This is my warning message");
}

// 声明一个 void 类型的变量没有什么大用,因为你只能为它赋予 undefined 和 null
let unusable: void = undefined;

null和undefined

默认情况下 null 和 undefined 是所有类型的子类型。 就是说你可以
把 null 和 undefined 赋值给 number 类型的变量。

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

注意:当你指定了 –strictNullChecks 标记, null 和 undefined 只能赋值给 void 和它们各自。

Object

object 表示非原始类型,也就是除 number , string , boolean , symbol , null 或 undefined 之外的类型。

强制类型转换

1
2
3
4
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
// as 语法
let strLength: number = (someValue as string).length;

可选属性

可选属性使用?标识

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Vue的ComponentOptions声明的一部分
export interface ComponentOptions<
V extends Vue,
Data=DefaultData<V>,
Methods=DefaultMethods<V>,
Computed=DefaultComputed,
PropsDef=PropsDefinition<DefaultProps>,
Props=DefaultProps> {
data?: Data;
props?: PropsDef;
propsData?: object;
computed?: Accessors<Computed>;
methods?: Methods;
watch?: Record<string, WatchOptionsWithHandler<any> | WatchHandler<any> | string>;
// ...
}

去除null和undefined类型断言使用!标识

有的时候TypeScript无法正确推导出类型时,需要手动去除nullundefined

1
2
3
4
5
export default class Login extends Vue {
@Mutation(UserMutationTypes.SET_USER_NAME)
private changeUserName!: (name: string) => void;

}

接口

在TypeScript里,接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约。

1
2
3
4
5
6
7
8
9
interface LabelledValue {
label: string;
}
function printLabel(labelledObj: LabelledValue) {
console.log(labelledObj.label);
}
// let myObj = {size: 10, label: "Size 10 Object"};
let myObj = {size: 10, label: "Size 10 Object"};
printLabel(myObj);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Animal {
move(distanceInMeters: number = 0) {
console.log(`Animal moved ${distanceInMeters}m.`);
}
}
class Dog extends Animal {
bark() {
console.log('Woof! Woof!');
}
}
const dog = new Dog();
dog.bark();
dog.move(10);
dog.bark();

函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Named function
function add(x, y) {
return x + y;
}
// Anonymous function
let myAdd = function(x, y) {
return x + y;
};

function tsAdd(x: number, y: number): number {
return x + y;
}

let tsMyAdd = function(x: number, y: number): number {
return x +y;
};

声明合并

注意每组接口里的声明顺序保持不变,但各组接口之间的顺序是后来的接口重载出
现在靠前位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
interface Cloner {
clone(animal: Animal): Animal;
}
interface Cloner {
clone(animal: Sheep): Sheep;
}
interface Cloner {
clone(animal: Dog): Dog;
clone(animal: Cat): Cat;
}

// 这三个接口合并成一个声明:
interface Cloner {
clone(animal: Dog): Dog;
clone(animal: Cat): Cat;
clone(animal: Sheep): Sheep;
clone(animal: Animal): Animal;
}

扩充全局或者模块作用域

不论是模块扩充还是全局声明扩充都不能向顶级作用域添加新的项目 - 它们只能为
已经存在的声明添加 “补丁”.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// map.ts
import { Observable } from "./observable";
// 扩充 "./observable"
declare module "./observable" {
// 使用接口合并扩充 'Observable' 类的定义
interface Observable<T> {
map<U>(proj: (el: T) => U): Observable<U>;
}
}
Observable.prototype.map = /*...*/;

// declare global 声明被增强:
declare global {
interface Array<T> {
mapToNumbers(): number[];
}
}
Array.prototype.mapToNumbers = function () { /* ... */ }

Vue对TypeScript的支持

Vue的TypeScript 支持

TypeScript识别Vue组件

1
2
3
4
5
// vue-shim.d.ts
declare module "*.vue" {
import Vue from "vue";
export default Vue;
}

TypeScript识别JSX

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 渲染函数 & JSX
Vue.component('anchored-heading', {
render: function (createElement) {
return createElement(
'h' + this.level, // 标签名称
this.$slots.default // 子元素数组
)
},
props: {
level: {
type: Number,
required: true
}
}
});

// shims-tsx.d.ts
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;
}
}
}

扩展Vue类和原型声明书写

1
2
3
4
5
6
7
8
9
// 例子:
declare module 'vue/types/vue' {
interface Vue {
$axios: AxiosInstance
}
interface VueConstructor {
$axios: AxiosInstance
}
}

Vue组件声明方式1

1
2
3
4
5
6
7
<template>
<div>
<div class="greeting">Hello {{name}}{{exclamationMarks}}</div>
<button @click="decrement">-</button>
<button @click="increment">+</button>
</div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

</script><script lang="ts">
import Vue from "vue";

export default Vue.extend({
props: ['name', 'initialEnthusiasm'],
data() {
return {
enthusiasm: this.initialEnthusiasm,
}
},
methods: {
increment() { this.enthusiasm++; },
decrement() {
if (this.enthusiasm > 1) {
this.enthusiasm--;
}
},
},
computed: {
exclamationMarks(): string {
return Array(this.enthusiasm + 1).join('!');
}
}
});
</script>
1
2
3
4
5
<style>
.greeting {
font-size: 20px;
}
</style>

Vue组件声明方式2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import Vue from 'vue'
import Component from 'vue-class-component'

// @Component 修饰符注明了此类为一个 Vue 组件
@Component({
// 所有的组件选项都可以放在这里
template: '<button @click="onClick">Click!</button>'
})
export default class MyComponent extends Vue {
// 初始数据可以直接声明为实例的属性
message: string = 'Hello!'

// 组件方法也可以直接声明为实例的方法
onClick (): void {
window.alert(this.message)
}
}

Vue组件使用注意事项

  • Vue组件或者ts文件中导入*.vue文件时,需要完整文件名

    1
    import Toolbar from '@/components/toolbar.vue';
  • <script lang="ts">标签必须带lang=“ts”

  • <script>导出的对象必须是Vue的实例

    1
    2
    3
    4
    5
    6
    7
    @Component
    export default class HelloDecorator extends Vue {}

    // 或者
    export default Vue.extend({
    // ComponentOptions
    })

vue-class-component注解的限制

  1. methods直接声明为类方法成员.

  2. 计算属性声明为类属性访问器.

  3. data声明为类的字段。

  4. datarender和所有的Vue生命周期钩子函数都能可以直接声明为类成员方法。但是你不能在Vue实例上直接调用。当你声明自定义方法时,应该避免声明周期钩子函数保留的名字

  5. 其它的options属性,使用传递给@Component装饰器函数。例如:components属性

data属性声明注意事项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Component
export default class Login extends Vue {
public name: string = 'login';
// 直接声明为类的字段
private userName: string = '';
private userPass: string = '';

@Mutation(UserMutationTypes.SET_USER_NAME)
private changeUserName!: (name: string) => void;

// Vue默认的data函数返回属性
data() {
return {
labelPosition: 'right',
};
}
}

如果data属性需要在classmethod中修改时,声明为类的字段。

如果data属性只在template中使用,可以使用data函数返回.

TypeScript官方声明文件仓库

引入第三方库(没有声明文件)

比如说我想引入async-validator,虽然已经在本地安装,但是typescript还是提示找不到模块。原因是typescript是从node_modules/@types目录下去找模块声明,有些库并没有提供typescript的声明文件,所以就需要自己去添加

解决办法:在src/types目前下建一个async-validation.d.ts文件,声明这个模块即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
declare module 'async-validator' {
export type AsyncValidator<T = object> = (
rule: RuleConfig,
value: any,
callback: ValidatorCallback,
source: T,
options: {
messages: object;
},
) => void;
}

// 看看*.vue模块怎么声明的
declare module '*.vue' {
import Vue from 'vue';
export default Vue;
}

前端涉及的知识

前端基础

  • JavaScript / HTML / CSS

  • ES6 的使用

Vue部分

  • Vue的基础知识

  • Vuex和Vue-Router使用

  • Vue工具链的相关知识

    vue-loader

    vue-cli

    vetur(vs code 插件)

    vue.config.js

    vue-property-decorator

    vuex-class

  • Vue生态链的组件库(例如: Element UI)

Node部分

  • Node.js模块知识
  • CommonJs规范(require, module, export)
  • npm基本使用
  • Node.js的package.json和模块知识

Webpack

  • 了解Webpack配置

  • Webpack插件

    css-loader, style-loader, sass-node…

TypeScript

  • TypeScript基本语法
  • TypeScript类型声明文件*.d.ts写法
  • tsconfig.json文件配置
  • 了解tslint.json

其它类库

  • Axios.js库的使用
  • Eslint

链接

npm模块管理器

npm 模块安装机制简介

vue-cli:

https://cli.vuejs.org/zh/

vue-router:

https://router.vuejs.org/zh/

vuex:

https://vuex.vuejs.org/zh/guide/

vue-loader:

https://vue-loader.vuejs.org/zh/spec.html

vue-class-component

https://github.com/vuejs/vue-class-component

vue-property-decorator

https://github.com/kaorun343/vue-property-decorator

vuex-class

https://github.com/ktsn/vuex-class/

Axios.js

https://github.com/axios/axios

Vue的TypeScript支持

https://cn.vuejs.org/v2/guide/typescript.html

语义化版本

https://semver.org/lang/zh-CN/

TypeScript+ Vue起步

https://github.com/Microsoft/TypeScript-Vue-Starter#typescript-vue-starter

https://github.com/SimonZhangITer/vue-typescript-dpapp-demo

vue + typescript 项目起手式

https://segmentfault.com/a/1190000011744210

https://segmentfault.com/a/1190000011878086

https://github.com/ws456999/vue-typescript-starter

https://www.zhihu.com/question/310485097