Vue.js是什么?
vue是现在前端三大框架之一,使用起来简单轻便,上手快速。
Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。
简单来说,vue的核心就是:
- 响应式数据绑定:所谓响应式,就是当数据发生改变,视图可以自动更新,可以不用关心dom操作,而专心数据操作
- 视图组件可嵌套组合:将视图按照功能分成若干个组件,由组件嵌套组合整个应用
快速创建Vue工程
安装vue
直接通过<script>引用vue
1
| <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
|
npm安装
ps:前提要先安装好node.js
由于npm速度比较慢,可以通过淘宝镜像源安装。
安装淘宝镜像源:
1
| npm install -g cnpm --registry=https://registry.npm.taobao.org
|
安装成功后,就可以是用cnpm
来安装vue了,速度会快很多。
安装vue命令行工具
Vue 提供了一个官方的 CLI,为单页面应用 (SPA) 快速搭建繁杂的脚手架。
1 2 3 4
| # 使用npm npm install -g @vue/cli # 使用淘宝镜像源 cnpm install -g @vue/cli
|
创建vue工程
所有准备工作就绪后,vue提供了一条命令来快速创建工程:
ps:该命令只有vue-cli3.0以上才支持
目录结构
目录/文件 |
说明 |
build |
项目构建(webpack)相关代码 |
config |
配置目录,包括端口号等。 |
node_modules |
npm 加载的项目依赖模块(类似python中的site-packages) |
src |
这里是我们要开发的目录,基本上要做的事情都在这个目录里。里面包含了几个目录及文件: assets: 放置一些图片,如logo等。components: 目录里面放了一个组件文件,可以不用。 App.vue: 根组件,我们也可以直接将组件写这里,而不使用 components 目录。 main.js: 项目的核心文件。 |
static |
静态资源目录,如图片、字体等。 |
test |
初始测试目录,可删除 |
启动项目
通过在根组件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
中实际是指定了username
和password
的值分别为admin
和123
,但是前端只显示了密码,为什么?因为我们对密码输入框进行了数据的双向绑定
,即可以将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-if
、v-else-if
、v-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-show
和v-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_show
为true
时,效果:
而当is_show
为false
时,两者均不会渲染标签内容,但是有一个区别:
可以看到,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
提供了非常多丰富又好看的组件,可以直接“拿来主义”,让我们的网站快速成型。
安装
引入到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-change
、clearSelection()
等,在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)。
安装
简单路由
新建一个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'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld' import pl from '@/components/ProjectsList' import pln from '@/components/ProjectsListNew'
Vue.use(Router)
const router = new Router({ routes: [ {path: '/', component: HelloWorld}, {path: '/pl', component: pl}, {path: '/pln', component: pln},
] })
export default router;
|
然后在main.js
中导入路由
1 2
| 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: [ {path: '/', component: HelloWorld}, { path: '/pl', component: pl, children: [ {path: '/pln', component: pln} ] }
] })
|
axios
axios
是一个很流行的请求库,封装了ajax
,是vue发起异步请求的标配。
安装
案例
在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'
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>
|