0%

比较官方的解释:接口是一系列抽象方法的声明,是一些方法特征的集合,这些方法都应该是抽象的,需要由具体的类去实现,然后第三方就可以通过这组抽象方法调用,让具体的类执行具体的方法。

我的理解:接口是一种约束行为,约束数据的结构。

简单使用

举个例,比如每个人都有名字和年龄,那么我把他定义成一个Person接口,如下:

1
2
3
4
interface Person {
name: string,
age: number
}

这个时候,我要使用这个Person接口了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 在这里会报错,因为Person接口里并没有sex这个属性,所以使用时不能有。
let person: Person = {
name: '小三',
age: 24,
sex: '男'
}

// 在这里也会报错,因为Person接口里缺少了age这个属性,所以使用时不能缺少它。
let person: Person = {
name: '小三',
}

// 应该是这样,接口里面有什么就用什么,不能多也不能少。
let person: Person = {
name: '小三',
age: 24
}

接口继承

那么问题来了,使用了Person后发现,Person还分了男人和女人,怎么办?

不慌,我们再写个男人和女人的接口,如下:

1
2
3
4
5
6
7
interface Man {
sex: '男'
}

interface Woman {
sex: '女'
}

写好了,这两个接口目前还是独立的,那么要怎么跟Person扯上关系呢?

分析一下,男人和女人都是人,那么自然是Person的一部分,如果Person是一级接口,那么ManWoman就是二级接口,我们可以使用继承(extends)将他们关联起来。即这个男人(女人)继承了人的属性。

1
2
3
4
5
6
7
interface Man extends Person {
sex: '男'
}

interface Woman extends Person {
sex: '女'
}

这个时候就可以正常使用了,如下:

1
2
3
4
5
6
7
8
9
10
11
let man: Man = {
name: '老王',
age: 30,
sex: '男'
}

let woman: Woman = {
name: '翠花',
age: 18,
sex: '女'
}

接口合并

再后来呀,我们发现人不仅仅有姓名和年龄,还有身高和体重,那么应该怎么补上去呢?

这个时候,可以有三种方法

  1. 第一种是直接在原来的Person接口上添加。
  2. 第三种是再添加一个Person接口,让两个接口合并在一起。
    现在说一下第二种(接口合并)。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    interface Person {
    height: number,
    weight: number
    }
    let person: Person = {
    name: '小三',
    age: 18,
    height: 178,
    weight: 60
    }

[props: string]

写着写着发现,人还有手长腿长腰围胸围一堆属性,如果一直使用接口合并,肯定让人受不了。好在有[props: string],可以帮助我们减少这个烦恼。

1
2
3
4
5
6
7
8
9
10
11
interface Person {
[props: string]: any
}

let person: Person = {
name: '小三',
age: 18,
height: 178,
weight: 60,
waistline: 50
}

至于为什么使用any类型,因为我们无法确定下一个发现缺少的属性是number还是string,如果清楚知道缺少属性类型的话,也可以使用联合类型(|)。

1
2
3
interface Person {
[props: string]: number | string
}

添加函数

接口不仅可以添加属性,还可以添加函数。比如每个人都要吃东西,那么要用什么方法吃,是要大口大口吃还是小口小口吃。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface Person {
eat: () => void
}

let person: Person = {
name: '小三',
age: 18,
height: 178,
weight: 60,
waistline: 50,
eat: () => {
console.log('我要大口大口吃东西')
}
}

完整代码

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// 简单使用
interface Person {
name: string,
age: number
}

// 接口合并
interface Person {
height: number,
weight: number
}

// [props: string]
interface Person {
[props: string]: any
}

// 接口函数
interface Person {
eat: () => void
}

// 接口继承
interface Man extends Person {
sex: '男'
}

interface Woman extends Person {
sex: '女'
}

let person: Person = {
name: '小三',
age: 18,
height: 178,
weight: 60,
waistline: 50,
eat: () => {
console.log('我要大口大口吃东西')
}
}

let man: Person = {
name: '小三',
age: 18,
height: 178,
weight: 60,
waistline: 50,
eat: () => {
console.log('我是男人,我要大口大口吃东西')
}
}

