前引
今天是2018年12月30,虽不是2018年的最后一天,但是却是自己在2018年写的最后一篇博客了,昨天下班在地铁上闲来无事,翻起了关注的一些公众号发的技术博文,里面就提到写博客的重要性,其实这样的内容看了N多了,但是,里面有一句感觉说的很对,现在的博客变了味了,因为我们可以Ctrl+c、Ctrl+v,短短几秒钟就是一遍几千字的博文,真正自己写出的博文需要花费很长的时间。说心里话,自己的博客中也有,大概有三分之一吧,突然莫名的恐慌,因为很可能就是这三分之一的博客,就会掩盖自己花费精力完成的其他的博客,被别人冠上这么一个标签,这人也是一个充数的、装大半蒜的家伙。其实内心已经一万个*了,因为我们都是人,说话说的好,一个人一直做好事,但是突然有一天做了一件坏事,大家就定义这个人是坏人,好人难做啊,同样的,写博客亦如此。
不感慨了,看到的小伙伴们支持一下,当然欢迎转载,欢迎Ctrl+c,但是有一个请求,在文章的最上方请注明文章来源,非常感谢。
接下来,说今天的正事,本篇博客的正事,Vue的进阶篇,进阶,谈不上,也算是一点稍微不那么基础的基础吧,更基础的欢迎浏览本人的另一篇博客Vue基础篇
*惯,开始正文之前,先来了解一些必知必会的小知识点
前言
1,组件和插件的区别(组件是一个项目的必须组成部分,而插件是不必须的,动态的,即插即用)
2,在一个项目中,一个文件以.开头表示电脑系统的隐藏文件。
3,在前端中,一个项目中的index文件或main文件,默认为一个项目的入口
备注:在vue-cli项目中会有一个包含很多包的文件,这个文件的文件名就是node_modules,还有一个.gitignore的文件,这个隐藏文件的意思就是git忽视的一个文件,打开这个文件,里面的有这些内容:
.DS_Store
node_modules
/dist
在这个文件中,我们可以配置当前项目一些忽略的文件,支持部分正则匹配。比如,node_modules项,如果项目上传git,或者GitHub时,会自动忽略掉这个文件,所以我们上传到GitHub上的项目中是不包含这个node_modules文件的,这也是GitHub的一个局限性,大的文件无法上传,导致我们从GitHub上下载的一些开源的项目,并不能直接使用,对于vue的项目,还需要使用一个命令:npm install 将包含很多包的这个文件下载下来。
因此,我们从GitHub上下载下来的vue的开源代码,第一步就是运行一个命令:npm install (一般在当前目录下)
GitHub中,每个开源文件都有一个文件READMI.md(markdown文件),这个文件中,会告诉我们怎么使用这个项目。
赠送的小技巧:另外的我们在一个项目名中,看到带有awesome的,表示一个生态系统,里面在这个基础上封装了很多插件和模块。
vue-cli开始
在vue中,一个.vue文件就是一个vue的组件。
一个标准的可重用的vue组件的格式:
<!--一个组件包含html、css、js--> <template>
<!--组件的结构--> </template> <script>
// 处理业务逻辑
export default {
name: "Vheader",
}
</script> <style>
/*页面的样式*/ </style>
备注:
1,在template模板中,必须有一个包裹的标签。 需要注意的是:template不是一个标签,是vue中的一个模板
2,在vue中,一个组件的命名规范是:首字母必须大写,比如:所有的组件以“V”开头。一个组件中的data(数据属性)必须是函数,这个函数必须return一个对象。
3,在一个项目中的MP3文件,由于webpack的打包编译,是无法直接引入到src中,所以导入MP3文件时,必须当做模块来引入
import song1 from './assets/薛之谦 - 一半 [mqms2].mp3' <audio src="song1" autoplay controls></audio>
总之,所有的静态资源都可以作为模块导入 。
组件的引入
组件的引入很简单 ,分三步:
1,导入组件 import Vheader from './components/Vheader.vue'
2,挂载组件 在export default 中 定义一个key为components,值为一个对象,这个对象中放着导入键值对;键为变量名,值为组件名,在es6中,键值相同时,可以缩写为一个单一的值。
比如:components:{ Vheader }
3,在template模板中使用组件: <Vheader> </Vheader>
备注:在父组件中定义的样式类,默认为全局的样式类,所以如果要保证自身元素的样式,在子组件的style标签中加入:scoped即可 <style scoped><style>
父子传值
父子组件的传值:
父组件向子组件传值
1,绑定自定义的属性 : 在父组件中对应的子组件的模板上绑定属性。
比如:<Vcontent :menu=“menus”><Vcontent> #menu为定义的属性名 menus为数据属性
2,在子组件中接收这个自定义的属性,并且属性必须要验证 ,接收的属性放在props中
比如:props:{ menu:Array } 备注:接收的变量名必须与自定义的变量名保持一致
3,这时子组件中的数据值就是这个对应的menu变量,我们也可以在数据属性中重新赋值
比如:data(){ return { menus:this.menu } } #这时我们在子组件用的值就是menus了
备注:这里的this是指当前的子组件的Vue对象,Vue创建组件时:Vue.component()
子组件向父组件传值
1,在子组件中定义一个事件: 比如:在Vheader中定义一个点击事件。
<template> <div class="header"> // 定义一个点击事件 addOne
<button @click="addOne">添加一个菜单</button>
</div>
</template> <script>
export default {
name: "Vheader",
data(){
return { }
},
methods:{
// 事件触发自定义的事件
addOne(){
this.$emit('addMenu','酸菜鱼'); #this.$emit(“自定义事件名”,“往父组件传的值”)
}
}
}
</script> <style scoped> </style>
2,触发自定义的事件,this.$emit("自定义的事件名","要向父组件传的值") #备注:固定写法,而且自定义的事件名必须与父组件中自定义的事件名一致
3,在父组件中对应的子组件模板中,定义好这个自定义事件,比如:<Vheader @addMenu="addHand"></Vheader>
并在对应的事件中,接收传的值,并做相应处理。
methods:{
addHand(value){
this.menus.push(value); #接收传的值,并将值添加到父组件的menus数组中
}
}
vue的优势和用途
vue主做单页面应用
比如:饿了么、掘金、网易云音乐、豆瓣、知乎等等
在vue中,可以给每一个单页面(组件)配置一个路由,这个是由vue-router来控制的
vue-router
首先,要在我们的项目中下载安装这个vue-router
npm install vue-router
如果在一个模块化工程中使用它,必须要通过 Vue.use()
明确地安装路由功能:
在项目的入口 main.js中
import Vue from 'vue'
import VueRouter from 'vue-router' Vue.use(VueRouter)
接着就可以在这个main.js中配置组件
1. 定义 (路由) 组件。
// 比如:
const Foo = { template: '<div>foo</div>' }
const Bar = { template: '<div>bar</div>' }
// 也可以从其他文件 import 进来
import Vhome from './components/Vhome.vue' import Vuser from './components/Vuser.vue'
2. 定义路由
每个路由应该映射一个组件。 其中"component" 可以是通过 Vue.extend() 创建的组件构造器,或者,只是一个组件配置对象。
const routes = [
{ path: '/foo', component: Foo },
{ path: '/bar', component: Bar }
]
3. 创建 router 实例,然后传 `routes` 配置
// 你还可以传别的配置参数, 不过先这么简单着吧。
const router = new VueRouter({
routes // (缩写) 相当于 routes: routes
}
4. 创建和挂载根实例。
// 记得要通过 router 配置参数注入路由,
// 从而让整个应用都有路由功能
const app = new Vue({
router
}).$mount('#app')
现在,应用可以启动了!
Tips: 通过注入路由器,我们可以在任何组件内通过 this.$router
访问路由器,也可以通过 this.$route
访问当前路由:
console.log(this.$router) #打印$router的结果:
[]
app:Vue {_uid: 0, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: Vue, …}
apps:[Vue]
beforeHooks:[]
fallback:false
history:HashHistory {router: VueRouter, base: "", current: {…}, pending: null, ready: true, …}
matcher:{match: ƒ, addRoutes: ƒ}
mode:"hash"
options:{routes: Array(2)}
resolveHooks:[]
currentRoute:(...)
__proto__:Object 点击打开__proto__(父级中声明的方法,在子级中都可以使用): addRoutes:ƒ addRoutes(routes)
afterEach:ƒ afterEach(fn)
back:ƒ back()
beforeEach:ƒ beforeEach(fn)
beforeResolve:ƒ beforeResolve(fn)
forward:ƒ forward()
getMatchedComponents:ƒ getMatchedComponents(to)
go:ƒ go(n)
init:ƒ init(app /* Vue component instance */)
match:ƒ match( raw, current, redirectedFrom )
onError:ƒ onError(errorCb)
onReady:ƒ onReady(cb, errorCb)
push:ƒ push(location, onComplete, onAbort)
replace:ƒ replace(location, onComplete, onAbort)
resolve:ƒ resolve( to, current, append )
constructor:ƒ VueRouter(options)
currentRoute:(...)
get currentRoute:ƒ ()
__proto__:Object
console.log(this.$route) 打印$route的结果:
- fullPath:"/marked"
- hash:""
- matched:[{…}]
- meta:{}
- name:"marked"
- params:
- __proto__:Object
- path:"/marked"
- query:{}
- __proto__:
- constructor:ƒ Object()
- hasOwnProperty:ƒ hasOwnProperty()
- isPrototypeOf:ƒ isPrototypeOf()
- propertyIsEnumerable:ƒ propertyIsEnumerable()
- toLocaleString:ƒ toLocaleString()
- toString:ƒ toString()
- valueOf:ƒ valueOf()
备注:$router中,push方法常用,在$route中,path(获取当前访问的路径)和fullpath(获取当前访问的全路径)方法常用
配置好了路由,接下来就该在HTML中使用了
<div id="app">
<h1>Hello App!</h1>
<p>
<!-- 使用 router-link 组件来导航. -->
<!-- 通过传入 `to` 属性指定链接. -->
<!-- <router-link> 默认会被渲染成一个 `<a>` 标签 -->
<router-link to="/foo">Go to Foo</router-link>
<router-link to="/bar">Go to Bar</router-link>
</p>
<!-- 路由出口 -->
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view></router-view>
</div>
备注:路由出口 --> 路由匹配到的组件将渲染在这里 --> <router-view></router-view> 这是路由的出口
使用 router-link 组件来导航. -->通过传入 `to` 属性指定链接. --><router-link> 默认会被渲染成一个 `<a>` 标签
注意:router-link和router-view也可以放置在不同的子组件中
<router-view></router-view> 可以简写成 <router-view/>
动态路由匹配:
一个“路径参数”使用冒号 :
标记。当匹配到一个路由时,参数值会被设置到 this.$route.params
,可以在每个组件内使用。于是,我们可以更新 User
的模板,输出当前用户的 ID:
const User = {
template: '<div>User {{ $route.params.id }}</div>'
}
你可以在一个路由中设置多段“路径参数”,对应的值都会设置到 $route.params
中。例如:
模式 | 匹配路径 | $route.params |
---|---|---|
/user/:username | /user/evan | { username: 'evan' } |
/user/:username/post/:post_id | /user/evan/post/123 | { username: 'evan', post_id: 123 } |
除了 $route.params
外,$route
对象还提供了其它有用的信息,例如,$route.query
(如果 URL 中有查询参数)、$route.hash
编程式的导航
除了使用 <router-link>
创建 a 标签来定义导航链接,我们还可以借助 router 的实例方法,通过编写代码来实现。
router.push(location, onComplete?, onAbort?)
注意:在 Vue 实例内部,你可以通过 $router
访问路由实例。因此你可以调用 this.$router.push
。
想要导航到不同的 URL,则使用 router.push
方法。这个方法会向 history 栈添加一个新的记录,所以,当用户点击浏览器后退按钮时,则回到之前的 URL。
当你点击 <router-link>
时,这个方法会在内部调用,所以说,点击 <router-link :to="...">
等同于调用 router.push(...)
。所以通过这个方法我们可以实现重定向的效果,
比如:登录后跳转到首页,可以把首页的路径push到$router中即可
声明式 | 编程式 |
---|---|
<router-link :to="..."> |
router.push(...) |
该方法的参数可以是一个字符串路径,或者一个描述地址的对象。例如:
// 字符串
router.push('home') // 对象
router.push({ path: 'home' }) // 命名的路由
router.push({ name: 'user', params: { userId: 123 }}) // 带查询参数,变成 /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})
在使用动态路由或者编程式的路由时,会有这样一个问题,路由切换后,页面不切换,解决方法:
使用watch监听路由的变化,并做数据的覆盖处理
<template>
// 两种方式触发切换
<!--方式一:通过点击事件的切换-->
<div v-for="(item,index) in theshow.recommend_courses" @click="toaimcourse(item)"><a href="javascript:vild(0);">{{item.title}}</a></div>
<!--方式二:通过动态路由的切换-->
<router-link v-for="(item,index) in theshow.recommend_courses" :to="{ name: 'coursedetail', params: { id:item.id }}">{{item.title}}</router-link> </template> <script>
export default {
name: "vcoursedetail",
data(){
return {
showdetail:this.$store.state.allCourseDetail,
theshow:'',
currcourse:'',
courseall:this.$store.state.allCourse,
descurl:'',
charperurl:''
}
},
created(){
this.courseshowdetail(this.$route.params.id);
this.getcurrentcourse(this.$route.params.id);
},
methods:{
courseshowdetail(nid){ for (var i=0;i<this.showdetail.length;i++){
if (this.showdetail[i].course.id === nid){
this.theshow = this.showdetail[i];
return this.showdetail[i]
}
}
},
getcurrentcourse(nid){ for (var i=0;i<this.courseall.length;i++){
if (this.courseall[i].id === nid){
this.currcourse = this.courseall[i].title;
this.descurl = "/course/coursedetail/"+nid;
this.charperurl = '/course/coursedetail/'+nid+'/charper';
console.log(this.descurl,'文章摘要的url');
return this.courseall[i]
}
}
},
toaimcourse(item){
this.$router.push({ name: 'coursedetail', params: { id:item.id }})
}
},
computed:{ },
watch:{
"$route"(to,from){
this.courseshowdetail(to.params.id);
this.getcurrentcourse(to.params.id);
}
}
} </script>
我们也可以做一个全局的路由操作,比如:在每个组件加载或者路由切换时,判断有没有登录
详情参考:https://router.vuejs.org/zh/guide/advanced/navigation-guards.html
完整的导航解析流程
- 导航被触发。
- 在失活的组件里调用离开守卫。
- 调用全局的
beforeEach
守卫。 - 在重用的组件里调用
beforeRouteUpdate
守卫 (2.2+)。 - 在路由配置里调用
beforeEnter
。 - 解析异步路由组件。
- 在被激活的组件里调用
beforeRouteEnter
。 - 调用全局的
beforeResolve
守卫 (2.5+)。 - 导航被确认。
- 调用全局的
afterEach
钩子。 - 触发 DOM 更新。
- 用创建好的实例调用
beforeRouteEnter
守卫中传给next
的回调函数。
拦截过滤认证
单个组件:
可以在mounted方法中做拦截,通过判断全局变量中的token值,来判断有无登录。
多个组件:可以在全局做拦截
# 在main.js中配置 router.beforeEach(function (to,from,next) {
// meta 用于设置一个标识 所以必须在需要验证是否登录的组件中加一个meta参数 meta:{requireAuth:true}
if (to.meta.requireAuth){
// 表示此时路径是要判断是否登录的
if (store.state.token){
next()
}else{
// query 用于给这个重定向路由设置一个参数 用于在登录成功后重定向的路径
// 设置此参数后需要在登录成功后做一个判断 this.$route.query.backUrl 获取设置的参数
// 有值,表示是需要跳转回的,然后通过重定向将取的值,定位即可
next({path:'/login',query:{backUrl:to.fullPath}})
}
}else{
next()
}
});
bootstrap的使用
在项目中,使用bootstrap时,先在package.json中查看开发环境依赖:
"dependencies": {
"bootstrap": "^3.3.7",
"marked": "^0.4.0",
"vue": "^2.5.16",
"vue-router": "^3.0.1"
},
如果没有,先在当前项目中 npm install xxx
配置好依赖后,再在需要的地方导入: 导入时,对于node_modules文件下的文件,直接导入即可。比如:import 'bootstrap/dist/css/bootstrap.min.css' 在vue-cli中,已经配置了路径,所以直接导入即可。
备注:使用vue-router时,路由的切换会在路径前默认加一个#/,显得url很不好看,所以,可以在实例router对象时加一个mode:"history"
const router = new VueRouter({
mode: 'history',
routes: [...]
})
在使用a标签时,会有一个默认的跳转事件,在vue中,我们可以在相应的事件后加一个.native用来阻止原生的默认事件
@click.native = "xxxx"
参考Vue提供的生命周期图示
下图展示了实例的生命周期。你不需要立马弄明白所有的东西,不过随着你的不断学习和使用,它的参考价值会越来越高。
在vue的实例的生命周期中,vue为我们提供了八个方法:
beforeCreate(在创建之前)、created(创建时的初始化操作,创键的是虚拟DOM)、beforeMount、mounted(挂载,将虚拟DOM转化为真实DOM)、beforeUpdate、updated、beforeDestroy、destroyed(销毁)
我们常用的是 created和mounted这两个方法,其中created方法适用于页面的初始化操作,mounted适用于发送ajax请求数据的操作。
比如:切换路由,页面样式在在此刷新时会出错,可以使用created方法进行一些初始化操作
created(){
for (var i=0;i<this.routes.length;i++){
if (this.routes[i].src === this.$route.path){
this.current=i;
return;
} }
},
vue-cli中2x版本和3x版本修改端口的方法:
在2x版本中:
在3x版本中:
在3x版本中,vue-cli不推荐我们直接在模块中修改配置
推荐这样:在当前项目的目录下直接创建一个js文件 ,文件名必须是 vue.config.js 的形式
在这个js文件中写入:
module.exports = {
devServer: {
open: process.platform === 'darwin',
host: '0.0.0.0',
port: 8088 ,
https: false,
hotOnly: false,
proxy: null, // 设置代理
before: app => {}
}
}
修改完成后重启项目即可。
使用vue-cli打包文件
生产环境中,我们都会讲项目中的文件打包压缩再上线,vue-cli中提供了一种快捷的打包方式。
在我们项目的根目录下运行 npm run build 就会将项目打包,打包后会在项目中多处一个文件夹dist,打开这个dist文件夹
备注:在项目中替换文件时,最好是先将原文件删除,在拖进去,不要直接替换,放置文件合并
在打包时,一定要配置压缩文件中的路径参数。比如:前缀加/static/
vue中的全家桶:vue、vue-router、vuex构成了vue的全家桶,vue是MVVM的架构,vuex--->M vue--->V vue-router--->VM
vue全家桶之vuex
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。
补充:在vue中专门用来发送ajax请求的是axios,详细用法参考:https://www.kancloud.cn/yunye/axios/234845
我们可以在全局的main.js或者store.js中定义一个axios ,将这个axios挂到vue的实例对象中,这样,在vue的子对象中都可以通过this.$axios调用到axios对象。使用axios时,可以通过返回的参数response.data取得返回的数据
import axios from 'axios'
import Vue from 'vue' Vue.prototype.axios = axios
特别注意:使用jQuery的$.ajax发送post请求发送数据时,内部会自动转化为json字符串的形式发送到后端,而使用vue中的axios时,则不行,axios post提交数据时,发送的是普通的值,所有在使用axios发送数据时,如果后端需要json形式的字符串,则需要借助一个模块 qs (npm install qs) 转化或者,JSON.stringfity
还有一个值得注意的地方,在vue的中,我们可以通过this拿到vue的实例对象,但是在axios中由于作用域的原因则不行,console.log(this)得到的是一个undefined,所以我们要在axios的外面声明一个变量接收这个this var _this = this
vue中的mutation:更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。
使用vue-cli时,会在src文件夹下默认创建views、components的两个文件夹,views中存放每一个vue-router(路由)对应的视图,components中存放父子嵌套的组件。
获取v-html指令对应的文本值
使用v-html指令时,如果我们要获取渲染的文本内容,可以给当前标签绑定一个属性ref="变量名",然后在vue的实例中可以通过this.$refs.变量名取得这个标签,在通过DOM方法.innerText取得内容,this.$refs是一个对象
<div id="show" v-html="currentmsg" ref="mark"> // 这个div的文本内容为:
var content = this.$refs.mark.innerText;
备注:不要滥用ref,对性能有损耗,多个标签时,可以使用js方法或者jQuery方法获取文本值 $("show").text()
Action 类似于 mutation,不同在于:
- Action 提交的是 mutation,而不是直接变更状态。
- Action 可以包含任意异步操作。
备注:mutation是同步的,action是异步的, action是用来提交的
用法:
mutation的用法:接收一个state参数作为第一个参数,有其他参数可以直接跟在后面,在mutation的方法中可以直接操作state中的数据,有多个参数时,最好可以将其余的参数作为一个对象,作为方法的第二个参数传值。
在vue的视图中可以直接this.$store.commit("方法名"),唤醒这个方法的执行
action的用法:接收一个context参数作为第一个参数,同样的,后可以跟其他参数,多个参数时,合并为一个object对象。通过context.commit("mutation方法名") 提交, 可以唤醒mutation中的方法
在vue的视图中可以通过this.$store.dispatch('action方法名'),触发action中的对应方法执行
mutation和action的关系图如下:
过滤器(格式化)
1.价格后面加上符号‘元’。
2.价格后面的单位也要动态的传值。(如:元、¥、$)
代码:
<!DOCTYPE html>
<html >
<head>
<meta charset="UTF-8">
<title>过滤器</title>
<script src="bli/vue.js"></script>
</head>
<body>
<div id="app">
<input type="text" v-model="price">
<!-- {{ price | currency }} -->
{{ price | currency('美元') }}
</div>
</body>
<script src="js/14.main.js"></script>
</html>
// 过滤器
Vue.filter('currency',function(data,unit){
data = data || 0; // data有值就等于data,没值就为0.
unit = unit || '元'; // unit有值就等于unit,没值就为'元'.
return data + unit;
// return data + '元';
}); new Vue({
el:'#app',
data:{
price:10,
},
});
3.毫米与米的转换。
4.毫米与米的转换,保留两位小数。
自定义指令-基础配置
1.自定义指令
2.给只为true的元素定位(固定定位)
3.加一个按钮,切换是否定位。
默认都是没有定住的。点击之后定住,再点击之后就是取消定住。
默认、取消定住
定住
4.可以给很多按钮就加上。
默认、取消定位
定位
自定义指令-配置传参及修饰符
以上例子只能定位到左上角,不够灵活。位置应该动态传参。
1.定位到右下角:
打印 var position = binding.modifiers;
console.log('position',position) // position {bottom: true, right: true}
运行结果:已定位到右下角
2.定位到左下角,只要改一个值即可。
3.让一些卡片样式有所不同,突出。
获取该值(:true),设置样式。
混合 mixins
1.点击切换显示隐藏。
默认不显示div.
点击后显示div,再次点击有隐藏div.
2.鼠标移入、移出切换显示隐藏。
默认、移出
移入:
3.点击显示的div应该有个可以关闭的按钮。
注意:这两个组件有好多重复的代码!
点击后隐藏div
混合 mixins(相同的代表放在一起)
功能是一样的。注意:自己写的会覆盖mixins的。
插槽 slots
1.定义一个样式
2.内容都是相同的。动态传参(插槽)
3.如果头部、底部都要动态传参呢???定义name!!!
4.指定默认值。
在Vue的项目中使用了Vue-Router,当某个路由有子级路由时,如下写法:
export default new Router({
routes: [
{
path: '/',
name: 'home',
component: Home,
children:[
{
path:'/',
name: 'console',
component: Console,
}
]
}
]
})
解决办法
因为当某个路由有子级路由的时候,这时候父级路由需要一个默认的路由,所以父级路由不能定义name
属性,SO解决办法是:即去除父级的name
属性即可。