# 基本语法
# 渐进式框架
# 渐进式
- 通过各种指令进行申明式渲染
- 组件系统
- 页面路由
- 大型的状态管理-VUEX
- 构建整个系统
# 框架
- Jquery是JS库,函数的集合,不提供逻辑,逻辑由程序员自己控制
- VUE是JS框架,一整套的解决方案,大部分逻辑已经确定好
# MVVM
一种更好的UI模式解决方案,通过数据双向绑定让数据自动的双向同步
- M:Model数据模型
- V:view试图(页面)
- VM:ViewModel视图模型
# 插值表达式
在data中必须存在: { { msg } }
# 指令(14个)
# v-bind
- 动态地绑定一个或多个属性,或一个组件 prop 到表达式,简写 :
- class和style可以绑定对象或数组
<div class="base fz" :class="obj"></div>
<div class="base fz" :class="arr"></div>
<div class="base fz" :class="{red:true}"></div>//注意单括号
//数据
obj:{base:true,red:true,fz:true}
arr:['base','fz','red']
<div :style="{width:w}"></div>
//数据
w:'200px'
# v-model
在表单控件或者组件上创建双向绑定,会忽略掉表单元素原本的value
修饰符:v-model.lazy(onChange时触发)、v-model.number(输入框的内容转为数字)、v-model.trim(去除数据前后的空格)
- 视图改变数据跟着改变
<p></p>
<input type="text">
<script>
const data={msg:'哈哈哈'};
const p=document.querySelector('p');
const input=document.querySelector('input');
p.innerText=data.msg;
input.value=data.msg;
//键盘弹起时触发,不管弹起的什么键都会触发
input.addEventListener('keyup',function(){
console.log('keyup');
});
//输入完毕后触发
input.addEventListener('change',funtion(){
console.log('change');
})
//只要input框中输入内容就会触发
input.addEventListener('input',function(){
console.log('input');
data.msg=input.value;
})
</script>
- 数据改变视图跟着改变
1、angular.js 1.0版本通过脏数据检查机制(数据轮询),性能比较低,兼容IE8
2、vue使用的数据劫持,ES5的语法:Object.defineProperty(),不兼容IE678
<p></p>
<input type="text">
<script>
const data={msg:'哈哈哈'};
let temp=data.msg;
//作用:给对象的某个属性增加修饰
//参数:对象名、属性名、修饰(是一个对象)
Object.defineProperty(data,'msg',{
//get方法会再获取到msg这个属性的时候执行
//劫持后获取不到msg原来的值,需要先定义let temp=data.msg
get:funtion(){
return temp;
},
//set方法会劫持到msg这个属性的修改操作
set:function(value){
temp=value;
}
})
</script>
# v-on
绑定事件监听,简写 @
修饰符:如果没有,修饰符也可以省略
.stop:阻止冒泡,等效于event.stopPropagation()
.prevent :阻止默认事件,等效于event.preventDefault()
.capture:捕获到事件时使用,先父再子,默认是先子再父(冒泡)
.self:点击元素本身上触发,可能不在子元素上
.once:只触发一次回调
.left:只当点击鼠标左键时触发
.right:只当点击鼠标右键时触发
.middle:只当点击鼠标中键时触发
.passive:以 { passive: true } 模式添加侦听器
.native:监听组件根元素的原生事件
@dblclick:双击事件
@keyup:按键事件 @keyup.enter/tab/delete/esc/space……
//也可以自己定义按键
Vue.config.keyCodes.sylone=13
//使用
@keyup.sylone
//capture捕获到事件,先执行father()再执行child()
<a @click.capture="father()">
<button @click.capture="child()"></button>
</a>
//事件传递参数
//事件处理函数中增加两个参数 $event,item 。 item就是要传递的对象参数
<el-radio v-model="item" label="A" @change="answer($event, item)"></el-radio>
注意
在开发过程中会遇到按键修饰符不生效的情况,此时我们需要加上 .native 按键修饰符
//只适用于 input 框 获得焦点 时按下回车时生效,失去焦点时,此功能仍不可用
<input v-on:keyup.enter.native="submit">
//如果是button按钮,那么应该把它绑定在document上
created: function () {
document.onkeyup = e => {
if (e.keyCode === 13 && e.target.baseURI.match('/')) {
this.onSubmit('form')
}
}
}
# v-text/v-html
- v-text:更新元素的innerText属性(textContent属性),不如插值表达式好用
- v-html:更新元素的innerText属性(textContent属性),可以识别html标签
<div id="app">
<p>{{content}}</p>
<p v-text="text"></p>
<p v-html="html"></p>
</div>
<script>
new Vue({
el:"#app",
data:{
content:"<p>测试差值表达式</p>",
text:"<p>测试v-text指令</p>",
html:"<p>测试v-html指令</p>",
}
})
</script>
# v-show/v-if v-else-if v-else
- v-show:通过display:none隐藏,用于频繁的显示和隐藏
- v-if:通过删除或者创建一个元素来显示或隐藏,如果
# v-for
维护状态:不加key默认使用"就地更新"策略,在有临时状态的元素(checkbox)时会出现bug
使用:key:'index',当然不会报错,但是其实就地更新策略默认用的就是index,建议还是id做key值
key的作用 (opens new window)
# v-pre/v-once
作用时用于性能优化,如果有大量的文字,不需要vue进行编译的时候
不要轻易使用,除非能明显感觉到速度变慢的时候才会使用
- v-pre:会跳过插值表达式的编译
- v-once:插值表达式会编译一次,后续数据更新,插值表达式不会更新
<div id="app">
<div v-pre>{{ msg }}</div>
<div v-once>{{ msg }}</div>
<div>{{ msg }}</div>
</div>
<script src="vue.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
msg: 'hello vue'
}
})
</script>
# v-cloak
用于解决插值表达式的闪烁问题,需要添加样式:[v-cloak]{display:none;}
只有在通过script引用vue.js文件的时候才会用到v-cloak
# 计算属性 computed
- 计算属性性能非常高,基于缓存实现,只有当它依赖的属性发生改变,才会重新执行
- 计算属性的完整形态:如果需要修改计算属性的值
<div id="app">
<input type="text" placeholder="请输入你的姓" v-model="lastName">
<input type="text" placeholder="请输入你的名" v-model="firstName">
<input type="text" placeholder="你的名字是" v-model="fullName">
</div>
<script>
const vm=new Vue({
el:'#app',
data:{
lastName:'',
firstName:''
},
computed:{
//普通用法
fullName:function(){
return this.lastName+''+this.firstName
}
fullName() {
return this.lastName+''+this.firstName
}
//完整形态
fullName:{
get(){
return this.lastName+''+this.firstName
},
set(value){
this.lastName=value.split(' ')[0];
this.firstName=value.split(' ')[1];
}
}
}
})
</script>
# 侦听器watch
使用watch来响应数据的变化,watch的用法大致有三种
# 第一种:简单用法
直接在watch里面写一个监听处理函数,当每次监听到 cityName 值发生改变时,执行函数
<input type="text" v-model="cityName"/>
new Vue({
el: '#root',
data: {
cityName: 'shanghai'
},
watch: {
cityName(newName, oldName) { // ... }
}
})
//也可以在所监听的数据后面直接加字符串形式的方法名
watch: {
cityName: 'nameChange'
}
# 第二种:使用immediate和handler
这样使用watch时有一个特点,就是当值第一次绑定的时候,不会执行监听函数,只有值发生改变才会执行。如果我们需要在最初绑定值的时候也执行函数,则就需要用到immediate属性 比如当父组件向子组件动态传值时,子组件props首次获取到父组件传来的默认值时,也需要执行函数,此时就需要将immediate设为true
new Vue({
el: '#root',
data: {
cityName: ''
},
watch: {
cityName: {
handler(newName, oldName) {
// ...
},
immediate: true
}
}
})
# 第三种:使用deep深度监听
当需要监听一个对象的改变时,普通的watch方法无法监听到对象内部属性的改变,只有data中的数据才能够监听到变化,此时就需要deep属性对对象进行深度监听
<input type="text" v-model="cityName.name"/>
const vm=new Vue({
el: '#root',
data: {
cityName: {id: 1, name: 'shanghai'}
},
watch: {
cityName: {
handler(newName, oldName) { // ... },
deep: true,
immediate: true
}
}
})
设置deep: true 则可以监听到cityName.name的变化,此时会给cityName的所有属性都加上这个监听器,当对象属性较多时,每个属性值的变化都会执行handler。如果只需要监听对象中的一个属性值,则可以做以下优化:使用字符串的形式监听对象属性:
watch: {
'cityName.name': {
handler(newName, oldName) {
// ...
},
deep: true,
immediate: true
}
}
# 第四种:使用$watch进行属性监听
//监听单个属性
vm.$watch('cityName',function(newValue,oldValue){
// ...
})
//监听多个属性
vm.$watch(function(){
return this.name+this.age;
},function(newValue,oldValue){
console.log(newValue+''+oldValue);
})
# 过滤器 filters
常用于格式化我们的文本,其结构为
<div>{{ companyName | Name }}</div>
中间使用 “|”管道符隔开,后面是针对前面关键字携带的参数,过滤器接收的第一个参数是companyName(data中的数据),然后才依次传过滤器的名字
const vm=new Vue({
el:'#app',
data: {
companyName: '浙江杭州阿里巴巴',
},
//局部过滤器 :只有在当前实例中能使用的过滤器
filters: {
Name: function (value) {
//编写事件要求,可以设置对companyName的一些限制,这里是截取前四个字
return value.substr(0, 4);
}
}
})
//全局过滤器 :可以在所有vue实例中都能使用的过滤器
Vue.filter('Name',funtion(value){
return value.substr(0, 4);
})
- 可以传递参数
<div>{{ companyName | Name(4) }}</div>
filters: {
Name: function (value,length) {
return value.substr(0, length);
}
}
- 可以传递多个过滤器
<div>{{ companyName | Name | Name}}</div>
# axios
- vue1.0使用vue-resource,引入它之后可以基于全局Vue对象或者某个vue使用http,支持jsonp跨域
- vue2.0开始使用axios,是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中,不支持jsonp
axios({
method:'post',
url:'/user/list',
params:'id=2', //设置URL地址里面的参数
data:{
firstName:'zhao',
lastName:'sylone'
}
}).then(res=>{
//res.data
}).catch(err=>{ //使用箭头函数
//err
})
注意
then/catch 使用箭头函数
# 跨域设置
withCredentials:是否允许带cookie,true时服务端需要设置SupportsCredentials=true
import axios from 'axios'
import router from './../router'
import comm from './comm'
//axios.defaults.baseURL='/api';
const myAxios = axios.create({
// 只要发送axios请求,就在请求前加入/api的开头,例如 /zzz/one -> /api/zzz/one
baseURL: '/api',
// 设置超时时间 5s
timeout: 10000,
responseType: 'json',
// 是否允许带cookie,true时服务端需要设置SupportsCredentials
withCredentials: false,
headers: {
'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8'
}
})
// POST传参序列化(添加请求拦截器)
myAxios.interceptors.request.use(
config => {
// 在发送请求之前做某件事
if (config.method === 'post' || config.method === 'put' || config.method === 'delete') {
// 序列化
const data = new FormData()
Object.keys(config.data).forEach(item => {
data.append(item, config.data[item])
})
config.data = data
}
if (localStorage.getItem('token')) {
config.headers.Authorization = localStorage.token
}
console.log(config.data)
return config
},
error => {
comm.toast(error.data.message)
return Promise.reject(error.data.message)
}
)
// 返回状态判断(添加响应拦截器)
myAxios.interceptors.response.use(
res => {
// 对响应数据做些事
if (res.data && res.data.code !== 1) {
comm.toast(res.data.msg)
return Promise.reject(res.data.msg)
}
return res.data
},
error => {
// 若是发现有鉴权的基础信息
if (localStorage.getItem('loginUserBaseInfo')) {
// 并且当前的时间大于服务器过期的时间
const loginUserBaseInfo = window.localStorage.getItem('loginUserBaseInfo')
const lifeTime = JSON.parse(loginUserBaseInfo).lifeTime * 1000
const nowTime = new Date().getTime()
if (nowTime > lifeTime) {
localStorage.removeItem('loginUserBaseInfo')
comm.toast('登录状态信息过期,请重新登录')
router.push({
path: '/login'
})
}
} else {
console.log(error)
// 服务器状态码不是200的情况,这些自定义
comm.toast(error.response.data.Message)
}
return Promise.reject(error.response)
}
)
export default myAxios
devServer: {
proxy: {
'/api': {
disableHostCheck:true, //当设置为true时,此选项将绕过主机检查
//默认情况下是/,所以您可以作为http://localhost:8080使用
//更改为 /assets/ 就变为 http://localhost:8080/assets
publicPath: '/assets/',
target: 'http://expo.sdxsk.com/api', //请求接口域名
ws: true,
secure: false,
// 是否允许跨域
changOrigin: true,
// 如果配置了axios的baseURL为'/api',在这里需要用空字符串代替
pathRewrite: {
'^/api': ''
}
}
}
}
//这个是对所有的接口都代理的,不止是检测到 /api 的接口
devServer: {
proxy: 'http://e.dxy.net'
}
在Vue项目中配置了代理,但是部署之后不生效,并且报404
这是因为Vue配置的代理仅在本地开发下有效,部署之后,需要:
- 在nginx中进行配置代理
- 或者后端配置跨域
# DOM的异步更新
- DOM渲染完成后会立即执行nextTick
- vue在数据发生改变的时候,视图会自动跟着改变,但这个过程是异步的,我们在修改完数据后不能立马获取更新后的DOM结构,为了提高渲染的性能,vue中会等待数据都修改完成才会渲染DOM
//全局的
Vue.nextTick(function(){
})
//局部的,自动绑定到调用它的实例上
vm.$nextTick(function(){
})
//或者
//mounted的先执行,再执行nextTick
//此时的nextTick可以获取到dom结构
created(){
this.$nextTick(function(){
console.log('这是执行了nextTick')
})
},
mounted(){
console.log('这是执行了mounted')
}
# 动态添加/修改的属性不是响应式
如果给data中的引用类型(数组或对象)动态添加或修改了一个属性,这个属性不是响应式的
//参数1:需要添加属性的对象,参数2:增加的属性名,参数3:属性的值
Vue.set(this.car,'color','red');
vm.$set(this.car,'color','red');
this.$set(this.car,'color','red');
# ref操作DOM
- 给元素设置ref的属性值,在Vue的实例选项mounted方法中通过this.$refs.属性值 获取到要操作的DOM
- 给组件设置ref的属性值,可以在父组件中使用$refs访问子组件
<div id="app">
<child ref="btn1"></child>
<child ref="btn2"></child>
</div>
<script>
Vue.component('child',{
template:'<button>{{count}}</button>',
data(){
return count:0
},
method(){
hello(){
console.log('hello')
}
this.$on('childmethod',function(){
console.log('我是子组件的方法')
})
}
})
const vm=new Vue({
el:'#app'
})
//获取值
vm.$refs.btn1.count=0;
vm.$refs.btn2.count=0;
//也可以修改值
vm.$refs.btn1.count=1;
vm.$refs.btn2.count=2;
//操作子组件的方法
this.$refs.btn1.hello();
//或者
this.$refs.btn1.$emit('childmethod')
//在组件对象中使用$el获取DOM节点
cont navbar = this.$refs.nav.$el.offsetTop
</script>
<ul @click ="clickfun($event)">
<li></li>
</ul>
methods: {
clickfun(e) {
e.target //是你当前点击的元素
e.currentTarget //是你绑定事件的元素
//获得点击元素的前一个元素
e.currentTarget.previousElementSibling.innerHTML
//获得点击元素的第一个子元素
e.currentTarget.firstElementChild
//获得点击元素的下一个元素
e.currentTarget.nextElementSibling
// 获得点击元素中id为string的元素
e.currentTarget.getElementById("string")
//获得点击元素的class属性
e.currentTarget.getAttributeNode('class')
// 获得点击元素的父级元素
e.currentTarget.parentElement
// 获得点击元素的前一个元素的第一个子元素的HTML值
e.currentTarget.previousElementSibling.firstElementChild.innerHTML
}
}
# 完整的定时器
const vm=new Vue({
el:'#app',
data:{
msg:'张三的速递',
timeID:''
},
methods:{
start(){
//如果有定时器在跑,直接结束
if(this.timeID){
return
}
this.timeID=setInterval(()=>{
//文字跑马灯,每次取字符串的第一个字符放到最后
this.msg=this.msg.slice(1)+this.msg.slice(0,1)
},300)
},
end(){
clearInterval(this.timeID)
//清空定时器ID
this.timeID=''
}
}
})
# 生命周期
const vm=new Vue({
el:'#app',
data:{
msg:'hello vue'
},
beforeCreate(){
console.log('beforeCreate','会在vue实例数据初始化前执行');
},
created(){
console.log('created','会在vue实例数据初始化后执行');
},
beforeMount(){
console.log('beforeMount','在渲染的结构替换el之前执行');
},
mounted(){
console.log('mounted','在渲染的结构替换el之后执行');
},
beforeUpdate(){
console.log('beforeUpdate','数据发生改变,DOM更新之前执行');
},
Updated(){
console.log('beforeUpdate','数据发生改变,DOM更新之后执行');
},
beforeDestory(){
console.log('beforeDestory','vue实例销毁前执行');
},
destoryed(){
console.log('beforeDestory','vue实例销毁后执行');
}
})
# created 创建
钩子函数,类似Uniapp的OnLoad,发送ajax、从缓存读取数据都在此方法内
# mounted 挂载
钩子函数,一般在初始化页面完成后,此时可以dom节点进行相关操作,类似Uniapp的OnReady
通常是为 metheds 函数提前定义( 类似提前声明变量 进入页面内容全部渲染完成后自动引函数),即
mounted() {
this.initData()
},
methods: {
//initData:function() { }的简写
initData() {
}
# 组件使用
模块化:一个JS文件就是一个模块,把一个独立的功能写到一个单独的JS文件,称之为一个模块
组件化:一个组件会包含有结构、样式、功能(js),一个组件就是一个vue实例
//全局组件,在所有的vue实例中都可以使用
//参数1:组件名 参数2:可以配置和vue实例相同的配置 methods/computed/watch/template
Vue.component('demo',{
template:'<div>这是一个全局组件</div>'
//以下是错误的,只能有一个根元素
//template:'<div>hello</div><div>hello</div>'
})
//根组件
const vm=new Vue({
el:'#app',
data:{},
//局部组件
componets:{
demo:{
template:'<div>这是一个局部组件</div>'
}
}
})
注意template
template参数是必须的,并且template中只能有一个根元素
# data属性
组件中的data必须是一个函数,且函数内部需要返回一个对象,这样可以保证每个组件的数据是独立的
Vue.component('demo',{
template:'<div>这是一个全局组件</div>',
data:function(){
return {
money:100
}
}
//或者是
data(){
return {
money:100
}
}
})
# Prop属性
- props中的数据是不能修改的,只读的,单向数据流,防止意外改变父组件的信息
- 如果props传递的是对象,是可以增、删、改对象的某个属性的,但是不提倡这样,不容易定位错误
- html的属性名是忽略大小写的,所以Prop中不能出现大写,或者camelCase(驼峰命名法) 的prop名需要使用其等价的kebab-case(短横线分隔命名)命名
//在 HTML 中是 kebab-case 的
<blog post-title="hello!"></blog>
Vue.component('blog', {
// 在 JavaScript 中是 camelCase 的
props: ['postTitle'], //数组不支持校验
template: '<h3>{{ postTitle }}</h3>'
})
//props校验
Vue.component('demo',{
props:{
propA:Number, //基础类型检测,首字母大写
propA:[ Number,String ], //多种类型检测
propC:{
//必须是字符串
type:String,
required:true
},
propD:{
//有默认值
type:Number,
default:100
}
}
})
//非props属性,会自动合并到子组件上,class和style也会自动合并
<div id="app">
<hello class="aa" style="color:#ff0000"></hello>
</div>
<script>
Vue.component('hello',{
template:'<div class="bb" style="font-size:14px">hello vue</div>'
})
</script>
# template属性
定义模板的四种形式:
- 直接使用字符串
- < script type="text/x-template" id="tpl1">,使用template:"#tpl1"
- 标签< template id="tpl1">,使用template:"#tpl1"
- 使用.vue组件
# is属性
像table、ol、ul、select这种有特殊结构的html标签,不能直接使用组件
<table id="app">
//自定义标签嵌套失效
//<hello></hello>
<tr is="hello"></tr>
</table>
<script>
Vue.component('hello',{
template:'<h1>hello vue</h1>'
})
const vm=new Vue({
el:'#app'
})
</script>
# keep-alive
- 是Vue提供的一个抽象组件,用来对组件进行缓存,而不是销毁它们,从而节省性能,由于是一个抽象组件,所以在页面渲染完毕后不会被渲染成一个DOM元素
- 当组件在keep-alive内被切换时组件的activated、deactivated这两个生命周期钩子函数会被执行
- 当组件使用keep-alive的时候created钩子函数只会被执行一次
//创建一个A组件
<template>
<div>
<h1>我是A组件</h1>
</div>
</template>
<script>
export default {
name: "index",
created(){
console.log("created");
},
activated(){
console.log("activated");
},
deactivated(){
console.log("deactivated");
}
}
</script>
//创建一个B组件
<template>
<div>
<h1>我是B组件</h1>
</div>
</template>
<script>
export default {
name: "index"
}
</script>
//修改App.vue文件
<template>
<div id="app">
<router-link to="/a">切换到a</router-link>
<span>-----</span>
<router-link to="/b">切换到b</router-link>
<keep-alive>
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view></router-view>
</keep-alive>
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
// 当第一次点击切换到A的时候,调用了A组件的create和activeted钩子
// 点击切换到B的时候执行了deactivated钩子函数
// 点击切换到A的时候执行了activeted钩子函数
# 如何强制刷新某些组件
- 在keep-alive激活会触发activated钩子函数 (页面被切换回来,展示在页面上的时候执行)
- 利用include、exclude属性(注意是组件的名字,不是路由的名字)
//include属性表示只有name属性为bookLists,bookLists的组件会被缓存
//其它组件不会被缓存exclude属性表示除了name属性为indexLists的组件不会被缓存
<keep-alive include="bookLists,bookLists">
<router-view></router-view>
</keep-alive>
<keep-alive exclude="indexLists">
<router-view></router-view>
</keep-alive>
- 利用meta属性
export default[
{
path:'/',
name:'home',
components:Home,
meta:{
keepAlive:true //需要被缓存的组件
},
{
path:'/book',
name:'book',
components:Book,
meta:{
keepAlive:false //不需要被缓存的组件
}
]
<keep-alive>
//这里是会被缓存的组件
<router-view v-if="this.$route.meat.keepAlive"></router-view>
</keep-alive>
//这里是不会被缓存的组件
<keep-alive v-if="!this.$router.meta.keepAlive"></keep-alive>
# 动态组件
我们之前在一个多标签的界面中使用 is attribute 来切换不同的组件:
<component v-bind:is="currentTabComponent"></component>
当在这些组件之间切换的时候,你有时会想保持这些组件的状态,以避免反复重新渲染导致的性能问题
<div id="app">
<button @click="page=='index'"></button>
<button @click="page=='news'"></button>
<button @click="page=='login'"></button>
//可以动态调用不同的组件,keep-alive失活的组件将会被缓存
<keep-alive>
<component :is="page"></component>
</keep-alive>
</div>
<script>
Vue.component('index',{
template:'<h1>首页</h1>'
})
Vue.component('news',{
template:'<h1>新闻</h1>'
})
Vue.component('login',{
template:'<h1>登录</h1>'
})
const vm=new Vue({
el:'#app',
data:{
page:'index'
}
})
</script>
# 父传子
- 子组件通过属性props,可以接受父组件传递过来的数据。props:['money','car']
- 父组件需要给子组件传值 < son :money='money' :car='car' >< /son >
<div id='app'>
<son :money='money' :car='car'></son>
</div>
<script>
Vue.component('son',{
template:'<div>这是子组件 {{money}} {{car}}</div>',
props:['money','car']
});
const vm=new Vue({
el:'#app',
data:{
money:1000,
car:'朗逸'
}
})
</script>
# 子传父
- 子组件触发一个自定义事件,通过this.$emit()触发某个实例来传递事件名和参数
- 父组件给子组件注册自定义事件,使用@标记
- 父组件提供一个方法,注册事件时使用的方法
<div id='app'>
<son @son-fn='parentFn'></son>
</div>
<script>
Vue.component('son',{
template:'<div>这是子组件 {{money}} {{car}}
<button @click='fn'>传值给父组件</button>
</div>',
data:{
money:1000,
car:'朗逸'
},
methods:{
fn(){
//传递的参数可以是一个对象
this.$emit('sonFn',this.money,this.car);
}
}
});
const vm=new Vue({
el:'#app',
data:{
money:1000,
car:'朗逸'
},
methods:{
parentFn(money,car){
this.money=money;
this.car=car;
}
}
})
</script>
# 非父子
- 创建一个bus(事件总线:event bus),所有组件都能访问,bus实质是一个空的vue实例
- jack去触发bus的一个自定义事件,可以传递参数
- rose给bus注册这个自定义的事件,提供一个函数,通过这个函数传递参数
- vm.$on 注册当前实例上的自定义事件,可以由vm.$emit触发
//1、注册一个bus:事件总线
const bus=new Vue();
Vue.component('jack',{
template:'<div>我是Jack組件
<button @click='say'>Jack说</button>
</div>',
data:(){
return {
msg:'you jump,i look'
}
},
methods:{
say(){
//2、通过bus触发某个实例
bus.$emit('shuo',this.msg)
}
}
})
Vue.component('rose',{
template:'<div>我是Rose組件
<font>{{msg}}</font>
</div>',
data(){
return {
mag:''
}
},
//3、rose给bus注册事件,这个事件注册的越早越好
created(){
//vm.$on 注册当前实例上的自定义事件,可以由vm.$emit触发
//必须使用箭头函数,否则this指向的是bus
bus.$on('shuo',(msg)=>{
this.msg=msg
})
}
})
# 插槽slot
- 当组件中某一项需要单独定义,那么就应该使用slot
- Vue实现了一套内容分发的API,将slot元素作为承载分发内容的出口
# 插槽的类型
- 默认插槽、匿名插槽:只有一个,不带name的slot会有一个隐含的default
- 具名插槽:带有名字的插槽 name="名字",具名插槽的内容需要包含在template标签中
- 作用域插槽:带有数据的插槽< slot name="header" money="100" car="小黄车" >< /slot >
<div id="app">
<modal>
//具名插槽
<tempalte v-slot:header>
<h3>温馨提示</h3>
</template>
//默认插槽、匿名插槽
<p>确定要删除吗?</p>
//scope:作用域,是一个对象,其中包含插槽中所有的数据
<tempalte v-slot:btnSlot1="scope">
<button>{{scope.aa}}</button>
</template>
<tempalte v-slot:btnSlot2="scope">
<button>{{scope.ab}}</button>
</template>
</modal>
</div>
<script>
Vue.component('modal',{
template:`
<div class="modal">
<div class="top">
//具名插槽
<slot name="header"></slot>
</div>
<div class="content">
//默认插槽、匿名插槽
<slot></slot>
</div>
<div class="bom">
//作用域插槽,value字符串可以随便写
<slot name="btnSlot1" :aa="btn1"></slot>
<slot name="btnSlot2" :ab="btn2"></slot>
</div>
</div>
`,
data(){
return {
btn1:'确定',
btn2:'取消'
}
}
})
const vm=new Vue({
el:'#app',
data:{
msg:'hello vue'
}
})
</script>
# 自定义指令
- 全局指令
- 局部指令
<div id="app">
//必须加 v-
<input type="text" v-focus>
</div>
//全局指令(指令名、对象)
Vue.directive('focus',{
//写指令的5个钩子函数
//表示指令所在的元素已经插入到页面中
//el:指的就是当前的元素
inserted(el){
el.focus();
}
})
const vm=new Vue({
el:'#app',
data:{
msg:hello vue''
},
//局部指令
directives:{
focus:{
inserted(el){
el.focus();
}
}
}
})
# 5个钩子函数
- bind():只会执行一次,在指令绑定到元素上的时候执行,此时元素不一定在页面中显示
- inserted():所在的元素插入到页面中(此时元素在页面中显示)
- update():当指令的值发生改变的时候触发,例如:v-text="msg"
- componentUpdated():当所有的DOM都更新完成的时候触发
- unbind():当指令在DOM元素上移除的时候触发
# 钩子函数的参数
所有钩子函数的参数是一样的
- el:指令所在的DOM元素
- binding:是一个对象,所含以下属性
v-on:click.stop.prevent='clickFn' 对应以下
v-指令名:指令的参数.指令的修饰符.指令的修饰符='指令的值'
<div id="app">
//指令的值必须在vue的data存在
<h1 v-demo:aa.bb.cc="msg">{{msg}}</h1>
</div>
Vue.directive('demo',{
bind(el,binding){
//指令的名字->demo
binding.name
//指令值->hello vue
binding.value
//指令的参数 冒号后面的部分->aa
binding.arg
//指令的修饰符,可以有多个->bb、cc
binding.modifiers
}
})
const vm=new Vue({
el:'#app',
data:{
msg:'hello vue'
}
})
参数的使用
<div id="app">
<h1 v-mybind:title="msg">{{msg}}</h1>
<h1 v-color:bg.bold="color">{{color}}</h1>
<h1 v-myon:click.prevent="clickFn">{{color}}</h1>
</div>
Vue.directive('mybind',{
bind(el,binding){
el.setattribute(binding.arg,binding.vue)
},
//修改msg的时候title的值会跟着变
update(el,binding){
el.setattribute(binding.arg,binding.vue)
}
})
Vue.directive('color',{
bind(el,binding){
if(binding.modifiers.bold){
el.style.fontWeight='bold'
}
},
update(el,binding){
if(binding.modifiers.bold){
el.style.fontWeight='bold'
}
}
})
Vue.directive('myon',{
bind(el,binding){
el.addEventListener(binding.arg,function(e){
binding.value();
if(binding.modifiers.prevent){
e.preventDefault();
}
if(binding.modifiers.stop){
e.stopPropagation();
}
});
},
update(el,binding){
el.addEventListener(binding.arg,function(e){
binding.value();
if(binding.modifiers.prevent){
e.preventDefault();
}
if(binding.modifiers.stop){
e.stopPropagation();
}
});
}
})
//指令简写
//如果自定义指令只需要提供bind和update,并且逻辑是一样的
Vue.directive('color',function(el.binding){
})
const vm=new Vue({
el:'#app',
data:{
msg:'hello vue',
color:'red'
},
methods:{
clickFn(){
}
}
})
# 路由使用
路由是浏览器URL中的哈希值(#hash)与视图组件之间的对应规则
vue中的路由:是hash和component的对应关系,一个哈希值对应一个组件
# 基本使用
- 下载vue-router
- 引入vue-router,一定要在vue.js后面
- 创建一个路由对象
- 关联路由对象和vue实例
const router=new VueRouter();
const vm=new Vue({
el:'app',
data:{
msg:'hello'
},
//指定路由对象
router:router
})
# 具体使用
- 配置路由规则:hash值和组件的映射规则
- 提供组件
- 配置路由的显示出口,组件显示的位置router-view
//方式1:Vue.component('index',Index) 全局注册
//方式2:局部注册
//方式3:配置到路由的规则中
<div>
<p>{{msg}}</p>
<hr>
//配置路由的显示出口
<router-view></router-view>
</div>
//一个对象其实就可以是一个组件,但这个组件默认是是用不了的
const Index={
template:`
<div>我是首页组件</div>
`
}
//方式1:全局注册
Vue.component('index',Index)
//router:一个路由对象,一个项目只有一个路由对象
//route:一条路由规则
const router=new VueRouter({
routes:[
//方式3:配置到路由的规则中
{path:'/index',component:Index},
{path:'/user',component:User},
{path:'/login',component:Login},
]
});
const vm=new Vue({
el:'app',
data:{
msg:'hello'
},
//指定路由对象的简写方式
router,
//方式2:局部注册
components:{
index:Index
}
})
# 关于Vue.use
相信很多人在用Vue使用别人的组件时,会用到 Vue.use() 。例如:Vue.use(VueRouter)、Vue.use(Vuex)
但是用 axios时,就不需要用 Vue.use(axios),就能直接使用,是因为开发者在封装 axios 时,没有写 install 这一步
//1、在 Loading.vue 中定义一个组件
<template>
<div class="loading-box">
Loading...
</div>
</template>
//2、在index.js中引入Loading.vue,并导出
import LoadingComponent from './loading.vue'
const Loading={
// install 是默认的方法
// 当外界在 use 这个组件的时候,就会调用本身的 install 方法,同时传一个 Vue 这个类的参数
install:function(Vue){
Vue.component('Loading',LoadingComponent)
}
}
export default Loading
//3、在main.js中引入loading文件下的index
import Loading from './components/loading/index'
// 这时需要 use(Loading),如果不写 Vue.use()的话,浏览器会报错
Vue.use(Loading)
注意
通过全局方法 Vue.use() 使用插件,Vue.use 会自动阻止多次注册相同插件
# 路由导航
router-link 最终会渲染成a标签
<router-link to="/index">首页</router-link>
const router=new VueRouter({
routes:[
//路由的重定向
{path:'/',redirect:'/index'},
{path:'/index',component:index},
{path:'/user',component:user},
{path:'/login',component:login},
]
})
# 路由命名
{path:'/index',component:index,name:'index'}
//使用
<router-link to="{name:'index'}}">首页</router-link>
# 当前导航
router-link-active:模糊匹配
router-link-exact-active:精确匹配
//让它精确匹配
<router-link to="/" exact></router-link>
- 修改方式一:直接该类的样式
.router-link-exact-active,
.router-link-active{
color:red;
font-size:24px;
}
- 修改方式二:修改默认类名
//全局配置<router-link>默认激活的class类名
const router=new VueRouter({
routes:[],
linkActiveClass:'cur',
linkExactActiveClass:'cur'
})
# 路由嵌套
- 子路由需要配置children
- 子路由path不需要加'/'
- 子路由需要单独加显示的位置 router-view
const index={
template:`
<div>
这里是首页组件
<router-link to='/index/login'>登录</router-link>
<router-link to='/index/reg'>注册</router-link>
<hr>
<router-view></router-view>
//或者
<router-view/>
</div>
`
}
const router=new VueRouter({
routes:[
{path:'/index',component:index,children:[
{path:'reg',component:reg},
{path:'log',component:log},
]}
]
})
# 编程式导航
除了使用router-link 创建声明式导航外,还可以使用router的实例方法,通过编写代码来实现路由的跳转
比如登录成功后跳转到用户中心
methods:{
login(){
//this.$router指整个的路由对象
this.$router.push('index'); // index
this.$router.push({path:'index'}); // index
this.$router.push({name:'index',params:{id:3}}); // index/3
this.$router.push({path:'index',query:{age:30}}); // index?age=30
this.$router.replace({path:'home'}); //替换当前路由
this.$router.go(-1);
}
}
# 动态路由匹配
- $router:整个项目的路由对象,一个项目只有一个
- $route:当前的路由规则 => 路径(当前地址栏中的路径)
const product={
template:`
<div>这是id为{{this.$route.params.id}}的商品</div>
`,
created(){
console.log(this.$router);
console.log(this.$route);
}
}
const router=new VueRouter({
routes:[
// :id 动态路由匹配
{ path:'/product/:id',component:product},
]
})
//$route详解
例:http://blog.1ge0.com/vue/02.html#/product/2?age=18&name=123
fullpath:#后面的内容 "/product/2?age=18&name=123"
params:匹配动态路由的数据 {id:"2"} -> this.$route.params.id -> 需要设置:id 动态路由匹配
query:?后面的内容 "age=18&name=123" -> this.$route.query.age
path:fullpath中除了query的部分"/product/2"
# 路由props
//使用props
<router-link to="/user/12">User</router-link>
const User = {
props: ['id'],
template: '<div>User {{ id }}</div>'
}
const routes = [
{ path: '/user/:id', component: User, props: true }
]
//静态props
<router-link to="/tom">Tim</router-link>
const Tom = {
props: ['id','age'],
template: '<div>Tom {{ id }} {{ age }}</div>'
}
const routes = [
{ path: '/tom', component: Tom, props: {id:2,age:20} }
]
//查询参数
<router-link to="/tim?q=456">Tim</router-link>
const Tim = {
props: ['q'],
template: '<div>Tim {{ q }}</div>'
}
const routes=[
{ path: '/tim', component: Tim, props:(route)=>({q:route.query.q}) }
]
# 命名视图
<div class="app">
<h3>hello vue</h3>
//可以打开多个视图
<router-link to="/user">user</router-link>
<router-view></router-view> //默认视图
<router-view name="a"></router-view> //a视图
<router-view name="b"></router-view> //b视图
</div>
<script>
const user={template:'welcome to you'};
const tim={template:'<h4>tim</h4>'};
const tom={template:'<h4>tom</h4>'};
//只替换默认视图的部分
const routes=[{path:'/user',components:{default:user,a:tim,b:tom}}]
const router=new VueRouter({
routes
})
const vm=new Vue({
el:'#app'
})
</script>
# 多视图控制
<div class="app">
<h3>hello vue</h3>
//可以打开多个视图
<router-link to="/user/12">user</router-link>
<router-view></router-view> //默认视图
<router-view name="a"></router-view> //a视图
<router-view name="b"></router-view> //b视图
</div>
<script>
const user={props:['id'],template:'welcome to you'};
const tim={props:['id'],template:'<h4>tim</h4>'};
const tom={props:['id'],template:'<h4>tom</h4>'};
//只替换默认视图的部分
const routes=[
{
path:'/user',
components:{default:user,a:tim,b:tom},
//只有a和b视图能获取到id的值12
props:{default:false,a:true,b:true}
}
]
const router=new VueRouter({
routes
})
const vm=new Vue({
el:'#app'
})
</script>
# 路由守卫
路由的钩子函数,分为全局的、路由的、组件的
<div id="app">
<h3>hello vue</h3>
<router-link to="/user/12">User-12</router-link>
<router-link to="/def/34">Def-34</router-link>
<router-link to="/def/56">Def-56</router-link>
</div>
<script>
const user={
props:['id'],
template:`<div>User:welcome!{{id}}</div>`
}
//组件的守卫
const ref={
props:['id'],
template:`<div>Ref:welcome!{{id}}</div>`,
beforeRouteEnter(to,from.next){
//在渲染该组件的对应路由前调用
//不能使用this,因为当前组件实例还没创建
console.log('组件def的守卫before……')
next(vm=>{
//通过vm访问组件实例
})
},
beforeRouteUpdate(to,from,next){
//在当前路由改变,组件被复用时调用
//例如:对于带有动态参数的路径/user/:id,在/user/1和/user/2之间跳转的时候
//由于会渲染同样的组件,因此组件实例会被复用,此时就用调用这个钩子函数
//可以使用this
console.log('组件def的守卫update……')
next(vm=>{
//通过vm访问组件实例
})
},
beforeRouteLeave(to,from,next){
//导航离开该组件的时候调用
//可以使用this
console.log('组件def的守卫leave……')
next(vm=>{
//通过vm访问组件实例
})
}
}
//路由的守卫
//与全局守卫相比路由守卫只是对当前路由进行单一控制,参数和全局前置守卫相同
const routes=[
{
path:'/user/:id',component:user,props:true,
beforeEnter:(to,from,next)=>{
console.log('路由守卫before……')
next(vm=>{
//通过vm访问路由实例
})
}
},
{path:'/def:id',component:tom,props:true}
]
//全局守卫
const router=new VueRouter({
routes
})
router.beforeEach((to,from,next)=>{
//前置守卫
console.log('全局守卫开始……')
next(vm=>{
//通过vm访问路由实例
})
})
router.afterEach((to,from,next)=>{
//前置守卫
console.log('全局守卫结束……')
next(vm=>{
//通过vm访问路由实例
})
})
const vm=new Vue({
el:'#app',
router
})
</script>
# 数据获取
- 导航完成之后获取
跳转到页面后,在组件的生命周期created钩子函数中获取数据。在数据获取期间显示“加载中”之类指示 - 导航完成之前获取
页面跳转之前,在组件守卫beforeRouteEnter中获取数据,在数据获取成功后执行导航
<div id="app">
<h3>hello vue</h3>
<router-link to="/user/1">User1</router-link>
<router-link to="/user/2">User2</router-link>
<router-view></router-view>
</div>
<script>
const user= {
template:'<div>user:welcome!</div>',
beforeRouteUpdate(to,from,next){
console.log('导航完成之前获取……')
}
}
const routes=[
{ path:'/user/:id',component:user }
]
const router=new VueRouter({
routes
})
const vm=new Vue({
el:'#app',
router,
created(){
console.log('导航完成之后获取……')
}
})
</script>
# 路由元信息
mate字段来定义路由的额外条件
比如:访问用户中心需要先登录,那么用户未登录访问用户中心应该能被检测到
<div id="app">
<h3>hello vue</h3>
<router-link to="/user/12">User</router-link>
<router-link to="/login">Login</router-link>
<router-view></router-view>
</div>
<script>
const user= { template:'<div>user:welcome!</div>'}
const login= { template:'<div>name:<input/> pass:<input/> <button>登录</button></div>'}
const routes=[
{
path:'/user/:id',component:user,
//元信息,需要登录校验
meta:{ requiresAuth:true }
},
{ path:'login',component:login}
]
const router=new VueRouter({
routes
})
//全局首位检查元信息
router.beforeEach((to,from,next)=>{
//检查匹配的路径元信息,判断是否需要登录
if(to.matched.some(record=>record.meta.requiresAuth)){
let login=false;//从后台查询的
if(!login) {
next({
path:'/login',
query:{ reditect:to.fullPath }
})
}else{
next()
}
}else{
next() //确保一定要调用
}
})
const vm=new Vue({
el:'#app',
router
})
</script>
# 懒加载和魔法注释
魔法注释:对打包起作用
const router=new VueRouter({
routes:[{
path:'about',
name:'About',
component:()=>import(/* webpackChunkName:"about" */ '../views/About.vue')
}]
})
# 路由动画
想要在你的路径组件上使用转场,并对导航进行动画处理,你需要使用 v-slot
<router-view v-slot="{ Component }">
<transition name="fade">
<component :is="Component" />
</transition>
</router-view>
# 单个路由的过渡
上面的用法会对所有的路由使用相同的过渡。如果你想让每个路由的组件有不同的过渡,你可以将元信息和动态的 name 结合在一起,放在transition上
<router-view v-slot="{ Component, route }">
<!-- 使用任何自定义过渡和回退到 `fade` -->
<transition :name="route.meta.transition || 'fade'">
<component :is="Component" />
</transition>
</router-view>
const routes = [
{
path: '/custom-transition',
component: PanelLeft,
meta: { transition: 'slide-left' },
},
{
path: '/other-transition',
component: PanelRight,
meta: { transition: 'slide-right' },
},
]
# 基于路由的动态过渡
也可以根据目标路由和当前路由之间的关系,动态地确定使用的过渡。使用和刚才非常相似的片段
<!-- 使用动态过渡名称 -->
<router-view v-slot="{ Component, route }">
<transition :name="route.meta.transition">
<component :is="Component" />
</transition>
</router-view>
我们可以添加一个 after navigation hook,根据路径的深度动态添加信息到 meta 字段
router.afterEach((to, from) => {
const toDepth = to.path.split('/').length
const fromDepth = from.path.split('/').length
to.meta.transitionName = toDepth < fromDepth ? 'slide-right' : 'slide-left'
})
# 滚动行为
使用前端路由,当切换到新路由时,想要页面滚到顶部,或者是保持原先的滚动位置,就像重新加载页面那样。 我们在创建一个router实例的时候,可以提供一个scrollBehavior方法,该方法会在用户切换路由时触发
# history 模式
vue-router 在 history 模式下,提供了一个 scrollBehavior 方法,用以控制滚动行为,想要页面滚到顶部,或者是保持原先的滚动位置,就像重新加载页面那样
const router = new VueRouter({
routes: [...],
scrollBehavior (to, from, savedPosition) {
// return 期望滚动到哪个的位置
}
})
如下为浏览器页面加载的默认操作,如果没有传入滚动条位置信息(savedPosition),就回到页面的顶部,即返回位置信息 {x:0,y:0}
scrollBehavior (to, from, savedPosition) {
if (savedPosition) {
return savedPosition
} else {
return { x: 0, y: 0 }
}
}
# hash 模式
在 hash 模式下,需要使用官方的导航守卫中的router.beforeEach
router.beforeEach((to, from, next) => {
window.scrollTo(0, 0)
next()
})
# 状态管理
# Module
store模块分割,每个模块拥有自己的State、Mutation、Action、Getter
const store=new Vuex.Store({
modules:{
user,
login
}
})
//user.js
export default {
namespaced: true, //设置模块声明
state: {
userStatus: false,
userInfo: {},
userToken: ''
},
……
}
# State 公共数据源
提供唯一的公共数据源,所有共享的数据都要统一当道Store的State中存储
//创建数据源
const store=new Vuex.Store({
state:{
// 登录状态
loginStatus:false,
// token
userToken:'',
// 用户信息
userInfo:{}
}
})
//第一种访问数据源方式
this.$store.state.userToken
this.store.state.userToken
<div>{{$store.state.userToken}}</div>
//第二种访问数据源方式:辅助函数
//只有mapState没加s
import { mapState } from 'vuex'
computed:{
...mapState(['loginStatus','userToken','userInfo'])
//或者
...mapState({
loginStatus:state=>state.user.userInfo,
userInfo:state=>state.login.userInfo,
userToken:state=>state.user.userToken
})
}
# Getter 加工处理Store中的数据
- Getter用于对Store中的数据进行加工处理形成新的数据,不会改变原数据,类似计算属性
- Store中的数据发生变化,Getter的数据也会发生变化
const store=new Vue.Store({
state:{
count:0
},
getters:{
showNum:state=>{
return '当前的数量是:'+ state.count +''
}
//或者
showNum(state){
return '当前的数量是:'+ state.count +''
}
}
})
//使用方式一
this.$store.getters.showNum
//使用方式二
import {mapGetters} from 'vuex'
computed:{
…mapGetters(['showNum'])
//或者
...mapGetters({
showNum1:state=>state.user.showNum1,
showNum2:state=>state.login.showNum2
})
}
# Mutation 变更State中的数据
不能在方法中执行异步的操作 setTimeOut(()=>{},1000)
const ADD_CART='addCart'
const store=new Vuex.Store({
mutations:{
login(state,userinfo){
state.userInfo =userinfo
state.loginStatus = true
state.userToken = userinfo.UserToken
// 持久化存储
localStorage.userInfo=JSON.stringify(userinfo)
},
//常量事件类型
[ADD_CART](state){
//todo
}
}
})
//触发方式一
this.$store.commit('login',userinfo)
this.store.commit('login',userinfo)
//触发方式二
import { mapMutations } from 'vuex'
methods:{
//mutation依然没有命名空间的概念 所以在定义 mutations 时要注意全局的唯一性
...mapMutations(['login','loginOut'])
...mapMutations('user',['loginUser','loginOutUser'])
...mapMutations('shop',['loginShop','loginOutShop'])
}
//可以直接调用此方法
<button @click="login">点击</button>
<button @click="loginOut">点击</button>
# Action 处理异步任务
在Action中还是要通过触发Mutation的方式间接变更数据
const store=new Vue.Store({
state:{
count:0
},
mutations:{
add1(state){
state.count++
},
add2(state,step){
state.count+=step
},
},
actions:{
addAsync1(context){
setTimeout(()=>{
//mutation中的方法add
context.commit('add1')
},1000)
},
addAsync2(context,step){
setTimeout(()=>{
//mutation中的方法add
context.commit('add2',step)
},1000)
}
}
})
//触发方式一
this.$store.dispatch('addAsync1');
this.$store.dispatch('addAsync2',5);
this.store.dispatch('addAsync1');
this.store.dispatch('addAsync2',5);
//触发方式二
import { mapActions } from 'vuex'
methods:{
...mapAction(['addAsync1','addAsync2'])
...mapAction('user',['loginUser','loginOutUser'])
...mapAction('shop',['loginShop','loginOutShop'])
}
//可以直接调用此方法
<button @click="addAsync1">点击</button>
<button @click="addAsync2(5)">点击</button>
# Mixin混入
定义一部分公共的方法或者计算属性,然后混入到各个组件中使用,方便管理和统一修改,同一个生命周期,混入对象会比组件的先执行
# 基础使用
# 第一步 导出mixins
创建minxins/index.js,写入
export const MixinsFn={
created(){
console.log("这是minxins触发的created")
}
}
# 第二步 引用mixins
import { MixinsFn } from '@/mixins/index.js'
export default {
mixins:[MixinsFn],
created(){
console.log("这是组件触发的created")
}
}