let woman: Person = {
name: '翠花',
age: 18,
height: 178,
weight: 60,
waistline: 50,
eat: () => {
console.log('我是女人,我要小口小口吃东西')
}
}

对象是包含一组键值对的实例。值可以是标量、函数、数组、对象等。

基本用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// ts
let obj = {
a: 'obj', // 标量
b: () => {}, // 函数
c: [1, 2, ,3], // 数组
d: { key01: 'a', key02: 'b' } // 对象
}
console.log(obj.a) // obj
console.log(obj.b()) // 我是函数
console.log(obj.c) // [1, 2, 3]
console.log(obj.d) // { key01: 'a', key02: 'b' }

// 编译成js后
var obj = {
a: 'obj', // 标量
b: function () { return '我是函数'; }, // 函数
c: [1, 2, 3], // 数组
d: { key01: 'a', key02: 'b' } // 对象
};
console.log(obj.a);
console.log(obj.b());
console.log(obj.c);
console.log(obj.d);

定义对象类型

匿名对象变量

匿名对象是在定义变量时直接使用花括号{},来定义一个对象类型。

1
const person: { name: string, age: number } = { name: '老王', age: 30 }

匿名对象函数

1
2
3
4
const personFn = (person: { name: string, age: number }) => {
console.log(`姓名:${person.name},年龄:${person.age}`)
}
personFn({ name: '老王', age: 30 }) // 姓名:老王,年龄:30

接口类型

从上面的例子可以看出,每次都这样写对象类型会很麻烦,如果遇到使用同一种对象类型的数据时,需要多次写入,增添了工作量。那么这个时候就可以使用接口来定义对象类型,使代码更加可读、易于维护。

1
2
3
4
5
6
7
8
9
interface Person {
name: string,
age?: number
}
const personFn = (person: Person) => {
if (person.age !== undefined) console.log(`姓名:${person.name},年龄:${person.age}`)
else console.log(`姓名:${person.name}`)
}
personFn({ name: '老王' }) // 姓名:老王

类型别名

类型别名用来给一个类型起个新名字,使用 type 创建类型别名,类型别名不仅可以用来表示基本类型,还可以用来表示对象类型、联合类型、元组和交集。

1
2
3
4
5
6
7
8
9
type Person = {
name: string,
age? : number
}
const personFn = (person: Person) => {
if (person.age !== undefined) console.log(`姓名:${person.name},年龄:${person.age}`)
else console.log(`姓名:${person.name}`)
}
personFn({ name: '老王' }) // 姓名:老王

属性修饰符

可选属性

对象类型还可以指定它们的部分或全部属性是可选的。为此,请在属性名称后添加 ?

比如我们在填写个人信息时,年龄一般会选择不填写。

1
2
3
4
const personFn = (person: { name: string, age?: number }) => {
console.log(`姓名:${person.name},年龄:${person.age}`)
}
personFn({ name: '老王' }) // 姓名:老王,年龄:undefined

从上面例子的结果可以看出,如果访问了一个不存在的属性age并不会运行错误,而是会返回undefined,所以当我们在读取可选属性中的数据时,尽量在使用这个属性之前检查一下,如下:

1
2
3
4
5
const personFn = (person: { name: string, age?: number }) => {
if (person.age !== undefined) console.log(`姓名:${person.name},年龄:${person.age}`)
else console.log(`姓名:${person.name}`)
}
personFn({ name: '老王' }) // 姓名:老王

只读属性

我们使用 readonly 关键字来声明只读属性,属性的值一旦被赋值就不能再次修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
type Person = {
name: string,
readonly age: number,
readonly sex: string
}
const person: Person = {
name: '老王',
age: 30,
sex: '男'
}
person.name = '翠花'
person.age = 18 // 报错,只读属性一旦被赋值就不能再次修改
person.sex = '女' // 报错,只读属性一旦被赋值就不能再次修改

索引签名

