Vue概述
Vue是一套用于构建前端页面的渐进式JavaScript框架,所谓渐进式就是逐步实现新特性(如MVVM、虚拟化Dom),与其他大型框架不同的是,Vue被设计为可以自底向上逐层应用
Vue的核心库只关注视图层(HTML+CSS+JS),易于上手,也便于与第三方库(vue-router:页面跳转,vue-resource:通信,vuex:状态管理,axios:网络通信)或既有项目整合
Vue借鉴了AngulaJS的模块化开发和React的虚拟Dom
MVVM模式
MVVM的组成:
- Model:模型层(数据),这里是Java对象(Json形式)
- View:视图层,即HTML标签及CSS样式等
- ViewModel:连接视图和数据的中间件,Vue.js就是其实现者之一
在MVVM架构中,是不允许数据和视图直接通信的,只能通过ViewModel来通信
- ViewModel能观察到数据的变化,并对视图对应的内容进行更新
- ViewModel能监听到视图的变化,并能够通知数据发生改变
为什么要使用MVVM
MVVM模式和MVC模式一样,主要目的是分离视图(View)和模型(Model),其主要优势为:
- 低耦合:View可以独立于Model变化和修改,一个ViewModel可以绑定到不同的View上,当View变化的时候Model可以不变,当Model变化的时候View也可以不变
- 可复用:可以把一些视图逻辑放在一个ViewModel里面,让很多View重复使用该逻辑
- 独立开发:开发人员可以专注于业务逻辑和数据的开发,设计人员可以专注于页面设计
- 可测试:可以针对ViewModel来进行测试
Vue程序的建立
注意:Vue不支持IE8及以下版本,因为Vue使用了IE8及以下版本无法模拟的ECMAScript5特性
Vue下载地址
-
开发版本:
包含完整的警告和调试模式:https://vuejs.org/js/vue.js
删除了警告:https://vuejs.org/js/vue.min.js
-
CDN
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
代码编写
-
创建一个HTML文件
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>First</title> </head> <body> <p id="div1"> </p> <div id="div2"> </div> </body> <!-- 导入 vue.js --> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.js"></script> <script> // 创建vue对象,用el绑定一个标签,标签中用绑定对象中data下的xxx的数据 var vm1 = new Vue({ el: "#div1", data:{ message: "This is div1" } }); var vm2 = new Vue({ el: "#div2", data:{ message: "This is div2" } }); </script> </html>
Vue语法
在上面的代码中,Vue的部分语法已经展现,还有其他语法如下
-
指令:v-xxx
v-bind:给标签的某一属性绑定数据
<body> <div id="app"> <span v-bind:title="message"> 鼠标悬停几秒以查看此bind标签title所绑定的信息 </span> </div> </body> <script> var vm1 = new Vue({ el: "#app", data:{ message: "页面加载于" + new Date().toLocaleDateString() } }); </script>
用浏览器中打开上面的内容并调出控制台,输入app.message = “new message”,可以看到span的title的内容自动发生改变
v-if v-else-if v-else
<body> <div id="app"> <h1 v-if = "type === 'A'">A</h1> <h1 v-else-if = "type === 'B'">B</h1> <h1 v-else>C</h1> </div> </body> <script> var vm1 = new Vue({ el: "#app", data:{ type: 'B' } }); </script>
v-for
<body> <div id="app"> Students name: <li v-for= "item in items"> </li> </div> </body> <script> var vm1 = new Vue({ el: "#app", data:{ items:[ {message: 'sam'}, {message: 'kim'} ] } }); </script>
-
监听DOM事件 v-on:xxx
<body> <div id="app"> <button v-on:click = "sayHi">click me</button> </div> </body> <script> var vm1 = new Vue({ el: "#app", data: { message: "Hi" }, methods: { sayHi: function(){ alert(this.message); } } }); </script>
-
v-model 实现数据双向绑定
<body> <div id="app"> 输入的文本:<input type="text" v-model="message"> </div> </body> <script> var vm1 = new Vue({ el: "#app", data: { message: "123" } }); </script>
<body> <div id="app"> <!-- 当某一按钮被选中时,其value值会赋给data中的personName --> person: <input type="radio" name="name" value="sam" v-model="personName"> sam <input type="radio" name="name" value="tim" v-model="personName"> tim <p> be chosed person: </p> </div> </body> <script> var vm1 = new Vue({ el: "#app", data: { personName: '' } }); </script>
注意:v-model会忽略所有表单元素的value、checked、selected特性的初始值而总是将Vue实例的数据作为数据来源。故应该通过JavaScript在组件的data选项中声明初始值
Vue组件
Vue组件可以理解为自定义标签,例如页头、侧边栏、内容区等组件,每个组件又包含了其他的如导航连接、文章等组件,目的是实现一定程度的复用
<body>
<div id="app">
<hello></hello>
</div>
</body>
<script>
// 定义一个叫hello的组件
Vue.component("hello",{
template: '<li>Hello</li>'
});
</script>
使用props属性传递参数
注意:默认规则下props属性里的值不能为大写
<body>
<div id="app">
<hello v-for="item in lessons" v-bind:lesson="item"></hello>
</div>
</body>
<script>
// 定义一个叫hello的组件
Vue.component("hello",{
props: ['lesson'],
template: '<li></li>'
});
var vm = new Vue({
el: "#app",
data: {
lessons: ["Java","C++","Vue"]
}
});
</script>
Axios异步通信
Axios是一个开源的可以用在浏览器端和NodeJS的异步通信框架,它的主要作用就是实现AJAX异步通信,其功能特点如下:
- 从浏览器中创建XMLHttpRequests
- 从node.js创建http请求
- 支持Promise API(js的链式编程)
- 拦截请求和响应
- 转换请求数据和响应数据
- 取消请求
- 自动转换JSON数据
- 客户端支持防御XSRF(跨站请求伪造)
Axios的由来
由于Vue.js并不包含如 AJAX 的通信功能,为了解决通信问题,作者单独开发了一个名为 vue-resource 的插件,不过在进入2.0版本以后停止了对该插件的维护并推荐了Axios框架
Axios的使用
获取一个本地的json数据并在控制台输出
注意:这里由于浏览器的同源策略,需要用VsCode的LiveServer测试,否则会产生跨域错误
<body>
<div id="app">
</div>
</body>
<!-- 导入 vue.js -->
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.js"></script>
<!-- 导入 axios -->
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script type="text/javascript">
var vm = new Vue({
el: "#app",
mounted(){
axios.get('./data.json').then(response=>(console.log(response.data)));
}
});
</script>
</html>
将获取到的数据在页面上展示
<body>
<div id="app">
<div>name:</div>
<div>address-street:</div>
<div>address-city:</div>
<div>address-country:</div>
</div>
</body>
<!-- 导入 vue.js -->
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.js"></script>
<!-- 导入 axios -->
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script type="text/javascript">
var vm = new Vue({
el: "#app",
data(){
return{
info:{
name: null,
url: null,
page: null,
isNonProfit: null,
address: {
street: null,
city: null,
country: null
},
links: [{
name: null,
url: null
}]
}
}
},
mounted(){
axios.get('./data.json').then(response=>(this.info=response.data));
}
});
</script>
用于测试的j
son文件
{
"name":"sam",
"url":"https://sam.company.com",
"page": 1,
"isNonProfit": true,
"address": {
"street": "54",
"city": "xi'an",
"country": "china"
},
"links":[
{
"name": "bilibili",
"url": "https://bilibili.com"
},
{
"name": "百度",
"url": "https://baidu.com"
},
{
"name": "game",
"url": "https://4399.com"
}
]
}
计算属性
计算属性的意思是有计算能力的属性,计算指函数,进一步理解,就是一种能够将计算结果缓存起来的属性
其本质是一种类似缓存的技术,目的是减少资源消耗
<body>
<div id="app">
<p>Time1: </p>
<p>Time2: </p>
</div>
</body>
<!-- 导入 vue.js -->
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.js"></script>
<script type="text/javascript">
var vm = new Vue({
el: "#app",
// 方法
methods:{
currentTime1: function(){
return Date.now();
}
},
// 计算属性 methods与computed中的方法如果重名,只会调用methods的方法
computed:{
currentTime2: function(){
return Date.now();
}
}
});
</script>
插槽slot
在Vue.js中,
<body>
<div id="app">
<todo>
<!-- : 是 v-bind的简写 -->
<todo-title slot="todo-title-slot" :title="title"></todo-title>
<todo-items slot="todo-items-slot" v-for="item in todoItems" :item="item"></todo-items>
</todo>
</div>
</body>
<script type="text/javascript">
Vue.component("todo",{
template: '<div>\
<slot name="todo-title-slot"></slot>\
<ul>\
<slot name="todo-items-slot"></slot>\
</ul>\
</div>'
});
Vue.component("todo-title",{
props: ['title'],
template:'<div></div>'
});
Vue.component("todo-items",{
props: ['item'],
template:'<li></li>'
});
var vm = new Vue({
el: "#app",
data:{
title: 'Lesson',
todoItems: ['Java','C++','Python']
}
});
</script>
自定义事件
用于解决事件分发的问题,例如在组件中删除Vue实例中的数据
<body>
<div id="app">
<todo>
<!-- : 是 v-bind的简写 -->
<todo-title slot="todo-title-slot" :title="title"></todo-title>
<!-- v-for 遍历元素及其下标 -->
<todo-items slot="todo-items-slot" v-for="(item,index) in todoItems"
:item="item" :index="index" v-on:remove="removeItem(index)"></todo-items>
</todo>
</div>
</body>
<script type="text/javascript">
Vue.component("todo",{
template: '<div>\
<slot name="todo-title-slot"></slot>\
<ul>\
<slot name="todo-items-slot"></slot>\
</ul>\
</div>'
});
Vue.component("todo-title",{
props: ['title'],
template:'<div></div>'
});
Vue.component("todo-items",{
props: ['item','index'],
// @ 是 v-on的简写 这里只能绑定当前组件里的方法
template:'<li> <button @click="remove">删除</button></li>',
methods:{
remove: function(index){
//$emit('自定义方法名',参数)
this.$emit('remove',index);
}
}
});
var vm = new Vue({
el: "#app",
data:{
title: 'Lesson',
todoItems: ['Java','C++','Python']
},
methods:{
removeItem:function(index){
console.log("删除了"+this.todoItems[index]);
this.todoItems.splice(index,1);// 删除当前下标的元素
}
}
});
</script>
Vue-cli
Vue-cli 是官方提供的一个脚手架,用于快速生成一个Vue的项目模板。使用它可以自动生成预先定义好的目录结构及基础代码
主要功能
- 统一的目录结构
- 支持本地调试
- 热部署
- 单元测试
- 集成打包上线
Vue-cli的使用
使用前需要先安装好Node.js及其常用组件,然后使用其中的cnpm组件安装vue-cli,命令如下
cnpm install vue-cli -g
一般可在C:\Users\Administrator\AppData\Roaming\npm\node_modules目录下看到已安装的内容
在cmd窗口中切换至想要生成Vue工程的路径,执行vue init webpack xxxx
即可生成一个文件名为xxxx的Vue项目模板
项目主目录下的package.json文件记录了此项目的依赖库,在cmd窗口中执行npm install
即可下载这些依赖,放在项目的node_modules文件中
在cmd窗口中执行npm run dev
,项目就被打包运行(运行在Node.js服务器上),可以在浏览器中访问
项目中src目录(代码目录)结构如下
│ App.vue
│ main.js (项目的入口)
│
├─assets
│ logo.png
│
└─components (组件)
HelloWorld.vue
补充:Vue组件的export default(https://www.cnblogs.com/blog-cxj2017522/p/8562536.html )
CommonsJS
服务器端的NodeJS遵循CommonsJS规范,该规范的核心思想是允许模块通过require方法来同步加载所需依赖的其他模块,然后通过exports或module.exports来导出需要暴露的接口
Webpack
Webpack是一个现代JavaScript应用程序的静态模块打包器(module bundler)。当Webpack处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个bundle
它可以将许多松散耦合的模块按照依赖和规则打包成符合生产环境部署的前端资源。还可以将按需加载的模块进行代码分离,等到实际需要时再异步加载。通过loader转换,任何形式的资源都可以当做模块,比如CommonsJS、AMD、ES6、CSS、Json、CoffeeScript、LESS等
Webpack的使用
安装:
-g 全局安装 -D 局部安装
npm install webpack -g
npm install webpack-cli -g
如果上面的方法不行 试试下面的
cnpm install webpack webpack-cli -g
测试安装成功:
webpack -v
webpack-cli -v
ps:在之前新建项目的build目录中都是有关项目打包内容的文件,项目主目录里的package.json中记录了webpack的版本
新建一个空项目,并在项目中建立一个webpack.config.js文件,内容如下:
module.exports = {
entry: './modules/main.js', (入口)
output: {
filename: "./js/bundle.js" (打包后文件的名称与路径)
}
};
在cmd窗口中于项目目录下执行webpack
,即可将项目打包,放在项目主目录中的dist目录下
ps:如果执行webpack
时报错,可能是webpack或node的版本不适应,需要调整其版本
vue-router 路由
vue-router是Vue.js官方的路由管理器,它所包含的功能有:
- 嵌套的路由/视图表
- 模块化的、基于组件的路由配置
- 路由参数、查询、通配符
- 基于Vue.js过渡系统的视图过渡效果
- 细粒度的导航控制
- 带有自动激活的CSS class 链接
- 自定义滚动条行为
vue-router的使用
首先执行npm install vue-router --save-dev
来安装它
编写一些模块
Main.vue
<template>
<h1>首页</h1>
</template>
<script>
export default {
name: "Main"
}
</script>
<style scoped>
</style>
Content.vue
<template>
<p>内容页</p>
</template>
<script>
export default {
name: "Content"
}
</script>
<style scoped>
</style>
建立router目录,并创建index.js文件以配置路由
import Vue from 'vue'
import VueRouter from 'vue-router'
//导入组件
import Content from '../components/Content'
import Main from '../components/Main'
//安装路由
Vue.use(VueRouter);
//配置导出路由
export default new VueRouter({
routes: [
{
//路由路径
path: '/content',
//路由名称
name: 'content',
//跳转的组件
component: Content
},
{
//路由路径
path: '/main',
//路由名称
name: 'main',
//跳转的组件
component: Main
}
]
});
在项目入口main.js中导入并配置路由
import Vue from 'vue'
import App from './App'
import router from './router'//会自动扫描该路径下的路由配置
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
//配置路由
router,
components: { App },
template: '<App/>'
})
在App.vue中放置路由
<template>
<div id="app">
<h1>Vue-Router</h1>
<router-link to="/main">首页</router-link>
<router-link to="/content">内容页</router-link>
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'App',
}
</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>
elementUI
一种常见的与Vue.js结合的组件库
elementUI的使用
这里推荐使用npm的方式安装,以便更好地和webpack打包工具配合使用
npm i element-ui -S
还需要安装SASS加载器
cnpm install sass-loader node-sass --save-dev
建立Login.vue作为登陆模块
<template>
<div>
<el-form ref="loginForm" :model="form" :rules="rules" label-width="80px" class="login-box">
<h3 class="login-title">欢迎登录</h3>
<el-form-item label="账号" prop="username">
<el-input type="text" placeholder="请输入账号" v-model="form.username"/>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input type="password" placeholder="请输入密码" v-model="form.password"/>
</el-form-item>
<el-form-item>
<el-button type="primary" v-on:click="onSubmit('loginForm')">登录</el-button>
</el-form-item>
</el-form>
<el-dialog
title="温馨提示"
:visible.sync="dialogVisible"
width="30%"
:before-close="handleClose">
<span>请输入账号和密码</span>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="dialogVisible = false">确 定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
export default {
name: "Login",
data(){
return{
form:{
username: '',
password: ''
},
// 表单验证 需要在 el-form-item 元素中增加 prop 属性
rules:{
username:[
{required: true, message: '账号不可为空', trigger: 'blur'}
],
password:[
{required: true, message: '密码不可为空', trigger: 'blur'}
]
},
// 对话框设置隐藏
dialogVisble: false
}
},
methods: {
onSubmit(formName){
// 为表单绑定验证功能
this.$refs[formName].validate((valid) => {
if(valid){
// 使用 vue-router 路由到指定页面
this.$router.push("/main");
} else {
this.dialogVisble = true;
return false;
}
});
}
}
}
</script>
<style lang="scss" scoped>
.login-box {
border: 1px solid #DCDFE6;
width: 350px;
margin: 180px auto;
padding: 35px 35px 15px 35px;
border-radius: 5px;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
box-shadow: 0 0 25px #909399;
}
.login-title {
text-align: center;
margin: 0 auto 40px auto;
color: #303133;
}
</style>
main.js中导入并使用ElementUI
import Vue from 'vue'
import App from './App'
import router from './router'//会自动扫描该路径下的路由配置
//导入ElementUI
import Element from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(Element);
new Vue({
el: '#app',
//配置ElementUI
render: h => h(App),
router,
components: { App },
template: '<App/>'
})
App.vue
<template>
<div id="app">
<!-- 展示路由内容 -->
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'App',
}
</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>
路由index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Main from '../view/Main'
import Login from '../view/Login'
Vue.use(VueRouter);
export default new VueRouter({
routes: [
{
path: '/main',
component: Main
},
{
path: '/login',
component: Login
}
]
});
嵌套路由
嵌套路由又称子路由,在实际应用中,通常由多层嵌套的组件组合而成
如将上面的登陆demo中的Main.vue编写为:
<template>
<div>
<el-container>
<el-aside width="200px">
<el-menu :default-openeds="[1]">
<el-submenu index="1">
<template slot="title"><i class="el-icon-caret-right"></i>用户管理</template>
<el-menu-item-group>
<el-menu-item index="1-1">
<router-link to="/user/profile">个人信息</router-link>
</el-menu-item>
<el-menu-item index="1-2">
<router-link to="/user/list">用户列表</router-link>
</el-menu-item>
</el-menu-item-group>
</el-submenu>
<el-submenu index="2">
<template slot="title"><i class="el-icon-caret-right"></i>内容管理</template>
<el-menu-item-group>
<el-menu-item index="2-1">分类管理</el-menu-item>
<el-menu-item index="2-2">内容列表</el-menu-item>
</el-menu-item-group>
</el-submenu>
</el-menu>
</el-aside>
<el-container>
<el-header style="text-align: right; font-size: 12px">
<el-dropdown>
<i class="el-icon-setting" style="margin-right: 15px"></i>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item>个人信息</el-dropdown-item>
<el-dropdown-item>退出登陆</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</el-header>
<el-main>
<router-view/>
</el-main>
</el-container>
</el-container>
</div>
</template>
<script>
export default {
name: "Main"
}
</script>
<style scoped lang="scss">
.el-header{
background-color: #B3C0D1;
color: #333;
line-height: 60px;
}
.el-aside{
color: #333;
}
</style>
路由index.js设置为:
import Vue from 'vue'
import VueRouter from 'vue-router'
import Main from '../view/Main'
import Login from '../view/Login'
import UserProfile from '../view/user/Profile'
import UserList from '../view/user/List'
Vue.use(VueRouter);
export default new VueRouter({
routes: [
{
path: '/main',
component: Main,
//嵌套路由
children: [
{path: '/user/profile',component:UserProfile},
{path: '/user/list',component:UserList},
]
},
{
path: '/login',
component: Login
}
]
});
List.vue与Profile.vue
<template>
<h1>用户列表</h1>
</template>
<script>
export default {
name: 'UserList',
}
</script>
<style scoped>
</style>
<template>
<h1>个人信息</h1>
</template>
<script>
export default {
name: 'UserProfile',
}
</script>
<style scoped>
</style>
参数传递与重定向
在进行路由时,可能需要传递参数,做法如下,接着上面的例子
Main.vue中指定路由并传递参数
...
<el-menu-item index="1-1">
<router-link :to="{name: 'UserProfile',params:{id: 1}}">个人信息</router-link>
</el-menu-item>
...
路由中进行参数传递(index.js)
...
{
path: '/main',
component: Main,
//嵌套路由
children: [
{path: '/user/profile/:id',name:'UserProfile',component:UserProfile},
{path: '/user/list',component:UserList},
]
},
...
对应组件中将数据展示(Profile.vue)
<template>
<div>
<h1>个人信息</h1>
</div>
</template>
...
也可以用props进行取值
...
{
path: '/main',
component: Main,
//嵌套路由
children: [
{path: '/user/profile/:id',name:'UserProfile',component:UserProfile, props:true},
{path: '/user/list',component:UserList},
]
},
...
<template>
<div>
<h1>个人信息</h1>
</div>
</template>
<script>
export default {
props:[
'id'
],
name: 'UserProfile',
}
</script>
...
重定向
建立路由
{
path: '/goHome',
redirect: '/main'
}
建立组件
<el-menu-item index="1-3">
<router-link to="/goHome">返回首页</router-link>
</el-menu-item>
404&路由钩子
前面使用过的路由是有两种模式的
- hash:路径带 # ,如http://localhost:8080/#/main/
- history:路径不带 # ,如http://localhost:8080/main/
路由模式的设置方式如下:
export default new VueRouter({
mode: 'history',
routes: [
]
});
配置404页面路由
...
import NotFound from '../view/NotFound'
export default new VueRouter({
mode: 'history',
routes: [
...
{
path: '*',
component: NotFound
}
]
});
路由钩子函数
- 进入路由前:beforeRouteEnter
- 进入路由后:beforeRouteLeave
<script>
export default {
props:[
'id'
],
name: 'UserProfile',
beforeRouteEnter (to, from, next) {
console.log("进入路由之前");
//进入路由页面之前调一个函数
next(vm => {
vm.getData();
});
},
beforeRouteLeave (to, from, next) {
console.log("进入路由之后");
next();
},
methods:{
getData: function(){
this.axios({
method: 'get',
url: 'http://localhost:8080/static/mock/data.json'
}).then(function(response){
console.log(response);
})
}
}
}
</script>
参数说明:
- to:路由将要跳转的路径信息
- from:路径跳转前的路径信息
- next:路由的控制参数
next函数
- next() 跳入下一个页面
- next(‘/path’) 改变路由的跳转方向,使其跳到另一个路由
- next(false) 返回原来的页面
- next((vm) => {}) 仅在beforeRouteEnter中可用,vm是组件实例