Vue基本模板
Vue.js 是一套构建用户界面的框架
,它不仅易于上手,还可以与其它第三方库整合(Swiper、IScroll、...)。
框架:是一套完整的解决方案;对项目的侵入性
较大,项目如果需要更换框架,则需要重构整个项目。
库(插件):提供某一个小功能,对项目的侵入性
较小,如果某个库无法完成某些需求,可以很容易切换到其它库实现需求。
Vue的优点
🏷 Vue的核心概念之一: 通过数据驱动界面更新,无需操作DOM来更新界面,使用Vue我们只需要关心如何获取数据,如何处理数据,如何编写业务逻辑代码。我们只需要将处理好的数据交给Vue,Vue就会自动将数据渲染到模板中(界面上)。
🏷 Vue的核心概念之二: 组件化开发,我们可以将网页拆分成一个个独立的组件来编写,再通过封装好的组件拼接成一个完整的网页。
Vue基本模板
// 导入Vue.js
<script src="./vue.js"></script>载导入Vue.js
<script>
let vue = new Vue({ // 创建一个Vue的实例对象
el: '#app', // 告诉Vue的实例对象, 将来需要控制界面上的哪个区域
data: {}
});
</script>
MVVM设计模式
M: Model 数据模型(保存数据, 处理数据业务逻辑)
V:View 视图(展示数据, 与用户交互)
VM:View Model 数据模型和视图的桥梁(M是中国人, V是美国人, VM就是翻译)
**MVVM ** 设计模式最大的特点就是支持数据的双向传递,数据可以从 M -> VM -> V,也可以从 V -> VM -> M。
Vue中MVVM的划分:
Vue其实是基于MVVM设计模式的
被控制的区域: View
Vue实例对象 : View Model
实例对象中的data: Model
Vue调试工具安装:Vue.js-devtools
Vue数据绑定过程
Vue会先将未绑定数据的界面展示给用户,然后再根据模型中的数据和控制的区域生成绑定数据之后的HTML代码,最后再将绑定数据之后的HTML渲染到界面上。所以如果用户网络比较慢或者网页性能比较差, 那么用户会看到模板内容,利用v-cloak
配合 [v-cloak]:{display: none}
默认先隐藏未渲染的界面,等到生成HTML渲染之后再重新显示。
<div id="app">
<p v-cloak="">{{ name }}</p>
</div>
v-bind
给"元素"绑定数据,我们可以使用{{}}
、 v-text
、v-html
,如果想给"元素的属性"绑定数据,就必须使用v-bind
,v-bind
的作用是专门用于给"元素的属性"绑定数据的。
v-bind:属性名称="绑定的数据"
:属性名称="绑定的数据"
v-bind
指令给"任意标签"的"任意属性"绑定数据。
:class="['需要绑定类名', ...]"
👉 直接赋值一个类名(没有放到数组中)默认回去Model中查找
:class="需要绑定类名"
👉 数组中的类名没有用引号括起来也会去Model中查找
:class="[需要绑定类名]"
👉 数组的每一个元素都可以是一个三目运算符按需导入
:class="[flag?'active':'']"
👉 可以使用对象来替代数组中的三目运算符按需导入
:class="[{'active': true}]"
👉 绑定的类名太多可以将类名封装到Model中
obj: {
'color': true,
'size': true,
'active': false,
}
:style="{color:'red','font-size':'50px'}"
👉 默认情况下v-bind会去Model中查找绑定的属性
<p :style="color: red">我是段落</p>
👉 不去Model中查找,样式代码需要放到对象中并且取值需要用引号括起来
<p :style="{color: 'red'}">我是段落</p>
👉 将数据放到Model对象中
obj: {
color: 'red',
'font-size': '80px',
}
👉 属性名称包含-, 那么必须用引号括起来
<p :style="{color: 'red', 'font-size': '100px'}">我是段落</p>
👉 需要绑定Model中的多个对象, 可以放到一个数组中赋值
<p :style="[obj1, obj2]">我是段落</p>
v-model
可以将数据绑定到input
标签的value
属性上,但是v-model
是有局限性的,v-model
只能用于input/textarea/select
。
v-on
v-on: 指令专门用于给元素绑定监听事件
v-on:事件名称="回调函数名称"
@事件名称="回调函数名称"
在事件中如事件冒泡,事件捕获,阻止默认行为等,在Vue中通过v-on修饰符来处理,常见修饰符:
.once - 只触发一次回调。
.prevent - 调用 event.preventDefault()。
.stop - 调用 event.stopPropagation()。
.self - 只当事件是从侦听器绑定的元素本身触发时才触发回调。
.capture - 添加事件侦听器时使用 capture 模式。
👉 如果想阻止元素的默认行为, 那么可以使用.prevent修饰符
<a href="http://www.baidu.com" v-on:click.prevent="myFn">标签</a>
👉 如果想阻止事件冒泡, 那么可以使用.stop修饰符
👉 默认情况下是事件冒泡, 如果想变成事件捕获, 那么就需要使用.capture修饰符(从上到下执行)
<div class="a" @click.capture="myFn1">
<div class="b" @click.capture="myFn2">
<div class="c" @click.capture="myFn3"></div>
</div>
</div><!-- 点击myFn3,先执行myFn1 -> myFn2 -> myFn3 -->
👉 如果在绑定的函数中需要用到data中的数据必须加上this
v-on按键修饰符
💡按键修饰符分类:
系统预定义修饰符 <input type="text" @keyup.enter="myFn">
自定义修饰符 先全局定义: Vue.config.keyCodes.f2 = 113 // 113是按键F2对应的code; <input type="text" @keyup.f2="myFn">
自定义指令
自定义全局指令:在任何一个Vue实例控制的区域中都可以使用
// 语法
Vue.directive('自定义指令名称', {
生命周期名称: function (el) {
指令业务逻辑代码
}
});
/*
directive方法接收两个参数
第一个参数: 指令的名称
第二个参数: 对象
🔑 注意点: 在自定义指令的时候, 在指定指令名称的时候, 不需要写v-, 使用时需要加上v-
🔑 注意点: 指令可以在不同的生命周期阶段执行
bind: 指令被绑定到元素上的时候执行
inserted: 绑定指令的元素被添加到父元素上的时候执行
*/
Vue.directive("focus", {
// 这里的el就是被绑定指令的那个元素
inserted: function (el) {
el.focus();
}
});
// 指令生命周期方法
// 自定义指令时一定要明确指令的业务逻辑代码更适合在哪个阶段执行
// 例如: 指令业务逻辑代码中没有用到元素事件, 那么可以在bind阶段执行
// 例如: 指令业务逻辑代码中用到了元素事件, 那么就需要在inserted阶段执行
自定义指令参数:在执行自定义指令对应的方法的时候,除了会传递el给我们,还会传递一个对象给我们,这个对象中就保存了指令传递过来的参数。
<p v-color="curColor">XXX</p> <!-- curColor 是Model中的对象 -->
data: {
curColor: 'green'
},
自定义局部指令:只能在自定义的那个Vue实例中使用
// 语法
'自定义指令名称', {
生命周期名称: function (el) {
指令业务逻辑代码
}
}
// 给创建Vue实例时传递的对象添加
new Vue({
el: '#app',
// 这里就是MVVM中的Model
data: {},
// 专门用于存储监听事件回调函数
methods: {},
// 专门用于定义局部指令的
directives: {
"color": {
// 这里的el就是被绑定指令的那个元素
bind: function (el, obj) {
el.style.color = obj.value;
}
}
}
});
计算属性
🏷 注意点:在定义计算属性的时候是通过一个函数返回的数据,但是在使用计算属性的时候不能在计算属性名称后面加上(),因为它是一个属性不是一个函数(方法)。
<p>{{msg2}}</p>
computed: {
msg2: function () {
return "abcdef".split("").reverse().join("");
}
}
计算属性和函数区别:
函数"不会"将计算的结果缓存起来,每一次访问都会重新求值。
计算属性"会"将计算的结果缓存起来,只要数据没有发生变化,就不会重新求值。
自定义全局过滤器
过滤器和函数和计算属性一样都是用来处理数据的,但是过滤器一般用于格式化插入的文本数据。
全局过滤器:Vue.filter("过滤器名称", 过滤器处理函数):
<!-- 使用方法 -->
{{msg | 过滤器名称}}
:value="msg | 过滤器名称"
<!--Vue会把name交给指定的过滤器处理之后, 再把处理之后的结果插入到指定的元素中-->
<p>{{name | formartStr1 | formartStr2}}</p>
<!--
💡过滤器注意点
只能在插值语法和v-bind中使用
过滤器可以连续使用
-->
局部过滤器: filters: { 'formartStr': function (value) {} }
常用指令
v-once 让界面不要跟着数据变化, 只渲染一次
<p v-once="">{{ name }}</p>
v-text 就相当于过去学习的innerText,会覆盖原有的内容
<p v-text="name">++++++++</p>-->
v-html 就相当于过去学习的innerHTML,会覆盖原有的内容
<p v-text="html">++++++++</p>-->
v-if 和 v-show区别:
v-if: 只要取值为false就不会创建元素,通过v-if来切换组件, 会直接删除和重新创建
v-show: 哪怕取值为false也会创建元素, 只是如果取值是false会设置元素的display为none
v-for: 相当于JS中的for in循环,可以根据数据多次渲染元素,可以 遍历 数组, 字符, 数字, 对象
<li v-for="(value, key) in obj">{{key}}---{{value}}</li>
obj: {
name: "",
age: 33,
gender: "man",
}
过渡动画
- 将需要执行动画的元素放到
transition
组件中 - 当
transition
组件中的元素显示时会自动查找.v-enter/.v-enter-active/.v-enter-to
类名;当transition组件中的元素隐藏时会自动查找.v-leave/ .v-leave-active/.v-leave-to
类名。 - 在
.v-enter
和.v-leave
中指定动画开始的状态,在.v-enter-active
和.v-leave-active
中指定动画执行的状态。
<transition>
<div class="box" v-show="isShow"></div>
</transition>
🧨 注意点:
transition
中只能放一个元素, 多个元素无效,如果想给多个元素添加过渡动画, 那么就必须创建多个transition
组件。- 默认情况下第一次进入的时候没没有动画的,如果想一进来就有动画,可以通过给
transition
添加appear
属性。
<transition appear="" name="one">
<div class="box" v-show="isShow"></div>
</transition>
.one-enter{}
.one-enter-active{}
.one-enter-to{}
- 如果有多个不同的元素需要执行不同的过渡动画,可以通过给
transition
指定name
的方式来指定"进入之前/进入之后/进入过程中,离开之前/离开之后/离开过程中"
对应的类名,来实现不同的元素执行不同的过渡动画。
通过transition
类名的方式确实能够实现过渡效果,但是实现的过渡效果并不能保存动画之后的状态,因为Vue内部的实现是在过程中动态绑定类名,过程完成之后删除类名,正式因为删除了类名,所以不能保存最终的效果。
JS钩子来实现过渡动
过Vue
提供的JS
钩子来实现过渡动画,可以在Vue中保存过渡最终的效果:
v-on:before-enter="beforeEnter" 进入动画之前
v-on:enter="enter" 进入动画执行过程中
v-on:after-enter="afterEnter" 进入动画完成之后
v-on:enter-cancelled="enterCancelled" 进入动画被取消
<transition appear="" v-bind:css="false" v-on:before-enter="beforeEnter" v-on:enter="enter" v-on:after-enter="afterEnter">
<div class="box" v-show="isShow"></div>
</transition>
<!--
注意点: 虽然我们是通过JS钩子函数来实现过渡动画,但是默认Vue还是回去查找类名, 所以为了不让Vue去查找类名,可以给transition添加 v-bind:css="false"
-->
beforeEnter(el){
el.style.opacity = "0"; // 进入动画开始之前
},
enter(el, done){ // 进入动画执行过程中
// 注意点: 如果是通过JS钩子来实现过渡动画,那么必须在动画执行过程中的回调函数中写上 el.offsetWidth / el.offsetHeight
el.offsetHeight;
el.style.transition = "all 3s";
// 注意点: 动画执行完毕之后一定要调用done回调函数,否则后续的afterEnter钩子函数不会被执行
// 注意点: 如果想让元素一进来就有动画, 那么最好延迟以下再调用done方法
setTimeout(function () {
done();
}, 0);
},
afterEnter(el){ // 进入动画执行完毕之后
el.style.opacity = "1";
el.style.marginLeft = "500px";
}
Velocity实现过渡动画
导入Velocity库,在动画执行过程钩子函数中编写Velocity动画。https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js
<script src="../velocity.min.js"></script>
beforeEnter(el){
},
enter(el, done){
Velocity(el, {opacity: 1, marginLeft: "500px"}, 1000);
},
afterEnter(el){
}
自定义类名动画
在Vue中除了可以使用默认类名(v-xxx)来指定过渡动画、自定义类名前缀(yyy-xx)来指定过渡动画(transition name="yyy")、使用 JS钩子函数来指定过渡动画以外,还可以使用自定义类名的方式来指定过渡动画。
.a{ opacity: 0; } .b{ opacity: 1; margin-left: 500px; } .c{ transition: all 3s; }
<transition appear="" enter-class="a" enter-active-class="c" enter-to-class="b">
<div class="box" v-show="isShow"></div>
</transition>
Animate.css实现过渡动画
<link href="https://cdn.jsdelivr.net/npm/animate.css@3.5.1" rel="stylesheet" type="text/css">
<transition appear="" enter-class="" enter-active-class="animated bounceInRight" enter-to-class="">
<div class="box" v-show="isShow"></div>
</transition>
同时给多个元素添加过渡动画
通过transition可以给单个元素添加过渡动画,如果想给多个元素添加过渡动画, 那么就必须通过transition-group来添加。transition-group和transition的用法一致,只是一个是给单个元素添加动画,一个是给多个元素添加动画而已。
<transition-group appear="" tag="ul">
<li v-for="(person,index) in persons" :key="person.id">
<input type="checkbox">
<span>{{index}} --- {{person.name}}</span>
</li>
</transition-group>
🧨 注意点:
- 默认情况下transition-group会将动画的元素放到span标签中,可以通过tag属性来指定将动画元素放到什么标签中。
-
v-for
v-for为了提升性能,在更新已渲染过的元素列表时,会采用“就地复用”策略,“就地复用”策略只和html元素有关,和内部的值无关。也正是因为这个策略,在某些时刻会导致我们的数据混乱。为了解决这个问题,我们可以在渲染列表的时候给每一个元素加上一个独一无二的key,v-for在更新已经渲染过的元素列表时,会先判断key是否相同,如果相同则复用,如果不同则重新创建。
不能使用index的作为key,因为当列表的内容新增或者删除时index都会发生变化。
自定义全局组件
Vue两大核心:1、数据驱动界面改变 2、组件化
自定义全局组件特点:在任何一个Vue实例控制的区域中都可以使用
let Profile = Vue.extend({ // 注意点: 在创建组件指定组件的模板的时候, 模板只能有一个根元素; 里面内容和定义组件一样
template: `
<div>
<div>全局组件</div>
<p>---描述信息--</p>
</div>
`
});
/* Profiles返回一个方法
ƒ VueComponent (options) {
this._init(options);
}
*/
new Profile() // 直接创建一个vue对象, VueComponent{}
// 注册已经创建好的组件
// 第一个参数: 指定注册的组件的名称
// 第二个参数: 传入已经创建好的组件构造器
Vue.component("abc", Profile); // vue文件内部都能直接使用组件 abc
// 也可以直接
Vue.component("abc", { template: "#abc" });
自定义局部组件
自定义局部组件特点:只能在自定义的那个Vue实例控制的区域中可以使用
在vue实例中新增components: {}
在{}中通过key/vue形式注册组件
components: {
"abc": { template: "#info" } // info是一个区域的ID,被template包围
}
🧨 自定义组件中data注意点:在自定义组件中不能像在vue实例中一样直接使用data(data:{要使用的数据}),而是必须通过返回函数的方式来使用data(data() {return{要使用的数据}})。因为自定义组件可以复用, 为了保证复用时每个组件的数据都是独立的,所以必须是一个函数。 组件中的data如果不是通过函数返回的,那么多个组件就会公用一份数据,就会导致数据混乱;如果组件中的data是通过函数返回的,那么每创建一个新的组件,都会调用一次这个方法,将这个方法返回的数据和当前创建的组件绑定在一起,这样就有效的避免了数据混乱。
动态组件
component
称之为动态组件,可以根据用户指定名称显示对应的组件。component
可以配合keep-alive
来保存被隐藏组件隐藏之前的状态,比用v-if好。
<keep-alive>
<component v-bind:is="name"></component>
</keep-alive>
keep-alive
Props:
include - 字符串或正则表达式,只有名称匹配的组件才会被缓存,多个名称逗号隔开。
exclude - 字符串或正则表达式,任何匹配名称的组件都不会被缓存,多个名称逗号隔开。
max - 数字,最多可以缓存多少组件实例。
组件动画
给组件添加动画和过去给元素添加动画一样,如果是单个组件就使用transition
,如果是多个组件就使用transition-group
。
🧨 过渡动画注意:默认情况下两个组件切换,进入动画和离开动画是同时执行的,如果想一个做完之后再做另一个,需要指定动画模式。
<transition mode="out-in">
<component v-bind:is="name"></component>
</transition>
父子组件方法传递
在父组件中通过v-on传递方法,传递格式 v-on:自定义接收名称 = "要传递方法"
。在子组件中自定义一个方法,在自定义方法中通过 this.$emit('自定义接收名称');
触发传递过来的方法
组件中的命名规范
注册组件的时候使用了"驼峰命名"
, 那么在使用时需要转换成"短横线分隔命名"
;例如: 注册时: myComponent
-> 使用时: my-component
。
传递参数的时候如果想使用"驼峰命名"
, 那么就必须写"短横线分隔命名"
;例如: 传递时: :parent-name="name"
-> 接收时: props: ["parentName"]
。
传递方法的时候不能使用"驼峰命名"
, 只能用"短横线分隔命名"
;@parent-say="say"
-> this.$emit("parent-say");
。
匿名插槽
插槽:默认情况下使用子组件时在子组件中编写的元素是不会被渲染的,如果子组件中有部分内容是使用时才确定的,那么我们就可以使用插槽;插槽就是在子组件中放一个"坑", 以后由父组件来"填"。
匿名插槽
没有名字的插槽, 会利用使用时指定的内容替换整个插槽。注意点: 如果有多个匿名插槽, 每一个匿名插槽都会被指定的内容替换,虽然写多个匿名插槽不会报错, 但是在开发中推荐只能有一个匿名插槽。
<template id="father">
<div>
<son>
<div>我是追加的内容1</div>
<div name="one">我是追加的内容2</div> <!-- 具名插槽 -->
</son>
</div>
</template>
<template id="son">
<div>
<div>我是头部</div>
<slot>我是默认数据</slot> <!-- 被<div>我是追加的内容1</div>替换 -->
<slot name="one">我是默认内容</slot> <!-- 被<div name="one">我是追加的内容2</div>替换 -->
<div>我是底部</div>
</div>
</template>
具名插槽
默认情况下有多少个匿名插槽,我们填充的数据就会被拷贝多少份,这导致了所有插槽中填充的内容都是一样的。如果想给不同的插槽中填充不同的内容就可以使用具名插槽。
具名插槽通过插槽的name属性给插槽指定名称,在使用时可以通过slot="name"方式,指定当前内容用于替换哪一个插槽。
v-slot指令
v-slot指令是Vue2.6中用于替代slot属性的一个指令,在Vue2.6之前,我们通过slot属性告诉Vue当前内容填充到哪一个具名插槽,从Vue2.6开始,我们通过v-slot指令告诉Vue当前内容填充到哪一个具名插槽。
注意点: v-slot指令只能用在template标签上,可以使用#号替代v-slot。
<template id="father">
<div>
<son>
<template v-slot:one="">
<div>我是追加的内容1</div>
</template>
<template #one=""> <!-- 简写 -->
<div>我是追加的内容1</div>
</template>
</son>
</div>
</template>
<template id="son">
<div>
<div>我是头部</div>
<slot name="one">我是one默认内容</slot>
<div>我是底部</div>
</div>
</template>
作用域插槽
作用域插槽就是带数据的插槽,就是让父组件在填充子组件插槽内容时也能使用子组件的数据。
<!-- 在slot中通过 v-bind:数据名称="数据名称" 方式暴露数据 -->
<!-- 在父组件中通过 <template slot-scope="作用域名称"> 接收数据 -->
<!-- 在父组件的<template></template>中通过 作用域名称.数据名称 方式使用数据 -->
<template id="father">
<div>
<son>
<template slot-scope="abc"> <!--abc对象里就是拿到的数据-->
<li v-for="(name, index) in abc.names">{{name}}</li>
</template>
</son>
</div>
</template>
<template id="son">
<div>
<div>我是头部 {{names}}</div>
<slot v-bind:names="names">我是默认内容 {{names}}</slot> <!--v-bind:names="names"作用: 将当前子组件的names数据暴露给父组件-->
<div>我是底部</div>
</div>
</template>
可以使用 v-slot 命令简写
<template v-slot:default="abc"> <!-- 匿名插槽用法 -->
<li v-for="(name, index) in abc.names">{{name}}</li>
</template>
<template #one="abc"> <!-- 具名插槽 -->
<li v-for="(name, index) in abc.names">{{name}}</li>
</template>
<slot name="one" v-bind:names="names">我是默认内容 {{names}}</slot>
Vuex
vuex 是 Vue 配套的 公共数据管理工具,它可以把一些共享的数据,保存到 vuex 中,方便整个程序中的任何组件直接获取或修改我们的公共数据。
注意点:只有需要共享的才放到vuex上,不需要共享的数据依然放到组件自身的data上。兄弟组件之间不能直接传递数据, 如果兄弟组件之间想要传递数据, 那么就必须借助父组件(非常麻烦),解决方案: 使用Vuex。
// 创建Vuex对象
const store = new Vuex.Store({
state: { // state: 用于保存共享数据
count: 0
},
mutations: { // state: 用于保存共享数据
// 注意点: 在执行mutations中定义的方法的时候,系统会自动给这些方法传递一个state参数,state中就保存了共享的数据
mAdd(state){
state.count = state.count + 1;
},
mSub(state){
state.count = state.count - 1;
}
},
getters: {
formart(state){
return state.msg + "www"
}
}
});
store: store // 组件中引用
{{this.$store.state.msg}} // 组件中使用
{{formart}}
getters:Vuex的getters属性就和组件的计算属性一样, 会将数据缓存起来, 只有数据发生变化才会重新计算。
VueRouter
Vue Router
和v-if/v-show
一样,是用来切换组件的显示的,v-if/v-show
是标记来切换(true/false)
,Vue Router
用哈希来切换(#/xxx)
,比v-if/v-show
强大的是Vue Router
不仅仅能够切换组件的显示,还能够在切换的时候传递参数。
Vue Router使用:
- 导入Vue Router
- 定义路由规则
const routes = [ // 数组中的每一个对象就是一条规则
{ path: '/one', component: one },
{ path: '/two', component: two }
];
- 根据路由规则创建路由对象
const router = new VueRouter({
routes: routes
});
- 将路径对象挂载到Vue实例中
let vue = new Vue({
el: '#app',
router: router, // 将创建好的路由对象绑定到Vue实例上
components: {
one: one,
two: two
}
});
- 修改URL哈希值
- 通过
渲染匹配的组件
<div id="app">
<a href="#/one">切换到第一个界面</a> <!-- URL哈希值 -->
<a href="#/two">切换到第二个界面</a>
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view></router-view>
</div>
router-link
通过a标签
确实能设置URL
的hash
,但是这种方式并不专业,在Vue Router
中提供了一个专门用于设置hash
的标签 router-link
。
router-link特点
:
- 默认情况下Vue会将router-link渲染成a标签,但是我们可以通过tag来指定到底渲染成什么。
<div id="app">
<router-link to="/one" tag="button">切换到第一个界面</router-link>
<router-link to="/two" tag="button">切换到第二个界面</router-link>
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view></router-view>
</div>
- 默认情况下我们可以通过重写router-link-active类名来实现设置选中样式,但是我们也可以通过linkActiveClass来指定选中样式。
.router-link-active{}
.my-active{}
const router = new VueRouter({
routes: routes,
linkActiveClass: "my-active" // 指定导航激活状态样式类名
});
- 重定向路由,
const routes = [
{ path: '/', redirect: '/two' }, // 重定向路由
{ path: '/one', component: one },
{ path: '/two', component: two }
];
参数传递
只要将Vue Router挂载到了Vue实例对象上,我们就可以通过vue.$route拿到路由对象;
- 通过URL参数参数(
?key=value&key=value
), 通过this.$route.query
获取 - 通过占位符传递(路由规则中
/:key/:key
, 路径中/value/value
), 通过this.$route.params
获取
嵌套路由
嵌套路由也称之为子路由,就是在被切换的组件中又切换其它子组件,例如: 在one界面中又有两个按钮,通过这两个按钮进一步切换one中的内容。
<div id="app">
<router-link to="/one" tag="button">切换到第一个界面</router-link>
<router-view></router-view>
</div>
<template id="one">
<div class="onepage">
<p>我是第一个界面</p>
<router-link to="/one/onesub1" tag="button">切换到第一个子界面</router-link>
<router-link to="/one/onesub2" tag="button">切换到第二个子界面</router-link>
<router-view></router-view>
</div>
</template>
const routes = [
{
path: '/one',
component: one,
children:[
{
path: "onesub1", // 嵌套路由(子路由), 不用写一级路径的地址, 并且也不用写/
component: onesub1
}
{
path: "onesub2",
component: onesub2
}
]
},
];
命名视图
命名视图和具名插槽很像,都是让不同的出口显示不同的内容;命名视图就是当路由地址被匹配的时候同时指定多个出口,并且每个出口中显示的内容不同。
<div id="app">
<!--
和具名插槽一样, 如果想同时显示多个不同的组件, 那么可以给出口指定名称;在路由规则中给组件起名称,在出口中指定显示哪个名称的组件
-->
<router-view name="name1"></router-view>
<router-view name="name2"></router-view>
</div>
const routes = [
{
path: '/',
components: {
name1: one,
name2: two
}
},
];
Watch属性
Watch属性是专门用于监听数据变化的,只要数据发生了变化,就会自动调用对应数据的回调方法;Watch属性不仅仅能够监听数据的变化,还能够监听路由地址的变化,在开发中我们可以通过Watch来判断当前界面是从哪个界面跳转过来的。
watch: {
"$route.path": function (newValue, oldValue) {
console.log(newValue, oldValue);
}
},
Vue-生命周期方法
vue
生命周期方法和webpack
生命周期方法一样,都是在从生到死的特定阶段调用的方法。
创建期间的生命周期方法 | 运行期间的生命周期方法 | 销毁期间的生命周期方法 |
---|---|---|
beforeCreate: created: beforeMount: mounted: |
beforeUpdate: updated: |
beforeDestroy: destroyed: |
![image-20210425214447147](https://img2020.cnblogs.com/blog/948662/202106/948662-20210614143909441-987855372.png)
Vue的特殊特性
Vue特点
:数据驱动界面更新, 无需操作DOM来更新界面,也就是说Vue不推荐我们直接操作DOM,但是在开发中确实需要拿到DOM操作DOM,可以通过ref
来获取。
ref
使用:
- 在需要获取的元素上添加ref属性。例如:
<p ref="mypp">我是段落</p>
- 在使用的地方通过
this.$refs.xxx
获取,例如this.$ref.myppp
。 ref
添加到元素DOM上,拿到的就是元素DOM,ref添加到组件上,拿到的就是组件。
组件渲染方式
Vue渲染组件的两种方式:
- 先定义注册组件, 然后在Vue实例中当做标签来使用
- 先定义注册组件, 然后通过Vue实例的render方法来渲染
两种渲染方法的区别:
- 当做标签来渲染, 不会覆盖Vue实例控制区域
- 通过render方法来渲染, 会覆盖Vue实例控制区域
<div id="app">
<!-- <one></one> 标签方式-->
</div>
<template id="one">
<div>
<p>我是组件one</p>
</div>
</template>
let vue = new Vue({
el: '#app',
render: function(createElement){
let html = createElement("one"); // createElement传入要渲染覆盖实例控制区域的组件id
return html; // html是一个VNode
}
});
Vue-CLI3基本使用
Vue-CLI(Command Line Interface)
,vue官方提供的脚手架工具,默认已经帮我们搭建好了一套利用webpack管理vue的项目结构。
安装脚手架工具: npm install -g @vue/cli
检查是否安装成功: vue --version
通过脚手架创建项目: vue create project-name
在Vue-CLI2.x
中生成的项目结构中我们能够看到build
文件夹和config
文件夹,这两个文件夹中存储了webpack
相关的配置,我们可以针对项目需求修改webpack
配置。
在Vue-CLI3
以后生成的项目结构中已经没有了build
文件夹和config
文件夹,这么做的目的是为了化繁为简, 让初学者不用关心webpack
, 只用关心如何使用Vue
。
node_modules
文件夹存储了依赖的相关的包。
public
文件夹,任何放置在 public
文件夹的静态资源都会被简单的复制,而不经过 webpack。需要通过绝对路径来引用它们。一般用于存储一些永远不会改变的静态资源或者webpack
不支持的第三方库。
src
文件夹代码文件夹:
|----assets文件夹: 存储项目中自己的一些静态文件(图片/字体等)
|----components文件夹: 存储项目中的自定义组件(小组件,公共组件)
|----views文件夹: 存储项目中的自定义组件(大组件,页面级组件,路由级别组件)
|----router文件夹: 存储VueRouter相关文件
|----store文件夹: 存储Vuex相关文件
|----App.vue:根组件
|----main.js:入口js文件
// vue.config.js
const path = require('path');
const webpack = require('webpack');
module.exports = {
// output: {
// path: path.resolve(__dirname, 'bundle')
// }
// Vue-CLI为了方便起见对webpack原有的属性进行了一层封装,如果我们需要修改webpack的配置,那么我们可以在项目中新建一个vue.config.js的文件,然后去查询Vue-CLI的封装是否能够满足我们的需求,如果可以满足我们的需求,那么就使用Vue-CLI封装的属性来修改webpack的配置。如果不可以满足我们的需求, 那么我们可以通过configureWebpack的属性来编写原生的webpack配置
outputDir: 'bundle',
configureWebpack: { // 可以在这个对象中编写原生的webpack配置
plugins: [
new webpack.BannerPlugin({ // 添加版权插件
banner: 'test'
})
]
}
}
ElementUI-MintUI-Vant
ElementUI
是饿了么前端团队推出的一款基于Vue
的桌面端UI框架,大白话: 和Bootstrap
一样对原生的HTML标签进行了封装,进行了美化,让我们能够专注于业务逻辑而不是UI界面。
MintUI
是饿了么前端团队推出的一款基于Vue
的移动端UI框架。
Vant
是有赞前端开发团队又推出的一款基于Vue
的移动端UI框架,在使用MintUI
的过程中发现有很多的坑,所以个人不推荐在移动端中选择MintUI
。
注意点:
在MintUI
中哪怕是按需引入也必须导入CSS文件;
MintUI
和ElementUI
的第一个不同, 就是在MintUI中需要通过Vue.component来告诉Vue我们需要使用哪个组件
import { Button } from 'element-ui'
Vue.use(Button)
import { Button } from 'mint-ui'
Vue.component(Button.name, Button)
Plugin
Vue.use()
的作用是注册一个Vue插件(注册组件), Vue.use
必须在new Vue
之前使用。
如何自定义一个插件:https://cn.vuejs.org/v2/guide/plugins.html#开发插件
// 当某一个组件或者功能经常需要被使用到时,我们就可以将这个组件或者功能定义成一个插件,例如: 网络加载指示器。
import Loading from './components/Loading'
Vue.component(Loading.name, Loading)
// 注册好之后可以在其他组件中任意使用
// Vue.use 方式注册
import Loading from './plugin/loading/index'
// 注意点: 如果想通过use的方式来注册组件, 那么必须先将组件封装成插件
Vue.use(Loading, { // use的时候会执行index.js里面的install方法,注册我们需要用到的方法
title: '正在加载中...' // 组件传值
})
// ./plugin/loading/index.js 一个index.js对应一个组件
// import Vue from 'vue'
import Loading from './Loading'
export default {
// 注意点: 如果要将一个组件封装成一个插件, 那么必须提供一个install方法 那么必须在install方法中注册当前的这个组件
install: function (Vue, Options) { // 方法的第一个参数是Vue构造器,第二个参数是一个可选的选项对象,所以不用手动导入vue
// Vue.component(Loading.name, Loading) // 注册组件,main.js中use后即可全局显示
// 推荐下面这种使用方式
// 1.根据我们的组件生成一个构造函数
let LoadingContructor = Vue.extend(Loading)
// 2.根据构造函数创建实例对象
let LoadingInstance = new LoadingContructor()
// 3.随便创建一个标签(元素)
let oDiv = document.createElement('div')
// 4.将创建好的标签添加到界面上
document.body.appendChild(oDiv)
// 5.将创建好的实例对象挂载到创建好的元素上
LoadingInstance.$mount(oDiv)
// 添加初始化值
if (Options && Options.title !== null && Options.title !== undefined) {
LoadingInstance.title = Options.title // 修改组件中的title属性值
}
// 添加全局方法,loading组件中通过isShow来判断是否显示
Vue.showLoading = function () { // Vue.showLoading() // 其他组件中通过这种方式调用显示组件
LoadingInstance.isShow = true
}
Vue.hiddenLoading = function () {
LoadingInstance.isShow = false
}
// 添加实例方法,推荐这种,可以在其他组件中直接 this.$showLoading() 来显示/隐藏loading组件。
Vue.prototype.$showLoading = function () {
LoadingInstance.isShow = true
}
Vue.prototype.$hiddenLoading = function () {
LoadingInstance.isShow = false
}
}
}
项目设置
1.利用rem+视口释放的方式来适配移动端,注意点: 如果在HTML文件中用到了字符串模板, 字符串模板中用到了变量, 那么html-plugin是无法处理的, 如果想解决这个问题, 那么我们需要再借助一个loader, html-loader
2.借助postcss-pxtorem实现自动将px转换成rem
3.借助webpack实现CSS3/ES678语法的兼容
4.借助fastclick解决移动端100~300ms的点击事件延迟问题
项目组件按需加载
// 项目使用router配置的index.js中,通过 improt xxx from xxx的方式加载组件, 无论组件有没有被用到, 都会被加载进来,为了提高性能,可以使用按需加载。
const Recommend = (resolve) => {
import('../views/Recommend').then((module) => { // module为加载成功后的组件
resolve(module)
})
}
const Detail = (resolve) => {
import('../views/Detail').then((module) => {
resolve(module)
})
}
axio网络工具封装
import axios from 'axios'
// 进行一些全局配置
axios.defaults.baseURL = 'http://127.0.0.1:3000/'
// axios.defaults.baseURL = 'http://192.168.0.101:3000/'
axios.defaults.timeout = 5000
// 添加请求拦截器
axios.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
return config
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error)
})
// 添加响应拦截器
axios.interceptors.response.use(function (response) {
// 对响应数据做点什么
return response
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error)
})
观察者监听数据变化
// 1.创建一个观察者对象
/*
MutationObserver构造函数只要监听到了指定内容发生了变化, 就会执行传入的回调函数
mutationList: 发生变化的数组
observer: 观察者对象
*/
let observer = new MutationObserver((mutationList, observer) => {
// 执行数据变化后的回调函数
})
// 2.告诉观察者对象我们需要观察什么内容
let config = {
childList: true, // 观察目标子节点的变化,添加或者删除
subtree: true, // 默认为 false,设置为 true 可以观察后代节点
attributeFilter: ['height', 'offsetHeight'] // 观察特定属性
}
// 3.告诉观察者对象, 我们需要观察谁, 需要观察什么内容
/*
第一个参数: 告诉观察者对象我们需要观察谁
第二个参数: 告诉观察者对象我们需要观察什么内容
*/
observer.observe(this.$refs.wrapper, config)
打包部署
npm run build
const router = new VueRouter({
//注意点: 如果Router中使用的是history模式,那么打包之后的项目不能刷新,刷新就会出现404. 解决方案: 1.Router这两个不要使用history模式,使用hash模式; 2.在服务端上面进行一些额外的配置
// 注意点: 如果需要使用预渲染的插件, 那么Router的模式必须是history模式
mode: 'history',
// mode: 'hash',
base: process.env.BASE_URL,
routes
})
SPA
单页Web应用(SPA - Single Page web Application)
,也就是说只有一个HTML文件的Web应用,我们就称之为单页Web应用,就称之为SPA应用。
SPA
的特点:
- SPA应用只有一个HTML文件, 所有的内容其实都在这个页面中呈现的。
- SPA应用只会加载一次HTML文件, 不会因为用户的操作而进行页面的重新加载;当用户与应用程序交互时, 是通过动态更新页面内容的方式来呈现不同的内容。
SPA
的优点:
- 有良好的交互体验,不会重新加载整个网页,只是局部更新。
- 前后端分离开发,前端负责页面呈现和交互,后端负责数据。
- 减轻服务器压力,只用处理数据不用处理界面。
SPA
的缺点:
- SEO(Search Engine Optimizee)难度较高,只有一个界面, 无法针对不同的内容编写不同的SEO信息。
- 初次加载耗时多,为实现单页Web应用功能及显示效果,需要在加载页面的时候将所有JavaScript、CSS统一加载。在Vue中可以使用按需加载解决
三种网页渲染方式
客户端渲染(CSR - Client Side Render)
后端只提供数据,前端做视图和交互逻辑(SPA应用就是典型的客户端渲染).
客户端渲染过程:
1. 客户端请求html (请求)
2. 服务端返回html
3. 客户端渲染HTML,找到依赖的JS/CSS文件
3. 客户端请求对应的JS/CSS文件 (请求)
4. 服务端返回对应的JS/CSS文件
5. 客户端等待JS/CSS文件下载完成
6. 客户端加载并初始化下载好的JS文件
7. 客户端执行JS代码向后端请求数据 (请求)
8. 服务端返回数据
9. 客户端利用服务端返回的数据填充网页
最大优点: 交互体验好可以做局部更新
最大缺点: 首屏加载慢(因为要等到HTML下载完才会去下载JS/CSS, 要等到JS下载完初始化完才会去获取数据),
服务端渲染(SSR - Server Side Render)
后端既提供数据又提供视图和交互逻辑,也就是服务器接到客户端请求之后,找到对应的数据并根据找到的数据生成对应的视图.然后将包含数据的视图一次性发给客户端,客户端直接将渲染即可.
服务端渲染过程
1.客户端请求html (请求)
2.服务端内部查找对应的html文件和数据
3.服务器内部根据html文件和数据生成完整网页
4.服务端返回完整网页
6.客户端渲染HTML,找到依赖的JS/CSS文件
7.客户端请求对应的JS/CSS文件 (请求)
8.客户端等待JS/CSS文件下载完成
9.客户端直接展示网页
最大优点: 首屏加载快(因为服务器返回的网页已经包含数据,所以下载完JS/CSS就可以直接渲染);每次请求返回的都是一个独立完成的网页,更利于SEO.
最大缺点: 网络传输数据量大
预渲染
无需服务器实时动态编译,采用预渲染,在构建时针对特定路由简单的生成静态HTML文件。本质就是客户端渲染, 只不过和SPA不同的是预渲染有多个界面。
最大优点: 由于有多个界面, 所以更利于SEO
最大缺点: 首屏加载慢, 预编译会非常的慢
如何选择
注重SEO的新闻、电商网站,建议采用服务器端渲染
强交互的页面,不注重SEO,采用客户端渲染
两者之间, 只需改善少数页面的SEO,采用预渲染
使用预渲染解决SPA应用SEO问题
vue-cli-plugin-prerender-spa
. https://www.npmjs.com/package/vue-cli-plugin-prerender-spa
注意点:Router
必须使用history
模式
注意点: Router必须使用history模式
// main.js 会自动加上
new Vue({
...
// 以下代码是安装了预渲染的插件之后自动添加的
mounted: () => document.dispatchEvent(new Event('x-app-rendered'))
}).$mount('#app')
// vue.config.js
const jsdom = require('jsdom')
const { JSDOM } = jsdom
module.exports = {
...
// 以下代码是安装了预渲染的插件之后自动添加的
pluginOptions: {
prerenderSpa: {
registry: undefined,
renderRoutes: [ // 要预渲染的组件,一个组件会生成一个页面
'/',
'/recommend',
],
useRenderEvent: true,
headless: true,
onlyProduction: true,
postProcess: route => { // 预渲染会写入一个网页适配的配置,需要预渲染后删除
// 预渲染内容写入之前的额外操作
let reg = /<meta name="viewport" .*user-scalable="no"">/gi
let res = route.html.match(reg)
route.html = route.html.replace(res[1], '')
// 预渲染生成的页面,动态显示隐藏的组件无法更改状态,需要预渲染后删除。比如全局的加载提示组件
// 1.根据字符串创建一个网页
let html = new JSDOM(route.html) // 需要安装插件 jsdom,在nodejs的执行环境下将渲染后的html字符串转成dom对象,然后用dom操作删除预渲染额外的内容
// 2.从创建好的网页中拿到document对象
let dom = html.window.document
// 3.找到对应的元素, 删除对应的元素
let loadingEle = dom.querySelector('.container')
dom.body.removeChild(loadingEle)
route.html = html.serialize()
return route
}
}
}
}
使用vue-meta-info统一管理SEO三大标签
vue-meta-info
https://www.npmjs.com/package/vue-meta-info
// ./main.js 中导入
import MetaInfo from 'vue-meta-info'
Vue.use(MetaInfo)
// 新建 ./vue-meta-info.js,配置SEO,即网页的title,keywords,description
export default {
recommend: {
title: '我是recommend',
meta: [
{
name: 'keyWords',
content: '关键字1,关键字1,关键字1'
},
{
name: 'description',
content: '这是一段网页的描述1'
}
]
},
...
}
// 预渲染组件中引入
metaInfo: MetaInfo.recommend, // 推荐组件,,这样spa页面也可以有多个页面,每个页面都有自己的SEO
<!--SEO三大标签-->
<title>网易云音乐</title>
<meta name="keywords" content="网易云音乐,音乐,播放器">
<meta name="description" content="网易云音乐是一款专注于发现与分享的音乐产品">