有时你无法提前知道类型属性的所有名称,在这种情况下,你可以使用索引签名来描述可能值的类型。

  1. 基本用法

    1
    2
    3
    4
    5
    6
    7
    8
    interface personInfo {
    [key: string]: string | number
    }
    const person_info: personInfo = {
    name: '老王',
    sex: '男'
    }
    console.log(person_info) // { name: '老王', sex: '男' }
  2. 只读索引签名

    1
    2
    3
    4
    5
    6
    7
    8
    9
    interface personInfo {
    readonly [key: string]: string | number
    }
    const person_info: personInfo = {
    name: '老王',
    age: 30,
    sex: '男'
    }
    person_info.age = 18 // 报错
  3. 联合类型索引签名

    1
    2
    3
    4
    5
    6
    7
    8
    9
    interface personInfo {
    [key: string]: string | number
    }
    const person_info: personInfo = {
    name: '老王',
    age: 30,
    sex: '男'
    }
    console.log(person_info) // { name: '老王', age: 30, sex: '男' }

泛型对象类型

在我们还不知道是什么样子的对象类型时,可以使用 泛型 先占位,等知道后再补充。

1
2
3
4
5
6
7
8
9
10
11
interface Person<T> {
phone: T
}
const person01: Person<number> = {
phone: 13222222222
}
const person02: Person<string> = {
phone: '020-1111111'
}
console.log(person01) // { phone: 13222222222 }
console.log(person02) // { phone: '020-1111111' }

联合类型

联合类型使用 | 分隔每个类型,表示多种类型的联合。

  1. 变量使用联合类型
    比较常见的是电话号码,电话号码可以是手机号码,也可以是座机。其中手机号码是纯数字,座机是字符串,这个时候就可以使用联合类型了。

    1
    2
    3
    let phone: number | string = 13222222222
    // 或者
    let phone: number | string = '020-123456'
  2. 函数使用联合类型

    1
    2
    3
    4
    5
    6
    function savePhone(phone: number | string) {
    if (typeof phone === 'number') console.log('保存了手机号码')
    else if (typeof phone === 'string') console.log('保存了座机')
    }
    savePhone(13222222222) // 保存了手机号码
    savePhone('020-123456') // 保存了座机

交叉类型

交叉类型使用 & 分隔每个类型,是将多个类型合并为一个类型。

比如上一章接口说到的男人女人要怎么和人关联起来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface Person {
name: string,
age: number
}
interface Sex {
sex: '男' | '女'
}
const sexFn = (info: Person & Sex) => {
console.log(info.name)
console.log(info.age)
console.log(info.sex)
}
sexFn({ name: '老王', age: 30, sex: '男' })
sexFn({ name: '翠花', age: 18, sex: '女' })

类型断言

类型断言Type Assertion可以收到指定一个类型,允许我们重写TypeScript数据的类型。

  1. 语法01:value as type

    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
    29
    interface Cat {
    run: () => void
    }
    interface Fish {
    swim: () => void
    }

    // 以下方法会报错,虽然猫和鱼都是动物,但是鱼并不会奔跑,在动物调用时时没有run这个方法的。
    // const isCat = (animal: Cat | Fish): boolean => {
    // if (typeof animal.run === 'function') return true
    // else return false
    // }

    // 所以应该在动物调用时给这个动物指定一个run的方法,如下:
    const isCat = (animal: Cat | Fish): boolean => {
    if (typeof (animal as Cat).run === 'function') return true
    else return false
    }

    const cat: Cat = {
    run() {}
    }
    console.log(isCat(cat)) // true

    const fish: Fish = {
    swim() {}
    }
    console.log(isCat(fish)) // false

  2. 语法02:<type>value

    1
    2
    3
    4
    const isCat = (animal: Cat | Fish): boolean => {
    if (typeof (<Cat>animal).run === 'function') return true
    else return false
    }

as const

const 声明的变量如果是基本类型,那么不允许改变,如果是引用类型,那么只要不改变引用的地址就是可以的。
举个例:

1
2
3
4
const a = 1
a = 2 // 报错
const b = [2, 3]
b.push(4) // 通过

那么如果使用as const断言会怎么样呢

1
2
3
4
5
let c = 5 as const
c = 6 // 报错

const d = [7, 8] as const
d.push(9) // 报错,因为此时变量d已经被断言字面量为[7, 8],数据无法再做任何修改

