Vue.js是什么?

vue是现在前端三大框架之一,使用起来简单轻便,上手快速。

Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。

简单来说,vue的核心就是:

  1. 响应式数据绑定:所谓响应式,就是当数据发生改变,视图可以自动更新,可以不用关心dom操作,而专心数据操作
  2. 视图组件可嵌套组合:将视图按照功能分成若干个组件,由组件嵌套组合整个应用

快速创建Vue工程

安装vue

  1. 直接通过<script>引用vue

    1
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  2. npm安装

    1
    npm install vue

    ps:前提要先安装好node.js

    由于npm速度比较慢,可以通过淘宝镜像源安装。

    安装淘宝镜像源:

    1
    npm install -g cnpm --registry=https://registry.npm.taobao.org

    安装成功后,就可以是用cnpm来安装vue了,速度会快很多。

    1
    cnpm install vue

安装vue命令行工具

Vue 提供了一个官方的 CLI,为单页面应用 (SPA) 快速搭建繁杂的脚手架。

1
2
3
4
# 使用npm
npm install -g @vue/cli
# 使用淘宝镜像源
cnpm install -g @vue/cli

创建vue工程

所有准备工作就绪后,vue提供了一条命令来快速创建工程:

1
vue create <app-name>

ps:该命令只有vue-cli3.0以上才支持

目录结构

目录/文件 说明
build 项目构建(webpack)相关代码
config 配置目录,包括端口号等。
node_modules npm 加载的项目依赖模块(类似python中的site-packages)
src 这里是我们要开发的目录,基本上要做的事情都在这个目录里。里面包含了几个目录及文件:
assets: 放置一些图片,如logo等。components: 目录里面放了一个组件文件,可以不用。
App.vue: 根组件,我们也可以直接将组件写这里,而不使用 components 目录。
main.js: 项目的核心文件。
static 静态资源目录,如图片、字体等。
test 初始测试目录,可删除

启动项目

1
npm run serve

通过在根组件App.vue中引用子组件HelloWorld.vue来定制一个页面:

App.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
31
<template>
<div id="app">
<img src="./assets/logo.png">
<!--<显示HelloWorld组件内容/>-->
<HelloWord></HelloWord>
</div>
</template>

<script>
// 导入HelloWorld组件
import HelloWord from "./components/HelloWorld"

export default {
name: 'App',
components:{
HelloWord //声明组件
}
}
</script>

<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>

HelloWorld.vue代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<div class="hello">
<h1>欢迎来到我的世界</h1>
</div>
</template>