类型断言是不具影响力的

从下面的例子可以看出,虽然参数f通过了编译,但是并没有影响到最终的结果。因为在编译过程中,类型断言已经被删除了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ts
function eFn(f: any): string {
return f as string
}

const g = eFn(true)
console.log(g) // true

// 编译成js后
function eFn(f) {
return f;
}
var g = eFn(true);
console.log(g); // true

注意

虽然使用类型断言可以指定一个类型,但是这样做只是避开了typeScript编译器的检测,在运行时还是有可能报错,所以在开发时应该避免滥用类型断言。

基础类型:布尔类型,数字类型,字符串类型,数组类型,null,undefined,never类型,空值类型,任意类型,unknown类型

布尔类型(boolean)

表示逻辑值:true 和 false

1
2
3
4
5
6
7
// ts
let a: boolean = true
let b: boolean = false

// 编译成js后
var a = true;
var b = false;

数字类型(number)

数字类型支持 二进制 八进制 十进制 十六进制 NaN Infinity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// ts
let num_2: number = 11111101000 // 二进制
let num_8: number = 3750 // 八进制
let num_10: number = 2024 // 十进制
let num_16: number = 7e8 // 十六进制
let num_NaN: number = NaN // NaN
let num_Infinity: number = Infinity // 无穷大

// 编译成js后
var num_2 = 11111101000;
var num_8 = 3750;
var num_10 = 2024;
var num_16 = 7e8;
var num_NaN = NaN;
var num_Infinity = Infinity;

字符串类型(string)

一个字符系列,使用单引号(’)或双引号(”)来表示字符串类型。反引号(`)来定义多行文本和内嵌表达式。

1
2
3
4
5
6
7
8
9
// ts
let str_01: string = '我是字符串'
let str_02: string = `你好,${str_01}`
console.log(str_02) // 你好,我是字符串

// 编译成js后
var str_01 = '我是字符串';
var str_02 = "\u4F60\u597D\uFF0C".concat(str_01);
console.log(str_02);

数组类型

声明变量为数组。

使用方法01,类型[]

1
2
3
4
5
6
7
8
// ts
let arr_01:number[] = [1, 2, 3]
let arr_02:string[] = ['a', 'b', 'c']
// let arr_03:boolean[] = [false, true, 1, 'a'] // 这样会报错,因为布尔类型数组中出现了数字和字符串,这是不允许的

// 编译成js后
var arr_01 = [1, 2, 3];
var arr_02 = ['a', 'b', 'c'];

使用方法02,数组<类型>

1
2
3
4
5
6
7
// ts
let arr_04: Array<number> = [1, 2, 3]
let arr_05: Array<string> = ['a', 'b', 'c']

// 编译成js后
var arr_04 = [1, 2, 3];
var arr_05 = ['a', 'b', 'c'];

使用方法03,多维数组,类型[][]

1
2
3
4
5
// ts
let arr_06: number[][] = [[1, 2], [3, 4]]

// 编译成js后
var arr_06 = [[1, 2], [3, 4]];

使用方法04,使用接口表示数组

1
2
3
4
5
6
7
8
9
// ts
interface interfaceArr {
[key: number]: string
}
// 索引key的类型为数字类型时才可使用
let arr_07: interfaceArr = ['a', 'b', 'c']

// 编译成js后
var arr_07 = ['a', 'b', 'c'];

使用方法05,any数组

数组中的值可以是任意类型

1
2
3
4
5
// ts
let arr_08: any[] = [1, 'a', true, { b: '2', c: 3 }]

// 编译成js后
var arr_08 = [1, 'a', true, { b: '2', c: 3 }];

null类型

表示对象值缺失。

1
2
3
4
5
// ts
let null_01: null = null

// 编译成js后
var null_01 = null;

undefined类型

用于初始化变量为一个未定义的值

1
2
3
4
5
// ts
let undefined_01: undefined = undefined

// 编译成js后
var undefined_01 = undefined;

never类型

never 是其它类型(包括 null 和 undefined)的子类型,代表从不会出现的值。

1
let never_01: number & string = 1     // let never_01: never

以上代码会报错,因为numberstring是无法同时存的,所以never_01是never类型。

空值类型(void)

用于标识方法返回值的类型,表示该方法没有返回值

1
2
3
4
5
6
7
8
9
// ts
function voidFn(): void {
console.log('我是空值函数')
}

// 编译成js后
function voidFn() {
console.log('我是空值函数');
}

任意类型(any)

声明为 any 的变量可以赋予任意类型的值,没有强制限定哪种类型。

1
2
3
4
5
6
7
8
9
10
11
// ts
let any_01: any = 123
any_01 = 'abc'
any_01 = true
any_01 = [1, 2, 3]

// 编译成js后
var any_01 = 123;
any_01 = 'abc';
any_01 = true;
any_01 = [1, 2, 3];

注意:如果使用any就失去了ts类型检测的作用了,建议减少使用。

unknown类型

一种顶级类型,表示一个未知的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// ts
let unknown_01: unknown
unknown_01 = 1
unknown_01 = 'a'
unknown_01 = true
unknown_01 = [1, 'a', true]
unknown_01 = { a: 1, b: 'c' }
unknown_01 = null
unknown_01 = undefined
unknown_01 = Symbol('type')

// 编译成js后
var unknown_01;
unknown_01 = 1;
unknown_01 = 'a';
unknown_01 = true;
unknown_01 = [1, 'a', true];
unknown_01 = { a: 1, b: 'c' };
unknown_01 = null;
unknown_01 = undefined;
unknown_01 = Symbol('type');

any类型 和 unknown类型 的区别

  1. unknow类型比any类型更严格,更安全。
  2. any类型表示任意类型,没有ts类型检查。unkonw类型是暂时未知类型,有ts类型检查。
  3. any是最宽的类型。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // any,可以正常调用属性和方法,哪怕是未知的属性和方法也可以调用
    let any_02: any = { a: 1, b: (): number => 2 }
    any_02.a
    any_02.b()
    any_02.e
    any_02.f()

    // unknown,无法调用属性和方法
    let unknown_02: unknown = { c: 3, d: (): number => 4 }
    unknown_02.c
    unknown_02.d()

    // any是最宽的类型
    type any_03 = unknown & any // any

介绍

什么是TypeScript

ts是js的超集,ts是一种基于js构建的强类型编程语言,可为你提供任何规模的更好工具,ts可以编译成纯js。

什么是超集

超集是包含一个较小集合的所有元素的集合,比如A的集合是[1,2,3],B的集合是[1],那么A就是B的超集。

安装

使用npm时需要有nodejs环境,可以前往node官网下载安装。

1
2
// 在终端执行以下代码安装typescript
npm install typescript -g

安装完成后可以在终端执行 tsc -v 查看版本号。

简单使用

新建一个文件夹,并在该文件夹中新建 index.ts 文件,在文件中输入

1
2
let a: number = 1
let b: boolean = false

输入完成后打开位于该文件夹位置的终端,并执行 tsc index.ts 即可在当前文件夹下生成index.js文件。

1
2
var a = 1;
var b = false;

如果觉得每次更新 index.ts 文件都需要手动编译太麻烦了, 可以在终端输入 tsc -w,这样每次更新后都会自动编译了。

前言

到目前为止,vue-cli 项目基本已经完成了,那么现在我们只剩最后一步了,那就是怎么将项目进行打包并放进服务器中。

首先

在 vue-cli 项目根目录中打开 cmd 命令行工具,执行命令:npm run build后命令行工具会开始处理。

然后

当执行完成后,我们可以看到项目根目录中多了一个名为dist的文件夹,打开一看,可以看到很多我们熟悉的东西,比如index.htmlcssjs等,没错了,这个就是打包后生成的文件了,我们只需要将这一份代码放进服务器中即可。

注意

在执行打包命令时,有时会报一些错误,比如:

1
To install it, you can run: npm install --save vue-router

这是因为vue-router没有找到,需要重新安装,我们在命令行工具执行npm install --save vue-router即可。安装完成后再次尝试进行打包。

如果还是失败,我们可以尝试将node_modules文件夹删除掉,然后再次执行npm install安装依赖。

一般情况下,项目能正常启动的话,打包时是不会报错的,如果报错了,按照报错提示进行修改即可。

介绍

Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。

特征

  1. 从浏览器中创建 XMLHttpRequests
  2. 从 node.js 创建 http 请求
  3. 支持 Promise API
  4. 拦截请求和响应
  5. 转换请求数据和响应数据
  6. 取消请求
  7. 自动转换 JSON 数据
  8. 客户端支持防御 XSRF

安装

vue-cli项目根目录中打开cmd窗口,输入命令:

1
npm install axios

简单使用

模版代码:

1
2
3
axios.get(url)
.then((res) => { })
.catch((err) => { })

打开v-news.vue文件,完整代码如下:

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
29
30
<template>
<div class="v-news content">
<ul>
<li v-for="item in lists" :key="item.id">
<router-link :to="{ name: 'detail', params: { id: item.time } }">{{ item.title }}</router-link>
</li>
</ul>
</div>
</template>

<script>
// 引入 axios
import axios from 'axios';

export default {
name: 'v-news',
desc: '新闻列表',
data() {
return {
lists: []
}
},
created() {
axios.get('/news/lists')
.then((res) => {
this.lists = res.data;
})
}
}
</script>

使用完成,在浏览器中运行查看效果,可以看到数据正常显示。

总结

axios的请求方式不仅仅有get,还有其他的,比如postdelete等,在本章使用了get,如果大家想了解更多,可以在网上查看axios使用文档。

前后分离的开发流程

  1. 项目需求分析
  2. 前端和后台商量所需接口数据
  3. 前端和后台各自开发
  4. 前端对接接口,此时后台很大可能是还没有开发完接口,所以前端需要按照第二步所商量的结果模拟数据
  5. 后台开发接口完成,开始联调
  6. 如果调试没有任何问题,将项目提交至测试环境交给测试人员进行测试
  7. 根据测试文档进行让人头疼的 bug 修改
  8. 测试通过,项目正式发布

什么是 mock.js

生成随机数据,拦截 Ajax 请求

在第一节中提到了需要模拟一些假数据,那么该怎么模拟呢?很多人可能会说直接在页面书写点数据就好了。这样做也是可以的,但是太麻烦了,不规范,虽然前期能够快速完成开发,但是后期对接完接口后需要对这些假数据进行删除是一件令人头疼的事。

简单使用

安装

首先打开vue-cli项目,然后在项目根目录下打开cmd窗口,输入指令npm install mockjs进行安装

模拟

src文件夹下新建名称为mock.js的文件,用于生成模拟数据。完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 引入mockjs
import Mock from 'mockjs';
// Mock.Random 是一个工具类,用于生成各种随机数据
const Random = Mock.Random;
let data = [];
for(let i = 0; i < 10; i ++) {
data.push({
title: Random.title(),
time: Random.datetime()
})
}

// 模拟新闻列表
Mock.mock('/news/lists', 'get', data);

引用

打开main.js文件并进行引用,完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import Vue from 'vue'
import App from './App.vue'

// 引入我们配置好的路由
import router from './router';

Vue.config.productionTip = false

// 引入样式
import './assets/css/app.css';

// 引入 mock 模拟数据
require('./mock');

new Vue({
router,
render: h => h(App),
}).$mount('#app')

mock.js 的优点

通过上面的简单使用,我们能直观体会到的优点有:

  1. 让前端工程师独立于后端进行开发
  2. 通过随机数据,模拟各种场景
  3. 符合直觉的接口
  4. 支持生成随机的文本、数字、布尔值、日期、邮箱、链接、图片、颜色等

总结

其实在敲代码时,我们可以发现mock.js其实是一个很好玩的东西,完全没有想象中的那么麻烦,当后台提供了接口后,我们只需要把在main.js的引用去掉就可以开始和后台进行联调了,简直不要太方便了。
那么,我们应该如何在v-news.vue文件中使用ajax请求模拟的新闻列表数据呢?请看下一章。

一,前言

我们经常会一边开发项目,一边调整项目。比如现在我们把所有的组件都放在了components文件夹下面,如果后期我们需要修改组件的路径,那么问题就来了,比如我需要修改v-about.vue文件的路径,那么我们就需要修改v-header.vuerouter -> index.js文件对应的路径。如果只是修改一个文件路径,问题倒不大,可是如果我们需要修改十几个或更多组件的路径,这就很头疼了。那么怎么解决这个问题呢?你可以在创建 Router 实例的时候,在 routes 配置中给某个路由设置名称。

模版代码:

1
2
3
4
5
6
7
8
9
const router = new VueRouter({
routes: [
{
path: '/user/:userId',
name: 'user',
component: User
}
]
})

其实就是在路由配置时,给该组件加了一个name

二,开始尝试

我们现在components文件夹下新建一个名称为content的文件夹,然后将v-about.vuev-index.vuev-news.vuev-news-detail.vue等四个文件放到content文件夹中。

当修改了路径后,大家可以看到cmd窗口开始报错,vue-cli项目在浏览器中打开是一片空白,什么都没有。

目前项目目录结构如下:

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
.
|-- node_modules
|-- public
| |-- favicon.ico
| |-- index.html
|-- src
| |-- assets
| | |-- css
| | | |-- app.css
| |-- components
| | | |-- content
| | | | |-- v-about.vue
| | | | |-- v-index.vue
| | | | |-- v-news-detail.vue
| | | | |-- v-news.vue
| | |-- HelloWorld.vue
| | |-- v-header.vue
| | |-- v-success.vue
| |-- router
| | |-- index.js
| |-- App.vue
| |-- main.js
|-- .gitignore
|-- babel.config.js
|-- package-lock.json
|-- package.json
|-- README.md
.

修改路由配置

打开router -> index.js文件添加name

修改后的完整代码如下:

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
29
30
31
32
33
34
35
36
37
38
39
// 引入 Vue
import Vue from 'vue';
// 引入 vue-router
import VueRouter from 'vue-router';
// 安装使用 vue-router
Vue.use(VueRouter);
// 引入首页
import vIndex from '../components/content/v-index.vue';
// 开始使用 vue-router
let routes = new VueRouter({
routes: [
{
path: '/',
name: 'index',
component: vIndex
},
{
path: '/about',
name: 'about',
component: () => import(/* webpackChunkName: "about" */ '../components/content/v-about.vue')
},
{
path: '/success',
component: () => import(/* webpackChunkName: "success" */ '../components/v-success.vue')
},
{
path: '/news',
name: 'news',
component: () => import(/* webpackChunkName: "news" */ '../components/content/v-news.vue')
},
{
path: '/news/:id',
name: 'detail',
component: () => import(/* webpackChunkName: "news" */ '../components/content/v-news-detail.vue')
},
]
});
// 提供接口给外面使用
export default routes;

接着打开v-header.vue文件修改router-link中的to

修改后的完整代码如下:

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<template>
<div class="v-header">
<ul class="menus">
<li
class="menu"
v-for="item in menus"
:key="item.id"
>
<router-link :to="{ name: item.path }">{{ item.name }}</router-link>
</li>
</ul>
</div>
</template>

<script>
export default {
name: 'v-header',
desc: '头部信息',
data() {
return {
menus: [
{ id: 1, name: '首页', path: 'index' },
{ id: 2, name: '新闻', path: 'news' },
{ id: 3, name: '关于', path: 'about' },
]
}
}
}
</script>

<style scoped>
.v-header {
width: 100%;
height: 70px;
background-color: #fff;
box-shadow: 3px 3px 3px #ddd;
color: #333;
}
.menus {
list-style: none;
padding: 0 20px;
overflow: hidden;
}
.menu {
float: left;
padding: 0 20px;
height: 70px;
line-height: 80px;
transition: .3s;
}
.menu:hover {
cursor: pointer;
color: #0051ff;
background-color: #efefef;
}
</style>

最后再打开v-news.vue文件修改router-link中的to

修改后的完整代码如下:

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
<template>
<div class="v-news content">
<ul>
<li v-for="item in lists" :key="item.id">
<router-link :to="{ name: 'detail', params: { id: item.id } }">{{ item.title }}</router-link>
</li>
</ul>
</div>
</template>

<script>
export default {
name: 'v-news',
desc: '新闻列表',
data() {
return {
lists: [
{ id: 1, title: '新闻标题 1' },
{ id: 2, title: '新闻标题 2' },
{ id: 3, title: '新闻标题 3' },
{ id: 4, title: '新闻标题 4' },
{ id: 5, title: '新闻标题 5' },
{ id: 6, title: '新闻标题 6' },
]
}
}
}
</script>

到目前位置,代码已经修改完成了,可以在浏览器中打开看看效果。如果再次发生组件的路径改变,那么我们只需要直接修改router -> index.js文件即可。

三,总结

希望大家能敲一遍代码,体验修改的繁琐和添加命名路由的方便,这样在以后开发时,就会考虑到底要使用path还是name。如果大家需要vue-cli项目源码,可以私信我哈。

前言

不知道大家在写组件的时候,有没有发现一个特别麻烦的事情,就是我们每次开始写一个新的组件的,都会有相似的样式出现,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* v-about.vue */
.v-about {
padding: 20px 30px;
}

/* v-index.vue */
.v-index {
padding: 20px 30px;
}

/* v-news-detail.vue */
.v-news-detail {
padding: 20px 40px;
}

/* v-news.vue */
.v-news {
padding: 20px 50px;
}

这些样式都是组件中的内边距,那么,我们是否可以对这些样式统一写在一个文件中,并使用统一的样式padding: 20px 30px;。这样写的话,在后期维护时不需要每个组件都打开进行修改,只需要修改一个文件的代码即可。

开始编写

在前面的项目目录介绍中,我们知道assets文件夹是公共文件存放处,既然如此,那么公共样式可以考虑放在该文件夹下。

  1. 首先在assets文件夹下再新建一个css文件夹,这样做的好处是,我们后期可能在开发时也会在assets文件夹下存放一些公共的js函数,提前划分好文件夹可以让项目目录结构变得更加清晰,避免后期花费大量时间查找文件。
  2. 然后在css文件夹下新建一个app.css文件,用于存放公共样式。

app.css完整代码:

1
2
3
.content {
padding: 20px 30px;
}

公共样式的引用

既然是公共样式,那么在一开始加载项目时就应该一起加载才行。

打开main.js文件,因为这个文件是程序入口文件,加载各种公共组件,所以可以在这里添加。
完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import Vue from 'vue'
import App from './App.vue'

// 引入我们配置好的路由
import router from './router';

Vue.config.productionTip = false

// 引入样式
import './assets/css/app.css';

new Vue({
router,
render: h => h(App),
}).$mount('#app')

对相关的组件进行修改

我们依次打开文件v-about.vuev-index.vuev-news-detail.vuev-news.vue对第一节中提到的样式进行删除,然后对应的元素添加content

比如v-index.vue的完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<div class="v-index content">
<h1>我是首页</h1>
</div>
</template>

<script>
export default {
name: 'v-index',
desc: '首页'
}
</script>

到了这里,公共样式已经修改完成,我们可以在浏览器中看看效果如何。

目前项目目录结构

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
.
|-- node_modules
|-- public
| |-- favicon.ico
| |-- index.html
|-- src
| |-- assets
| | |-- css
| | | |-- app.css
| |-- components
| | |-- HelloWorld.vue
| | |-- v-about.vue
| | |-- v-header.vue
| | |-- v-index.vue
| | |-- v-news-detail.vue
| | |-- v-news.vue
| | |-- v-success.vue
| |-- router
| | |-- index.js
| |-- App.vue
| |-- main.js
|-- .gitignore
|-- babel.config.js
|-- package-lock.json
|-- package.json
|-- README.md
.

总结

有些小伙伴可能会问写公共样式为什么不用 scss 或者 less,它们不香吗?,答案肯定是香,而且使用它们绝对能提高我们的工作效率,不过在vue-cli项目中,我们还是先使用一些比较传统的方法来写公共样式。如果有想尝试使用lessscss的小伙伴,可以尝试写一下,我相信你会喜欢上它们的。