<script>
export default {
name: 'HelloWorld'
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>

</style>

启动项目后,即可看到我们定制的页面:

基础知识

模板语法-插值

插值

数据绑定最常见的形式就是使用 {{...}}(双大括号)的文本插值,修改上面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<div class="hello">
<!--从外部插入变量值-->
<h1>欢迎来到我的世界,{{msg}}</h1>
</div>
</template>

<script>
export default {
name: 'HelloWorld',
data () {
return {
msg: '古一' //指定一个变量
}
}
}
</script>

<script>标签内中指明数据msg的值,然后在<template>中插入该变量(其实就是类似于python中的变量引用)。

模板语法-指令

什么是指令?下面是官方的解释:

指令 (Directives) 是带有 v- 前缀的特殊 attribute。指令 attribute 的值预期是单个 JavaScript 表达式 。指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM。

v-bind

通过v-bind做属性绑定,可以使标签的属性值动态变化。

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<div class="hello">
<h1>欢迎来到我的世界,{{msg}}</h1>
<!--给title属性绑定value的值-->
<span v-bind:title="value">鼠标放这里别动</span>
</div>
</template>

<script>
export default {
name: 'HelloWorld',
data () {
return {
msg: '古一',
value: '哈哈哈哈'
}
}
}
</script>

data中定义一个变量value,然后通过v-bind<span>标签的title属性绑定value,这样title属性的值就可以实现动态变化。

v-bind绑定是单向的,即前端改变值不会影响后端model

v-bind也可以缩写为:,即等价于:

1
<span :title="value">鼠标放这里别动</span>
v-model

v-model可以在控件元素上创建双向数据绑定,根据表单上的值,自动更新绑定的元素的值,还是直接看例子:

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
<template>
<div class="login">
<form method="post" action="">
<label for="username">用户名:</label>
<!--未绑定用户名输入-->
<input type="text" id="username" placeholder="请输入用户名">
<br>
<label for="password">密码:</label>
<!--绑定了密码输入-->
<input type="text" id="password" placeholder="请输入密码" v-model="password">
</form>
<p>输入的用户名是:{{username}}</p>
<p>输入的用户名是:{{password}}</p>
</div>
</template>

<script>
export default {
name: 'HelloWorld',
data () {
return {
username: 'admin',
password: '123'
}
}
}
</script>

这里定义了一个登录输入框,为了方便看到区别,将密码输入框做了v-model数据绑定,而登录输入框没有,看下效果:

model中实际是指定了usernamepassword的值分别为admin123,但是前端只显示了密码,为什么?因为我们对密码输入框进行了数据的双向绑定,即可以将model中的数据传给绑定的属性,同时,也可以将前端输入的内容交给model处理

在前端输入,后端model同时更新

注:通常v-model用在 input、select、textarea、checkbox、radio控件上。

v-on

v-on指令用来监听和响应dom事件。

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
<div class="login">
<!--给点击事件绑定一个函数-->
<span v-on:click="changeWeather">今天是{{weather}}</span>
</div>
</template>

<script>
export default {
name: 'HelloWorld',
data () {
return {
weather: '晴天' //天气默认值
}
},
methods:{
changeWeather(){ // 定义一个方法修改天气
return this.weather='阴天'
}
}
}
</script>

<span>标签默认显示“今天是晴天”,当点击该标签后,会自动调用方法changeWeather,来修改weather值为“阴天”,如图:

此外,v-on可以缩写为@,等价于:

1
<span @click="changeWeather">今天是{{weather}}</span>
v-if

通过v-ifv-else-ifv-else可以完成条件判断,

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>

<form method="post" action="">
<label for="age">年龄:</label>
<input type="text" id="age" placeholder="请输入年龄" v-model="age">
</form>
<p v-if="age>25">年纪太大,回去养老吧</p>
<p v-else-if="age<15">年纪太小,回去玩泥巴</p>
<p v-else>可以,来打职业吧</p>
</div>
</template>

<script>
export default {
name: 'HelloWorld',
data () {
return {
age: 17
}
}
}
</script>

效果:

v-show

v-showv-if相似,vue会根据表达式值的真假来渲染元素,

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<div class="login">
<p v-show="is_show">哈哈哈</p>
<p v-if="is_show">嘿嘿嘿</p>
</div>
</template>

<script>
export default {
name: 'HelloWorld',
data () {
return {
is_show:true
}
}
}
</script>

is_showtrue时,效果:

而当is_showfalse时,两者均不会渲染标签内容,但是有一个区别:

可以看到,v-show每次不会重新进行dom的创建和删除,只是切换了元素的display属性,而v-if每次都会重新创建和删除元素。

v-for

很明显,v-for可以帮我们实现循环,

以表格填充为例:

如果使用最朴素的写法,会写很多<tr>标签,类似这样:

而通过v-for我们可以大大简化代码:

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
<template>
<table>
<tr>
<th v-for="(project,key) in project_header" v-bind:key="key">
{{project}}
</th>
</tr>
<tr v-for="(item,key) in projects" v-bind:key="key">
<td>{{item.p_name}}</td>
<td>{{item.p_leader}}</td>
<td>{{item.p_level}}</td>
</tr>
</table>
</template>

<script>
export default {
name: "ProjectsList",
data() {
return {
project_header: ["项目名称", "项目负责人", "项目级别"],
projects: [
{p_name: "阿里云项目", p_leader: "马云", p_level: "5级"},
{p_name: "腾讯云项目", p_leader: "马化腾", p_level: "5级"},
{p_name: "百度云项目", p_leader: "李彦宏", p_level: "3级"}],
username: 'xm'
}
}
</script>

<style scoped>
table{
margin: 50px auto;
border-collapse: collapse;
width: 40%;
}
th,td{
border: aqua 1px solid;
padding: 8px;
}
</style>

1
<th v-for="(project,key) in project_header" v-bind:key="key">{{project}}</th>

其实就相当于python中的for project in project_header,而这个key就是索引,这里绑定索引是为了保证改变表头顺序,值不会受影响。

效果:

1
2
3
4
5
6
7
8
return {
project_header: ["项目名称", "项目负责人", "项目级别"],
projects: [
{p_name: "阿里云项目", p_leader: "马云", p_level: "5级"},
{p_name: "腾讯云项目", p_leader: "马化腾", p_level: "5级"},
{p_name: "百度云项目", p_leader: "李彦宏", p_level: "3级"}],
username: 'xm'
}

注:js中的{}就相当于python中的对象。

ElementUI

很多比较复杂且好看的组件要自己写,对我们做测试的来说,还是蛮费劲的,好在由饿了么前端团队开源的element ui提供了非常多丰富又好看的组件,可以直接“拿来主义”,让我们的网站快速成型。

安装

1
npm i elemnet-ui -S

引入到Vue

main.js中写入:

1
2
3
4
5
6
7
8
9
10
11
import Vue from 'vue';
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import App from './App.vue';

Vue.use(ElementUI);

new Vue({
el: '#app',
render: h => h(App)
});

案例

这是我们自己写的一个表格,如果我们想要一个改成一个带复选框的表格,该怎么写呢?

“饿了么”有现成的。

实现多选非常简单: 手动添加一个el-table-column,设type属性为selection即可;默认情况下若内容过多会折行显示,若需要单行显示可以使用show-overflow-tooltip属性,它接受一个Boolean,为true时多余的内容会在 hover 时以 tooltip 的形式显示出来。

源码:

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
<template>
<el-table
ref="multipleTable"
:data="tableData"
tooltip-effect="dark"
style="width: 100%"
@selection-change="handleSelectionChange">
<el-table-column
type="selection"
width="55">
</el-table-column>
<el-table-column
label="日期"
width="120">
<template slot-scope="scope">{{ scope.row.date }}</template>
</el-table-column>
<el-table-column
prop="name"
label="姓名"
width="120">
</el-table-column>
<el-table-column
prop="address"
label="地址"
show-overflow-tooltip>
</el-table-column>
</el-table>
<div style="margin-top: 20px">
<el-button @click="toggleSelection([tableData[1], tableData[2]])">切换第二、第三行的选中状态</el-button>
<el-button @click="toggleSelection()">取消选择</el-button>
</div>
</template>

<script>
export default {
data() {
return {
tableData: [{
date: '2016-05-03',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}, {
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}, {
date: '2016-05-04',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}, {
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}, {
date: '2016-05-08',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}, {
date: '2016-05-06',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}, {
date: '2016-05-07',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}],
multipleSelection: []
}
},

methods: {
toggleSelection(rows) {
if (rows) {
rows.forEach(row => {
this.$refs.multipleTable.toggleRowSelection(row);
});
} else {
this.$refs.multipleTable.clearSelection();
}
},
handleSelectionChange(val) {
this.multipleSelection = val;
}
}
}
</script>

截取我们需要的部分,直接copy到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
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
64
65
<template>
<div>
<el-table
ref="multipleTable"
:data="tableData"
tooltip-effect="dark"
style="width: 100%"
class="projects_new"
@selection-change="handleSelectionChange">
<el-table-column
type="selection"
width="55">
</el-table-column>
<el-table-column
prop="p_name"
label="项目名称"
width="120">
</el-table-column>
<el-table-column
prop="p_leader"
label="项目负责人"
width="120">
</el-table-column>
<el-table-column
prop="p_level"
label="项目难度"
width="120">
</el-table-column>
</el-table>
<div style="margin-top: 20px">
<el-button @click="toggleSelection()">取消选择</el-button>
</div>
</div>
</template>

<script>
export default {
name: "ProjectsListNew",
data() {
return {
project_header: ["项目名称", "项目负责人", "项目级别"],
tableData: [
{p_name: "阿里云项目", p_leader: "马云", p_level: "5级"},
{p_name: "腾讯云项目", p_leader: "马化腾", p_level: "5级"},
{p_name: "百度云项目", p_leader: "李彦宏", p_level: "3级"}],
multipleTable: []
}
},
methods:{
toggleSelection() {
this.$refs.multipleTable.clearSelection();
},
handleSelectionChange(val) {
this.multipleTable = val;
console.log(this.multipleTable)
}
}
}
</script>

<style scoped>
.projects_new{
margin: 50px 500px;
}
</style>

这里出现了一些我们不认识的属性、事件或方法等,比如selection-changeclearSelection()等,在elment ui官网对应组件中都有说明。

效果:

我们通常看到一个web页面会有页头、侧边栏、内容区等模块,而这些模块其实就是一个个单独的组件,一个完整的应用就是一棵组件树。

组件

组件声明

Vue项目中,App.vue 是根组件,其它我们自己写的组件想要显示在页面上,有两种引入方式:局部和全局。

局部组件

局部组件就是在根组件中引用,首先在App.vue中导入子组件,然后声明即可。

全局组件

全局组件在main.js中引用,比如上文提到的elemnt-ui就是全局组件。

main.js中写入:

1
2
3
4
// 导入子组件
import hello from './components/HelloWorld'
// 创建全局组件
Vue.component('helloworld', hello)

在根组件App.vue中直接引用,不需要再导入和声明:

组件传值

可以在父组件中通过props向子组件传递值,

例:

根组件App.vue

1
2
3
4
5
6
7
<template>
<div id="app">
<img src="./assets/logo.png">
<pln></pln>
<HelloWorld msg="古一"></HelloWorld>
</div>
</template>

子组件HelloWorld.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<div class="hello">
<h1>欢迎来到我的世界,{{msg}}</h1>
</div>
</template>

<script>
export default {
name: 'HelloWorld',
props:{
msg:String
},
}
</script>

slot插槽

当在父组件调用子组件并在子组件的标签内添加内容,默认是会忽略的,如果不想忽略,就在子组件中添加<slot>标签作为插槽来填充该内容。

普通插槽

<slot>中添加内容,

例:

先在子组件中添加<slot>插槽

1
2
3
4
5
6
7
<template>
<div class="hello">
<h1>欢迎来到我的世界,{{msg}}</h1>
<!--添加插槽-->
<slot></slot>
</div>
</template>

在根组件中填充内容:

1
2
3
4
5
6
<template>
<div id="app">
<img src="./assets/logo.png">
<HelloWorld msg="古一"><p>你好,召唤师</p></HelloWorld>
</div>
</template>

效果:

注意:<slot>xxx</slot>若插槽中有内容,会覆盖根组件标签内的填充内容。

命名插槽

当需要多个不同的插槽时,可以给插槽命名:

子组件添加插槽

1
2
3
4
5
6
7
<template>
<div class="hello">
<h1>欢迎来到我的世界,{{msg}}</h1>
<!--添加插槽-->
<slot name='slot1'></slot>
</div>
</template>

根组件插入内容

1
2
3
4
5
6
7
8
<template>
<div id="app">
<img src="./assets/logo.png">
<HelloWorld msg="古一">
<p slot="slot1">他日若遂凌云志,敢笑黄巢不丈夫</p>
</HelloWorld>
</div>
</template>

在vue2.6之后的版本也可以这样写:

1
2
3
4
5
6
     
<HelloWorld msg="古一">
<template v-slot:slot1>
<p slot="slot1">他日若遂凌云志,敢笑黄巢不丈夫 </p>
</template>
</HelloWorld>

路由

Vue.js 路由允许我们通过不同的 URL 访问不同的内容,通过 Vue.js 可以实现多视图的单页Web应用(single page web application,SPA)。

安装

1
npm install vue-router

简单路由

新建一个js文件专门存放路由信息

index.js

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
import Vue from 'vue'
// 1、导入路由
import Router from 'vue-router'

// 2、导入子组件
import HelloWorld from '@/components/HelloWorld'
import pl from '@/components/ProjectsList'
import pln from '@/components/ProjectsListNew'

// 3、注册路由
Vue.use(Router)


const router = new Router({
routes: [
// routes数组中的每个对象,就对应一条路由
{path: '/', component: HelloWorld},
{path: '/pl', component: pl},
{path: '/pln', component: pln},

]
})

// 4、导出路由
export default router;

然后在main.js中导入路由

1
2
// 如果路由文件名为index.js,这里可以省略/index.js
import router from './router'

最后在根组件中指明路由视图:

1
2
3
4
5
6
<template>
<div id="app">
<img src="./assets/logo.png">
<router-view></router-view>
</div>
</template>

访问不同的url,就能显示对应的组件内容。

嵌套路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const router = new Router({
routes: [
// routes数组中的每个对象,就对应一条路由
{path: '/', component: HelloWorld},
{
path: '/pl',
component: pl,
children: [
{path: '/pln', component: pln}
]
}

]
})

axios

axios是一个很流行的请求库,封装了ajax,是vue发起异步请求的标配。

安装

1
npm install axios -S

案例

HelloWorld子组件中引入axios

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
<template>
<div class="hello">
<h1>欢迎来到我的世界</h1>
<el-image :src="url" fit="cover"></el-image>
</div>
</template>

<script>

import axios from 'axios'

export default {
name: 'HelloWorld',
data () {
return {
url: ''
}
},
mounted() {
axios.get('https://dog.ceo/api/breeds/image/random')
.then(response => {
this.url = response.data.message
})
.catch(function (err) {
console.log(err)
})
}
}
</script>

如果每个页面都与后端有交互,那每个页面都会调用axios发起请求,为了方便维护,所以会把所有请求api统一管理起来,新建一个管理所有api的js文件。

1
2
3
4
5
6
import axios from 'axios'

var host = 'https://dog.ceo'
// 定义一个变量dogs来接收axios发起请求后返回的response

export const dogs = () => { return axios.get(`${host}/api/breeds/image/random`) }

导入到组件中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<script>
import {dogs} from '../api/api'
// import axios from 'axios'

export default {
name: 'HelloWorld',
data () {
return {
url: ''
}
},
mounted() {
dogs //引用dogs
.then(response => {
this.url = response.data.message
})
.catch(function (err) {
console.log(err)
})
}
}
</script>