一.Vue
-
Vue (读音 /vjuː/,类似于 view),不要读错。
-
Vue是一个渐进式的前端框架,什么是渐进式的呢? VUE全家桶
- 渐进式意味着你可以将Vue作为你应用的一部分嵌入其中,或者如果你希望将更多的业务逻辑使用Vue实现,那么Vue的核心库以及其生态系统。比如Core+Vue-router+Vuex+axios,也可以满足你各种各样的需求。
-
Vue的特点和Web开发中常见的高级功能:
- 解耦视图和数据
- 双向数据绑定
- 可复用的组件
- 前端路由技术
- 状态管理
- 虚拟DOM
注意:学习Vue框架,必须严格遵循他的语法规则,Vue代码的书写思路和以前的JQuery完全不一样。所以,在项目中使用Vue之前,必须先学习Vue的基本语法规则。
二.语法格式
1. v-bind指令
<div id="app">
<!--
v-bind: 指令==> 简写 : 是Vue提供给我们的一个标签属性指令
在标签属性中,想要使用data中的数据的话,需要在标签属性名前面加上 v-bind: 或者 :
-->
<a v-bind:href="url1">百度一下</a>
<a :href="url2">淘宝一下</a>
</div>
new Vue({
el:'#app',
data :{
url1 : 'http://www.baidu.com',
url2 : 'http://www.taobao.com'
}
})
2.v-on指令
/* v-on: 简写 @ ==>事件指令
语法: @事件类型='事件处理程序'*/
-
修饰符
- .stop —> 可以阻止事件冒泡,相当于调用 event.stopPropagation()
- .prevent —> 可以阻止事件默认事件, 相当于调用 event.preventDefault()
- {keyCode | keyAlias} — 值当事件是从特定键触发是才触发回调
- .native ---- > 监听组件根元素的原生事件
- .once -----> 只触发一次回调
<button @click.stop="btnclick">按钮</button> // 可以阻止事件冒泡 <input type="submit" value="提交" @click.prevent="submitclick"> // 可以阻止事件的默认事件 <input @keyup.enter="onEnter"> // 键修饰符, 键别名 <input @keyup.13="onEnter"> // 键修饰符, 键代码 <button @ click.once=""></button> // 点击回调只会触发一次 <button @click.stop.prevent="btnclick"></button>
-
传参问题
- 如果该方法不需要额外参数,那么方法后的()可以不添加
- 如果方法中本身中有一个参数,那么会默认将原生事件event参数传递进去
- 如果需要同时传入某个参数和event时,可以通过 $event 传入事件
3.Vue控制类名
<!-- :class的值可以是一个对象,键名就是类名,值就是布尔值,为true则有这个类,为false则没有这个类 -->
<div :class="{current:bool1,active:bool2}">文字1</div>
<!-- :class的值可以是一个三元运算符,通过条件的结果来确定有没有这个类 -->
<div :class="bool3 ? 'current' : '' ">文字2</div>
<!-- :class的值可以是一个数组,每一个元素都可以是一个字符串,都是类名(少用) -->
<div :class="['current','active']">文字3</div>
4.Vue控制行内样式
<div style="font-size: 20px;">文字1</div>
<!-- :style="" 的值可以是一个对象,key就是属性名,值就是属性值 -->
<div :style="{color:'red',fontSize:'30px'}">文字2</div>
<!-- 可以使用三元表达式把它写活 -->
<div :style="{color:bool1 ? 'green' : 'red' ,fontSize :'50px'}">文字3</div>
<!-- 跟数组,跟跟对象的区别不大,唯一不同的是可以跟多个对象(很少用) -->
<div :style="[{color : bool2 ? 'pink' : 'red'},{fontSize : '40px'}]">文字4</div>
5.v-if 和 v-show指令
v-if 指令控制标签的显示和隐藏,后面跟一个布尔值,true为显示,false为隐藏(会对元素进行DOM删除)
v-if v-if-else v-else if -- else 中间不能跟其他标签,不然后面的就不会再进行判断
v-show 只是对样式层面上的显示和隐藏,同样是跟一个布尔值,区别是不会对元素进行DOM删除
如果需要对一个元素频繁的进行显示和隐藏就用 v-show ,这样子对浏览器的性能要好一点
6.v-for指令
<!-- v-for 遍历数组 -->
<ul>
<!-- arr是一个数组,item是数组的每一个元素,index是数组每一个元素对应的索引(下标) -->
<li v-for="(item,index) in arr">{{index}}. {{item}}</li>
</ul>
<!-- v-for 遍历对象 -->
<ul>
<!-- obj 是一个对象,item是一对象的每一个值, key是对象的每一个键 -->
<li v-for="(item,key) in obj">{{key}} : {{item}}</li>
</ul>
<!-- v-for 遍历数字 -->
<ul>
<li v-for="item in 6">{{item}}</li>
</ul>
7.双向数据绑定
-
修饰符
- lazy: 可以让数据在失焦或者回车的时候才会更新
- number: 默认输入框输入的不管是字母还是数字,都会被当做字符串进行处理,number修饰符可以让输入框中输入的内容自动转成数字类型
- trim: 可以让过滤内容左右的两边的空格
<input type="text" v-model.lazy="val"> <input type="text" v-model.number="val"> <input type="text" v-model.trim="val"> 多个修饰符继续点 <input type="text" v-model.number.trim.lazy="val">
<div id="app">
<!--
v-model 专门给表单元素使用
注意点: 1. v-medol的值是data中属性名,这个属性的值,可以理解为表单元素的value的值
2. 一旦用户输入了数据,实际上是在修改了 val 的值,所以页面上引用到val的地方全部发生改变
-->
<input type="text" v-model="val">
<p>{{val}}</p>
<!-- 用户选择其实是在改变 selval的值,改变后, 就隐形了p标签的内部内容 -->
<select v-model="selVal">
<option value="bj">北京</option>
<option value="gz">广州</option>
<option value="sh">上海</option>
</select>
<p>{{selVal}}</p>
</div>
new Vue({
el :'#app',
data :{
val:'初始值', // 这是一个初始值
selVal:'gz'
}
})
8.其他一些指令
- v-text : 和{{}}差不多
- v-html : 可以解析HTMl标签
- v-pre : 让浏览器不解析{{}}
- v-cloak : 在夹杂的过程让用户不会 看到{{}}语法
- css样式 : [v-cloak]{display : none}
<div id="app">
<p>{{str1}}</p>
<!-- v-text 跟{{}}差不多 -->
<p v-text="str1"></p>
<!-- v-html 可以解析标签 -->
<p v-html="str1"></p>
<!-- v-pre 可以让浏览器不解析胡子语法 -->
<p><span v-pre>{{num}}</span> 的值为: {{num}}</p>
<!-- v-cloak 在网速比较慢的时候让用户看不见胡子语法 -->
<p v-cloak>{{str1}}</p>
</div>
new Vue({
el : '#app',
data : {
str1 : '<strong>我是一个strong标签</strong>',
num : 10
}
})
[v-cloak]{
display: none;
}
9.Vue中的MVVM
-
Model层:
数据层,数据可能是我们自定定义的数据,或者是从网络请求下来的数据;
-
View层
视图层,在前端里就是我们常说的DOM层,主要作用是给用户展示各种信息;
-
ViewModel层:
视图模型层,是View层和Model层沟通的桥梁;一方面它实现了数据绑定(Data Binding),将Model的改变实时反应到View中;另一方面它实现了DOM监听,当DOM发生改变可以对应改变数据(Data)
10. Vue中的计算属性
- computed 使用: 当一个值受到其他值的影响,就可以写到计算属性中
- 在Vue中写在computed中的这些方法被看成属性,不需要调用,只需要写名字就行执行(不需要小括号)
- 计算属性有缓存效果.普通方法没有缓存效果,比较消耗性能
- 传参问题,计算属性返回一个函数就可以向计算属性传递参数
11.使用:value和@input代替v-model
v-model
本质上包含了两个操作:
-
v-bind
绑定input元素的value属性 -
v-on
指令绑定input元素的input事件<input type="text" v-model="textVal"/> <!-- 等同于 --> <input type="text" v-bind:value="textVal" v-on:input="textVal = $event.target.value"/>
13.过滤器
-
局部过滤器
<div id="app"> <!-- num ===> 是我们的数据 RMBFormat ===> 是过滤器的名字 --> <h1>价格为:{{num | RMBFormat}}</h1> </div>
// 过滤器的作用: 让数据以一定的格式渲染到页面上展示,这个格式是可以复用的 filters: { RMBFormat(value){ // value 接收 | 前面的值 return '¥' + value.toFixed(2) + '元'; } }
-
全局过滤器
// 全局过滤器(其他组件都可以使用,因为一次就只能创建一个格式,所以不用加 s ) Vue.filter('RMBFormat', value=>{ // value 接收 | 前面的数据 return '¥' + value.toFixed(2) })
14.本地存储
-
localStorage
- 除非是主动删除,不然是不会自动删除的
- 默认是以字符串的形式保存
- 一般浏览器存储的大小是5M
- 5M = 1024 * 5kb
// 本地存储localStorge数据(是以键值对的形式存储) localStorage.setItem('name','Vue'); localStorage.setItem('age',7); // 获取localstorage数据(是以键名的方式获取) console.log(localStorage.getItem('name')); // 删除localStorage数据 localStorage.removeItem('name'); // 指定删除 localStorage.clear(); // 会清空所有本地储存的数据
-
sessionStorage
- 关闭浏览器会自动清空数据
- 一般浏览器存储的大小是5M
// 临时存储浏览器关闭之后会清空 sessionStorage.setItem('name','Vue'); // 临时存储 sessionStorage.setItem('age',7); console.log(sessionStorage.getItem('name')); // 获取临时存储的数据 // sessionStorage.removeItem('name'); // 指定删除临时存储的数据 sessionStorage.clear(); // 删除全部的临时储存的数据
-
本地存储注意的点
let obj = { name: "Vue", age: 7, job: 'web开发' } // 保存引用类型数据(对象,数组),需要用JSON.stringify()转成字符串储存 localStorage.setItem('obj',JSON.stringify(obj)); // 获取的使用再使用JSON.parse()转成对象,这样子才可以使用 . 语法来获取 console.log(JSON.parse(localStorage.getItem('obj'))); let myobj = JSON.parse(localStorage.getItem('obj')); console.log(myobj.name); console.log(myobj.age);
-
cookie
- 4.1. cookie
- 网站中,http请求是无状态的。也就是第一次登陆成功(发送请求),第二次请求服务器依然不知道是哪一个用户。这时候的cookie就是解决这个问题的,第一次登陆后服务器返回数据(cookie)给浏览器,然后浏览器保存在本地,当该用户发送第二次请求,浏览器自动会把上次请求存储的cookie数据自动带上给服务器,服务器根据客户端的cookie来判断当前是哪一个用户。cookie存储有大小限制,不同浏览器不一样,一般是4kb,所以cookie只能存储小量数据。
- 4kb = 4 * 1024 byte (字节) = 4 * 1024 * 8 bit(位)
- token: 用户登录凭证,服务端返回给浏览器(前后端分离项目,基本都是发送ajax请求)
- 4.2 session
- session和cookie的作用有点类似,也是存储用户相关信息。不同的是cookie存储在浏览器,而session存储在服务器。
- 4.1. cookie
15.深入双向数据绑定
-
vue框架特点:双向数据绑定与组件化开发
-
深入双向数据绑定
- Vue 最独特的特性之一,是其非侵入性的响应式系统。数据模型仅仅是普通的 JavaScript 对象。而当你修改它们时,视图会进行更新。Vue里面是怎么做到的的呢?其实就是使用了
Object.defineProperty
把Vue内的属性全部转成getter/setter
。Object.defineProperty
是 ES5 中一个无法 shim 的特性,这也就是 Vue 不支持 IE8 以及更低版本浏览器的原因。 Object.defineProperty
实现了对象劫持这个功能- vue双向数据绑定原理:
- 借助Object.defineProperty()对数据进行劫持,并结合发布-订阅者模式,来实现双向数据绑定
语法:
Object.defineProperty(obj, prop, desc)
obj
需要定义属性的当前对象prop
当前需要定义的属性名desc
属性描述符
数据属性:
通过Object.defineProperty()为对象定义属性,有两种形式,分别为数据描述符,存取描述符,下面分别描述两者的区别:
value
表示它的默认值writable
如果为true标识可以被修改,如果为false标识不能被修改(默认值为false)configurable
描述属性是否配置,以及可否删除,可以认为是总开关 默认值 false(不可删除)enumerable
描述属性是否出现在for in 或者 Object.keys()的遍历中 默认值false(不能遍历)
<input type="text" id="ipt"> <p id="pp"></p> <script> // 原生实现双向数据绑定 // 原理:借助Object.defineProperty()对数据进行劫持,并结合发布-订阅者模式来实现双向绑定数据 let obj = {}; let val = '初始值'; Object.defineProperty(obj,'inpVal',{ get(){ return val; }, set(newValue){ // 当obj.iptVal发生改变的时候,界面中涉及到的数据也都要发生改变 // 利用了Vue的思想,数据一改,界面上的数据也会发生改变 ipt.value = newValue; pp.innerHTML = newValue; } }) // 将val的值给到页面上面的两个元素 ipt.value = obj.inpVal; pp.innerHTML = obj.inpVal; // 注册事件 ipt.addEventListener('input',function(e){ // 修改obj.inpVal的值 obj.inpVal = e.target.value; // 修改了inpVal的值,触发了set() }) </script>
- Vue 最独特的特性之一,是其非侵入性的响应式系统。数据模型仅仅是普通的 JavaScript 对象。而当你修改它们时,视图会进行更新。Vue里面是怎么做到的的呢?其实就是使用了
三.组件化
-
什么是组件化
面对复杂问题的处理方式,把问题拆解成很多个能处理的小问题,再将其放在整体中,会发现大的问题也会迎刃而解。
而组件化的思想也类似:
- 如果我们实现一个页面结构和逻辑非常复杂的页面时,如果全部一起实现会变得非常复杂,而且也不利于后续的维护和迭代功能。
- 但如果我们这时候把页面分成一个个小的功能块,每个功能块能完成属于自己这部分独立的功能,那么整个页面之后的维护和迭代也会变得非常容易。
组件化开发的优势:可维护性高 可复用性高
-
Vue组件化思想
- 组件化是Vue重要的思想
- 它提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用。
- 任何的应用都会被抽象成一颗组件树。
- 组件化思想的应用开发:
- 有了组件化的思想,我们在之后的开发中就要充分的利用它。
- 尽可能的将页面拆分成一个个小的、可复用的组件。
- 这样让我们的代码更加方便组织和管理,并且扩展性也更强。
- 组件化是Vue重要的思想
-
全局组件
- 通过
Vue.component('组件名称', {})
,通过这个方法注册的都是全局组件,也就是他们再注册之后可以用在任何新创建的Vue
实例挂载的区域内。
- 通过
-
局部组件
- 通过
Vue.component
方式注册的组件,称之为全局组件。任何地方都可以使用。全局注册往往是不够理想的。比如,如果你使用一个像 webpack 这样的构建系统,全局注册所有的组件意味着即便你已经不再使用一个组件了,它仍然会被包含在你最终的构建结果中。这造成了用户下载的 JavaScript 的无谓的增加
- 通过
-
那组件如果要使用data定义自己属性保存数据要怎么做呢?
- 组件对象也有一个data的属性(也有methods等属性,下面我们有用到)
- 只是这个data属性必须是一个函数,而且函数返回一个对象 ,对象保存着数据
-
为什么data在组件中必须是一个函数呢?
- 原因是在于Vue让每个组件对象都返回一个新的对象,因为如果是同一个对象的,组件在多次使用后会相互影响。
1. 组件之间的通讯
1.1父传子
- 在组件中,使用选项props来声明需要从父级接收到的数据。
- props的值有两种方式:
- 字符串数组,数组中的字符串就是传递时的名称。
- 对象,对象可以设置传递时的类型(String,Number,Boolean,Array, Object,Date,Function,Symbol),也可以设置默认值等。
<div id="app">
<cnp :cstars="stars" :cmessage="message"></cnp>
</div>
<template id="cnp">
<div>
<ul>
<li v-for="item in cstars">{{item}}</li>
</ul>
<h2>{{cmessage}}</h2>
</div>
</template>
// 子组件
// 父传子(props属性)
const cnp1 = {
template:'#cnp',
// 1.以数组的形式(用的比较少)
// props:['cstars','cmessage'],
// 2. 以对象的形式传递
props:{
// 2.1 类型限制
// cstars:Array,
// cmessage:[String,Array],
// 2.2 提供一些默认值,以及必传值
cmessage:{
type:String,
default:'啊啊啊啊啊',
required: true
},
// 类型是对象或者数组时,默认值必须是一个函数
cstars:{
type:Array,
default(){
return []
}
}
}
}
// 父组件
new Vue({
el:'#app',
data:{
stars:['杨幂','白百何','迪丽热巴','赵丽颖'],
message:'你好啊',
},
components:{
cnp:cnp1
}
})
1.2 子传父
父组件向子组件传递数据,通过自定义事件
<body>
<!-- 父组件模板 -->
<div id="app">
<cnp @itemclick="cnpclick"></cnp>
</div>
<!-- 子组件模板 -->
<template id="cnp">
<div>
<button v-for="item in categories"
@click="btnclick(item)">
{{item.name}}
</button>
</div>
</template>
</body>
</html>
<script>
// 子组件
const cnp1 = {
template: '#cnp',
data() {
return {
categories: [
{ id: 'aaa', name: '热门推荐' },
{ id: 'bbb', name: '手机数码' },
{ id: 'ccc', name: '家用家电' },
{ id: 'ddd', name: '电脑办公' },
],
}
},
methods:{
btnclick(item){
// 向父组件传递数据
// this.$emit('事件名称',参数)
this.$emit('itemclick',item)
}
},
}
// 父组件
new Vue({
el: '#app',
data: {
},
components: {
cnp: cnp1
},
methods:{
cnpclick(item){
console.log(1111,item);
}
},
})
</script>
1.3 兄弟之间的通讯
<body>
<div id='app'>
<child1></child1>
<child2></child2>
</div>
<!--
兄弟组件的传值问题的解决方案
1. 子传父,父传子(比较麻烦)
2. Vuex 状态管理
3. 中央事件总线(一个空的Vue对象)
-->
<template id="tml1">
<div>
<button :style="{backgroundColor:bgc}">按钮1</button>
</div>
</template>
<template id="tml2">
<div>
<button @click="change">按钮2</button>
</div>
</template>
<script>
let bus = new Vue(); // 1. 创建一个空Vue对象
let child1 = {
template: '#tml1',
data() {
return {
bgc: 'pink',
}
},
created() {
// 组件创建完毕时就会执行这里的代码
// 步骤2 通过bus来开启事件监听,监听有没有其他组件来修改我的bgc数据
// 定义了一个自定义事件,提供可修改bgc的方案
// bus.$on("事件名称", 事件触发时候要执行的函数)
bus.$on('changebgc',val=>{
this.bgc = val;
})
}
}
let child2 = {
template: '#tml2',
data(){
return {
bgc :'blue'
}
},
methods:{
change(){
// 3. 通过bus触发自定义事件
bus.$emit('changebgc',this.bgc)
}
}
}
new Vue({
el: '#app',
data: {
},
components: {
child1, child2
}
})
</script>
</body>
1.4 多层级之间的通讯
- 父级组件中: 父节点的组件可以通过 provide 方法, 对其子孙组件共享数据
export default {
data(){
return {
color: 'red' // 1. 定义'父组件'要向 '子孙组件' 共享的数据
}
},
provide(){ // 2. 在 provide 函数 return 的对象中, 包含了要向 子孙组件 共享的数据
return{
color: this.color
}
}
}
- 子孙节点通过 inject 数组, 接收父级节点向下共享的数据
<template>
<h1>
子孙组件 ---- {{ color }}
</h1>
</template>
<script>
export default {
// 子孙自减, 使用 inject 接收父节点向下共享的 color 数据, 并在页面上使用
inject: ['color']
}
</script>
2.插槽
-
匿名插槽: slot 标签没有name属性的就是匿名插槽
-
具名插槽
- 在默认情况下,不展示(不渲染)子组件中标签中的内容
- 如果想展示子组件中的标签中的内容,需要在子组件中模板中使用 slot 标签来接收
- slot 标签出现的位置,就是这些内容出现的位置
- slot 标签属性可以用来和上面标签的 slot 属性做对应,标签标签 可以放到对应的slot放到对应的slot的位置上
- 有name属性的slot标签成为具名插槽
<slot name="myslot"></slot>
-
插槽作用: 提高组件复用性,提高组件的扩展性
-
作用域插槽
- 作用域插槽在父组件使用我们的子组件时, 插槽的数据从子组件中拿到数据,而不是从父组件拿到。
-
作用于插槽的四种写法
<div id='app'> <!-- 第一种写法: 直接写在标签上面 --> <child> <button slot="btn" slot-scope="scope">{{scope.mymessage}}</button> </child> <!-- 第二种写法: 写在template标签上面 --> <child> <template slot="btn" slot-scope="scope"> <div> <p>{{scope.mynum}}</p> <button>{{scope.mymessage}}</button> </div> </template> </child> <!-- 第三种写法:指令写法 --> <child v-slot:btn="scope"> <button>{{scope.mymessage}}</button> </child> <!-- 第四种写法: --> <child> <template v-slot:btn="scope"> <div> <P>{{scope.mynum}}</P> <button>{{scope.mymessage}}</button> </div> </template> </child> </div> <template id="tmp"> <div> <h1>我是组件</h1> <slot name="btn" :mynum="num" :mymessage="message"></slot> </div> </template> <script> let child = { template:'#tmp', data(){ return { num:99, message:'按钮' } } } new Vue({ el:'#app', data:{ }, components:{ child } }) </script>
四.webpack
从本质上来说,webpack是一个静态模块打包工具。
要想让我们写好的模块化代码在各式各样的浏览器上能做到兼容,就必须借助于其他工具;而webpack的其中一个核心就是让我们可以进行模块化开发,并帮我们处理模块间的依赖关系。不仅仅是Javascript文件,我们的css、图片、json文件等在webpack中都可以当作模块来使用,这就是webpack的模块化概念。
- gulp和webpack
Gulp侧重于前端开发的 整个过程 的控制管理(像是流水线),我们可以通过给gulp配置不同的task(通过Gulp中的gulp.task()方法配置,比如启动server、sass/less预编译、文件的合并压缩等等)来让gulp实现不同的功能,从而构建整个前端开发流程。
gulpfile.js
var gulp = require('gulp');
var uglify = require('gulp-uglify'); //压缩代码
// 压缩js
gulp.task('uglify',function(){
var combined = combiner.obj([
gulp.src('src/scripts/**/*.js'), //需要压缩的js文件路径
sourcemaps.init(),
uglify(), //压缩js
sourcemaps.write('./'),
gulp.dest('dest/scripts') //生成的js文件的目录
]);
});
//默认任务
gulp.task('default',['uglify']);
Webpack有人也称之为 模块打包机 ,由此也可以看出Webpack更侧重于模块打包,当然我们可以把开发中的所有资源(图片、js文件、css文件等)都可以看成模块,最初Webpack本身就是为前端JS代码打包而设计的,后来被扩展到其他资源的打包处理。Webpack是通过loader(加载器)和plugins(插件)对资源进行处理的。
五.创建项目
1. 创建项目准备工作
Node版本
Vue CLI 需要Node.js 8.9 或者更高版本(推荐使用 12.11.0)。你可以使用
nvm
或nvm-window
来管理电脑上面的Node版本。
安装Vue CLI脚手架的包:
npm install -g @vue/cli
# OR
yarn global add @vue/cli
安装之后,你就可以在命令行中访问 vue
命令。你可以通过简单运行 vue
,看看是否展示出了一份所有可用命令的帮助信息,来验证它是否安装成功。
你还可以用这个命令来检查其版本是否正确 (4.x):
vue --version
vue -V
如果安装比较慢,可以把下载源切换成淘宝的源:
npm 对应的淘宝下载源设置:
//切换taobao镜像源
npm config set registry https://registry.npm.taobao.org/
// 查看下载源
npm config get registry
yarn 对应的淘宝下载源设置:
//切换taobao镜像源
yarn config set registry https://registry.npm.taobao.org/
// 查看下载源
yarn config get registry
2. 初始化项目
运行以下命令来创建一个新项目:
vue crate 文件名
vue create hello-world
对于每一项的功能,此处做个简单描述:
- TypeScript 支持使用 TypeScript 书写源码。
- Progressive Web App (PWA) Support PWA支持
- Router 路由
Vuex 状态管理
CSS Pre-processors 支持 CSS 预处理器。
Linter / Formatter 支持代码风格检查和格式化。
Unit Testing 支持单元测试。
E2E Testing 支持 E2E 测试。
如果你决定手动选择特性,在操作提示的最后你可以选择将已选项保存为一个将来可复用的 preset
~/.vuerc
被保存的 preset 将会存在用户的 home 目录下一个名为
.vuerc
的 JSON 文件里。如果你想要修改被保存的 preset / 选项,可以编辑这个文件。在项目创建的过程中,你也会被提示选择喜欢的包管理器或使用淘宝 npm 镜像源以更快地安装依赖。这些选择也将会存入
~/.vuerc
。
3.项目结构
node_modules
public // 静态资源文件
|-favicon.ico
|-index.html
src // 项目源代码,书写代码的地方
|-assets
|-App.vue
|-main.js
.browserslistrc // 浏览器相关支持情况
.eslintrc.js // 代码相关支持情况
.gitignore // Git忽略文件
babel.config.js // babel配置ES语法 转换
package-lock.json // npm安装依赖库的具体信息
package.json // npm依赖库版本信息
postcss.config.js // css相关转换
README.md // 项目说明
vue.config.js // Vue及webpack配置项
vue.config.js
vue.config.js
是一个可选的配置文件,如果项目的 (和package.json
同级的) 根目录中存在这个文件,那么它会被@vue/cli-service
自动加载。你也可以使用package.json
中的vue
字段,但是注意这种写法需要你严格遵照 JSON 的格式来写。这个文件应该导出一个包含了选项的对象:
// vue.config.js
module.exports = {
// 选项...(例如:)
lintOnSave: false // 关闭eslint
}
4 . “@/”路径提示配置
安装 Path Intellisense插件
打开设置 - 首选项 - 搜索 Path Intellisense
- 打开 settings.json
,添加:
"path-intellisense.mappings": {
"@": "${workspaceRoot}/src"
}
在项目 package.json
所在同级目录下创建文件 jsconfig.json
:
{
"compilerOptions": {
"target": "ES6",
"module": "commonjs",
"allowSyntheticDefaultImports": true,
"baseUrl": "./",
"paths": {
"@/*": ["src/*"]
}
},
"exclude": [
"node_modules"
]
}
最后重启打开即可
六.常用修饰符
vue中常用的修饰符分三种,分别是事件修饰符、按键修饰符和表单修饰符。
一. 事件修饰符
-
.stop
阻止事件冒泡(*) -
.prevent
阻止默认事件(*) -
.prevent.stop
阻止默认事件的同时阻止冒泡 -
.once
阻止事件重复触发(once与stop不能一起使用,否则再次触发事件,依然会冒泡)(*)
<div class="big" @click="big">
<div class="small" @click.stop="small">
<a href="#" @click.stop.prevent>百度一下</a>
</div>
<button @click.once="btn">按钮</button>
</div>
二.按键修饰符
在监听键盘事件时,我们经常需要检查详细的按键。Vue 允许为 v-on
在监听键盘事件时添加按键修饰符:
<input v-on:keyup.enter="submit">
<input v-on:keyup.13="submit">
为了在必要的情况下支持旧浏览器,Vue 提供了绝大多数常用的按键码的别名:
.enter
(*).tab
.delete
(捕获“删除”和“退格”键).esc
.space
.up
.down
.left
.right
三.系统修饰键
可以用
实现仅在按下相应按键时才触发鼠标或键盘事件的监听器。
-
.ctrl
(实测结果:由于mac系统ctrl+鼠标左键 = 右键
这个功能,所以mac上无法实现) -
.alt
-
.shift
-
.meta
(meta在win上代表⊞键,在mac上代表⌘键)
<!-- Alt + C -->
<input v-on:keyup.alt.67="doSomething">
<!-- Ctrl + Click -->
<div v-on:click.ctrl="doSomething">Do something</div>
四.表单修饰符
.trim
: 去掉表单字符串的前后空格.number
: 把表单里面的字符串转成数字类型.lazy
: 失焦或者回车的时候再加载,节约了浏览器性能的损耗
<input type="text" v-model.trim="txtVal">
<input type="number" v-model.number.lazy="t2Val">
五.watch
一. watch的作用
-
可以监控一个值的变换,并调用因为变化需要执行的方法。可以通过watch动态改变关联的状态。
-
简单点说,就是实时监听某个数据的变化
二. 普通监听
watch:{
num(newVal,oldVal){ // 接收两个参数,newVal 和 oldVal
// 监听的属性发生改变时执行这里的代码
console.log('num发生改变的时候执行这里的代码');
console.log(newVal,oldVal);
}
}
三. 立即监听
如果我们需要在最初绑定值的时候也执行函数,则就需要用到immediate属性。
immediate需要搭配handler一起使用,其在最初绑定时,调用的函数也就是这个handler函数
watch:{
num:{ // 立即监听:页面一刷新的时候就会执行
handler(newVal,oldVal){
console.log('页面一刷新就会执行');
console.log(newVal,oldVal);
},
immediate:true // 执行了立即监听
}
}
四.深度监听
当需要监听一个对象的改变时,普通的watch方法无法监听到对象内部属性的改变,只有data中的数据才能够监听到变化,此时就需要deep属性对对象进行深度监听
注意:
1、如果监听的数据是一个对象,那么
immediate: true
失效;2、一般使用于对引用类型的监听,深度监听,如监听一个Object,只要Object里面的任何一个字段发生变化都会被监听,但是比较消耗性能,根据需求使用,能不用则不用。
3、因为上面代码obj是引用数据类型,val, oldVal指向一致,导致看到的结果一样。
data () {
return {
obj:{
name:'Vue',
age:7
}
}
},
watch:{
obj:{
// 深度监听:这只是监听obj的对象,不能监听里面的属性,加了deep 属性才会能监听到里面的值,新值和老值都是一样的
handler(){
console.log('深度监听');
console.log(this.obj.age);
},
deep:true // 实行了深度监听
}
}
五.Watch与Computed的区别
- watch中的函数是不需要调用的,computed内部的函数调用的时候不需要加()
- watch(属性监听),监听的是属性的变化,而computed(计算属性),是通过计算而得来的数据
- watch需要在数据变化时执行异步或开销较大的操作时使用,而对于任何复杂逻辑或一个数据属性,在它所依赖的属性发生变化时,也要发生变化,这种情况下,我们最好使用计算属性computed。
- computed 属性的结果会被缓存,且computed中的函数必须用return返回最终的结果
- watch 一个对象,键是需要观察的表达式,值是对应回调函数。主要用来监听某些特定数据的变化,从而进行某些具体的业务逻辑操作;
1. Watch与Computed的使用场景
-
computed
- 当一个结果受多个属性影响的时候就需要用到computed
- 最典型的例子: 购物车商品结算的时候
-
watch
- 当一个数据的变化需要有额外操作的时候就需要用watch
- 搜索数据
-
总结:
- 一个值的结果受其他值的影响,用computed
- 一个值的变化将时刻影响其他值,用watch
六.ref 和 $refs
vue中获取页面里的某个元素(标签或组件),可以给它绑定ref属性,有点类似于给它添加id名
<div>
<!-- ref 标签属性 和 $ refs 对象 -->
<!-- ref 是标签/子组件 的标签属性.类似于起了一个id -->
<p ref="op">我是一个P标签</p>
<button @click="btnClick">按钮</button>
<Child ref="ch"/>
</div>
methods:{
btnClick(){
console.log(this.$refs); // 获取到了所有拥有 ref鼠标的标签对象
console.log(this.$refs.op); // 获取到了ref的值是op的标签
this.$refs.op.style.color="pink"; // 可以通过this.$refs修改标签的样式
this.$refs.op.innerHTML='我是新的p标签';
console.log(this.$refs.ch.num); // 直接获取到了子组件的值(但是很消耗性能)
}
}
七. Vue生命周期
一. 什么是生命周期:
- 从Vue创建、运行、到销毁期间,总是伴随着各种各样的事件,这些事件,统称为生命周期。
- **生命周期钩子函数:**就是生命周期事件的别名
二. 8 个声明周期
- 初始
- **beforeCreate:**实例刚刚在内存中被创建出来,此时还没有初始化
data
和methods
属性。 - created:实例已经在内存中创建好,此时
data
和methods
已经创建好,此时还没有开始编译模板
- **beforeCreate:**实例刚刚在内存中被创建出来,此时还没有初始化
- 挂载
- **beforeMount:**此时已经完成了模板编译,但是还没有挂载到页面中;
- **mounted:**这个时候已经把编译好的模板挂载到页面指定的容器里;
beforeMount:
data
的数据可以访问和修改,而且此时的模板已经编译好了,还没有更新到页面中
mounted:
此时编译的模板更新到页面中了
- 更新(视图)
- **beforeUpdate:**状态更新之前执行此函数,此时的
data
中的数据是最新,但是界面上显示的还是旧的,因为此时还没有开始重新渲染DOM节点; - **updated:**实例更新完毕之后调用此函数,此时
data
中的状态值和界面上显示的数据,都已经完成了更新,界面已经被重新渲染好了;
- **beforeUpdate:**状态更新之前执行此函数,此时的
beforeUpdate: 此时修改输入框的内容,
data
中的数据已经更新了,但是页面上显示的还是旧数据;**updated:**此时
data
的内容已经更新,页面显示的也是最新的数据。
- 销毁
- **beforeDestroy:**实例销毁之前调用,在这一步,实例让然完全可用。
- **destroyed:**实例销毁后调用,调用后,Vue实例指向的所以东西会被解绑,所有的事件监听器会被移除,所有的子实力也会被销毁。
<template>
<div>
<p ref="op">{{num}}</p>
<button @click="add">按钮</button>
</div>
</template>
<script>
export default {
data () {
return {
num:20
}
},
methods:{
add(){
this.num++;
}
},
// 初始化阶段 beforeCreate 和 created
beforeCreate(){ // 这个在组件对象创建之前执行, 数据,方法和标签都不能获取到
console.log('1.1==> beforeCreate ', this.num, this.add,this.$refs.op);
},
created(){ // 这个在组件对象创建之后执行, 数据,方法可以获取到, 标签获取不到
// 一般在这里执行异步操作(ajax请求),请求成功后拿到数据更新数据
console.log('1.2==> created ', this.num, this.add,this.$refs.op);
},
// 挂载节点 beforeMount 和 mounted
beforeMount(){ // 这个在组件对象挂载之前执行, 数据,方法可以获取到, 标签获取不到
console.log('2.1==> beforeMount ', this.num, this.add,this.$refs.op);
},
mounted(){ // 这个在组件对象挂载之后执行, 数据,方法和标签都可以获取到
// 如果异步操作(ajax)放在这个函数里面,页面会渲染两次
console.log('2.1==> beforeMount ', this.num, this.add,this.$refs.op);
},
// (视图)更新阶段
beforeUpdate(){ // 视图更新之前执行
console.log('3.1==> beforeUpdate ', this.num, this.add,this.$refs.op);
},
updated(){ // 视图更新之后执行
console.log('3.2==> update ', this.num, this.add,this.$refs.op.innerHTML);
},
// 销毁阶段(在子组件中)
beforeDestroy(){ // 组件销毁之前执行
// 回收和清理的工作,比如清除定时器,清除全局事件
console.log('执行了4.1beforeDestroy');
clearInterval(this.timer);
},
destroyed(){ // 组件销毁之后执行
console.log("执行了4.2destroyed");
}
}
</script>
八.mixins(混入)
mixins就是定义一部分公共的方法或者计算属性,然后混入到各个组件中使用,方便管理与统一修改。同一个生命周期,混入对象会比组件的先执行。
1、导出mixins
在src下创建 mixins/index.js
,写入:
export const MixinsFn = {
created() {
console.log("这是mixins触发的created")
}
}
2、引用mixins
<template>
<div class="about">
<h1>This is an about page</h1>
</div>
</template>
<script>
import { MixinsFn } from '@/mixins/index.js'
export default {
created(){
console.log("这是about触发的created")
},
mixins: [MixinsFn]
}
</script>
总结: 我们会发现,
mixins中的created
比about中的created
优先执行。
九.keep-alive
<keep-alive>
是Vue的内置组件,能在组件切换过程中将状态保留在内存中,防止重复渲染DOM。
KeepAlive用于处理组件缓存。有什么用呢?想象一个业务场景:
在Login页面填写完手机号,发现未注册,需要跳去Register页面完成注册,再返回Login页面填写密码,进行登录,此时如果在Login页面没有保存好手机号,那么跳回来时,用户又得重新输入手机号。为了解决这样的需求,我们需要借助keep-alive。
这里,我们新创建两个页面, You.vue
和 Me.vue
,并且都实现累加功能。
1、修改App.vue
// 将原本的:
<router-view/>
// 修改为:
<keep-alive>
<router-view/>
</keep-alive>
这样,我们实现了整个项目中每个页面的缓存,显然我们不想这么做,我们只想针对某些页面,这时,我们修改一下:
<!-- 放在keep-alive标签中的组件就会有缓存效果 没有套上keep-alive就不会有缓存效果 -->
<keep-alive>
<!-- router-view 占位符,将来被某个组件代替 被谁代替,看router/index.js -->
<router-view v-if="$route.meta.isKeep" />
</keep-alive>
<router-view v-if="!$route.meta.isKeep" />
/*
如果页面需要需要缓存,在router/index.js的routes下面这只一个meta属性,给meta属性设置一个变量,
如果为真就会进第一个,就会有缓存效果,如果变量为false就会进下面,没有缓存效果
*/
注意:
<keep-alive>
是用在其一个直属的子组件被开关的情形。如果你在其中有v-for
则不会工作。
那这里的 $route.meta.keepAlive
存在于哪里呢?
2、Router中的meta
这是写在路由文件中的字段,我们去 router/index.js
中,将我们要缓存的 Me.vue
页面加上meta:
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/me',
name: 'Me',
component: () => import('../views/Me.vue'),
meta: {
isKeep: true // 这是判断页面是否需要缓存的依据
}
},
{
path: '/you',
name: 'You',
component: () => import('../views/You.vue'),
meta:{
isKeep: false
}
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router
在有缓存的页面会有两个生命周期函数(这两个生命周期函数只有在有缓存的页面才会有)
// 激活和取消激活的生命周期函数,是有keep-aline组件独有的
activated(){
console.log("被激活(显示出来)的时候执行这里的代码");
},
deactivated(){
console.log("被取消激活(隐藏起来)的时候执行这里的代码");
}
十.vuex
官方定义: Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。
它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
说得直白点,vuex就是vue.js中管理数据状态的一个库,通过创建一个集中的数据存储,供程序中所有组件访问。
一个数据只要放在了vuex中,当前项目所有的组件都可以直接访问这个数据。
一. 使用场景
普通的父传子和子传父,或是兄弟组件之间的互传值,都是两个组件之间的数据连接,但如果数据需要多组件共享,并且数据量庞大,那么就不适宜用中央事件总线来解决。此时,我们需要一个更加强大的,能够维护庞大数据的东西,它就是vuex,我们称之为:状态管理
二. 什么时候使用vuex
官方回答: Vuex可以帮助我们管理共享状态,并附带了更多的概念和框架,这需要对短期和长期效益进行权衡.如果您不打算打开大型单页应用,使用Vuex可能是繁琐冗余的,确实是如此___如果您的应用够简单,你最好不要使用Vuex,一个简单的 **store模式 **就足够您所需了,但是,如果您需要构建一个中大型单页应用,您很可能会考虑如何更好的在组中外部管理状态,Vuex将会成为自然而然的选择,
引用Redux作者Dan Abramove的话说就是: Flux架构就像眼镜: 您自会知道什么时候需要它
很显然,如果你的项目比较简单,建议还是别强行使用vuex了。
另外,值得注意的点:vuex会随着页面刷新或关闭,将所有数据恢复至最初始的状态,所以它并不能替代localStorage。
三. state
vuex中的state类似于data,用于存放数据,只不过这个数据是所有组件公用的。
我们来实现一个计数功能,但咱们把数据存放到vuex中的state,这里是专门用来存放组件共享的数据的。
四. getters
vuex中的getters类似于computed计算属性,getters的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
五.mutations
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。
有一个必须注意的点,Mutations中不允许出现异步操作,它必须是一个同步函数
六.actions
Action 类似于 mutation,不同在于:
- Action 提交的是 mutations,而不是直接变更状态。
- Action 可以包含任意异步操作。
store/index.js
文件
import Vue from 'vue'
import Vuex from 'vuex'
import changeNum from './changeNum'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
title: '我是组件',
num: 10
},
getters: {
dbnum(state) {
return state.num*2;
}
},
mutations: {
addNum(state,payload){
return state.num += payload;
}
},
actions: {
asyncAddNum(state,payload){
setTimeout(()=>{
state.commit('addNum',payload);
},1000)
}
},
modules: {
changeNum
}
})
组件中
<template>
<div>
<!-- <h2>{{ $store.state.title }}</h2> -->
<h2>{{ title }}</h2>
<h2>{{ $store.state.num }}</h2>
<h2>{{ num }}</h2>
<h2>{{ dbnum }}</h2>
<button @click="changeNun">按钮1</button>
<button @click="changeNum2">异步按钮2</button>
</div>
</template>
<script>
export default {
computed: {
title() {
return this.$store.state.title;
},
num() {
return this.$store.state.num;
},
dbnum(){
return this.$store.getters.dbnum;
}
},
methods:{
changeNun(){
// commit(),专门调用store的mutations属性里面的方法
return this.$store.commit('addNum',10);
},
changeNum2(){
// dispatch(),专门调用store的actions属性里面的方法
return this.$store.dispatch('asyncAddNum',20)
}
}
};
</script>
七. 辅助函数
获取单个数据或触发某个方法比较容易,我们直接拿到和触发就行,但如果要获取的数据和触发的方法很多个,我们就比较麻烦了,这时候我们需要借用辅助函数。比如,我们刚刚只写了累加,现在再补充一个递减。然后来看看辅助函数怎么用
组件中
<template>
<div>
<!-- 辅助函数 -->
<h2>{{ title }}</h2>
<h2>{{ num }}</h2>
<h2>{{ dbnum }}</h2>
<button @click="changeNum">按钮</button>
<button @click="asyncAddNum(20)">异步按钮2</button>
</div>
</template>
<script>
import {mapState , mapGetters, mapMutations,mapActions} from 'vuex';
export default {
computed:{
...mapState(['title','num']),
...mapGetters(['dbnum'])
},
methods:{
...mapMutations(['addNum']),
...mapActions(['asyncAddNum']),
changeNum(){
this.addNum(10);
}
}
}
</script>
八. module
假设我们把累加单独抽出来作为一个模块,在store下新建一个 changeNum/index.js
文件
export default {
namespaced:true, // 命名空间,为true时,可以在store中把当前模块文件夹名称(changeNum),当作模块名使用
state: {
title: '我是组件',
num: 10
},
getters: {
dbnum(state) {
return state.num * 2;
}
},
mutations: {
addNum(state, payload) {
return state.num += payload;
}
},
actions: {
asyncAddNum(state, payload) {
setTimeout(() => {
state.commit('addNum', payload);
}, 1000)
}
},
}
把有关累加的所有内容,都移动至本文件。再到原来仓库index.js中的modules添加:
import Vue from 'vue'
import Vuex from 'vuex'
import changeNum from './changeNum' // 引入
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
changeNum
}
})
组件中
<template>
<div>
<h1>{{ title }}</h1>
<h1>{{ num }}</h1>
<h2>{{ dbnum }}</h2>
<button @click="change">按钮</button>
<button @click="asyncAddNum(20)">异步按钮</button>
</div>
</template>
<script>
import {mapState , mapGetters ,mapMutations, mapActions} from 'vuex'
export default {
computed:{
...mapState({
// 变量名: state=> state.文件名.变量名
'title': state=>state.changeNum.title, // module中state值的获取方法和getters等不太一样,需要写函数形式
'num' : state=> state.changeNum.num
}),
...mapGetters({
'dbnum' : 'changeNum/dbnum' // 文件名/函数名
})
},
methods:{
...mapMutations({
'addNum' : 'changeNum/addNum'
}),
...mapActions({
'asyncAddNum' : 'changeNum/asyncAddNum'
}),
change(){
this.addNum(10)
}
}
}
</script>
九. 拆分写法
实际上我们可以把state、getter、mutation、action和module都抽离出来,这样可以让store文件看着更加简洁。我们来将 store/index.js
进行拆分:
state.js
:
export default {
num1: 0,
title: '标题'
}
mutations.js
:
export default {
cutNum(state, payload) {
state.num1 -= payload;
}
}
actions.js
:
export default {
AsyncCutNum({ commit }, payload) {
setTimeout(() => {
commit('cutNum', payload)
}, 300)
}
}
modules.js
:
import add from './add'
export default {
add
}
最后,在 store/index.js
中:
import Vue from 'vue'
import Vuex from 'vuex'
import state from './state'
import mutations from './mutations'
import actions from './actions'
import modules from './modules'
Vue.use(Vuex)
export default new Vuex.Store({
state,
mutations,
actions,
modules
})
十. vuex数据在 路由守卫中获取
在 router/index.js
文件中: (vuex的数据是用辅助函数写的)
import store from "@/store" ; // 引入store数据
# store.state.文件名.state里面的变量名
store.state.navList.navListArr // 获取vuex state里面的数据
#store._mutations["文件名/mutations里面的函数名"][索引](传入的参数)
store._mutations["navList/changeNavListArr"][0](res.data.menu) // 修改vuex里面的数据
十一. vue-router
一. SPA
- Single Page Application,中文:单页应用。
- 以下两个链接请用浏览器的手机模拟器打开:
- 单页应用有很多,比如:https://m.huxiu.com/
- 非单页应用也是以往的开发习惯,比如:https://36kr.com/
二. 什么是路由
路由器是用来做什么的
- 路由是决定数据包从来源到目的地的路径;
- 将输入端的数据转移到合映射表适的输出端;
- 路由中最重要的概念就是路由表:路由表的本质就是一个映射表,决定了数据包的指向;
三. 后端路由
- 早期的网站开发整个HTML页面是由服务器来渲染的。服务器将渲染好的对应的HTML页面返回给客户端进行展示;
- 但是一个网站包含很多页面,那服务器是怎么处理的呢?
- 每个页面都有对应的网址,也就是URL
- URL会发送给到服务器,服务器会通过正则对该URL进行匹配,最后交给Controller进行处理
- Controller进行处理,最终生成HTML或者数据,然后返回给前端。
- 这其实就是服务器的一个IO操作,这其实就是对后端对路由的解析
- 后端渲染的好处,相对于发送ajax请求拿数据,可以提高首屏渲染的性能,也有利于SEO的优化;
- 后端路由的缺点:
- 另一种情况是前端开发人员如果要开发页面, 需要通过PHP和Java等语言来编写页面代码
- 而且通常情况下HTML代码和数据以及对应的逻辑会混在一起, 编写和维护都是非常糟糕的事情
四. 前端路由:
记录路径和视图(组件)的对应关系
前后端分离阶段:
- 随着Ajax的出现,有了前后端分离的开发模式;
- 后端只提供API来返回数据(json,xml),前端通过Ajax获取数据,并且可以通过JavaScript将数据渲染到页面中
- 这样做最大的优点就是前后端责任的清晰, 后端专注于数据上, 前端专注于交互和可视化上
- 并且当移动端(iOS/Android)出现后, 后端不需要进行任何处理, 依然使用之前的一套API即可
- 目前很多的网站依然采用这种模式开发
单页面应用阶段:
- 其实SPA最主要的特点就是在前后端分离的基础上加了一层前端路由
- 也就是前端来维护一套路由规则
前端路由的核心是什么呢?
- 改变URL,但是页面不进行整体的刷新
五. 前端路由的规则
-
URL的hash
- URL的hash也就是锚点(#), 本质上是改变window.location的href属性
- 我们可以通过直接赋值location.hash来改变href, 但是页面不发生刷新
location.hash = '/aaa'; location.hash = '/bbb';
-
HTML 5 的 history模式
- history接口时HTML5新增的,它有5种模式改变URL而不刷新页面
- history.pushState(data, title, url)
- history.replaceState(data, title, url)
- **history.go(-1) **返回上一页
- history.back() 等价于 history.go(-1)
- history.forward() 等价于 history.go(1)
- history接口时HTML5新增的,它有5种模式改变URL而不刷新页面
六. vue-router 的基本使用
-
认识路由
- vue-router是Vue的官方路由插件,它和Vue是深度集成的,适合用于构建单页面应用 https://router.vuejs.org/zh/ 。
- vue-router是基于路由和组件的,路由用于设定访问路径, 将路径和组件映射起来;在vue-router的单页面应用中, 页面的路径的改变就是组件的切换.
-
路由的使用
src/router/index.js
文件中import Vue from 'vue' import VueRouter from 'vue-router' import Home from '../views/Home.vue' Vue.use(VueRouter) const routes = [ { path: '/', name: 'Home', component: Home }, { path: '/about', name: 'About', // route level code-splitting // this generates a separate chunk (about.[hash].js) for this route // which is lazy-loaded when the route is visited. // ES6懒加载作用:让组件等到用户获取这个页面的时候才来加载,提高用户体验(解决第一次加载的时候白屏的情况) component: () => import(/* webpackChunkName: "about" */ '../views/About.vue') }, { path: '/user', name: 'User', component: ()=> import('../views/User.vue') } ] const router = new VueRouter({ mode: 'history', base: process.env.BASE_URL, routes }) export default router
可以看到,我们有两种引入组件的方式。第一种比较能理解,第二种我们称之为“路由懒加载”。而这个懒加载中,有个 webpackChunkName
,这东西我们称为魔法注释。
魔法注释的作用:
webpack在打包的时候,对异步引入的库代码(lodash)进行代码分割时,为分割后的代码块取得名字。
Vue中运用import的懒加载语句以及webpack的魔法注释,在项目进行webpack打包的时候,对不同模块进行代码分割,在首屏加载时,用到哪个模块再加载哪个模块,实现懒加载进行页面的优化。
当你
npm run build
之后,生成的js文件中,就能看到以魔法注释定义的js文件名。
七. 懒加载
当一个vue项目很大的时候,对于一些“暂时”用不到的组件,我们可以不进行加载,等到用到次组件时再加载。这样可以优化spa应用首次加载白屏情况,也给用户更好的体验。这就是vue路由懒加载。
懒加载的方式
// 方式一: 结合Vue的异步组件和Webpack的代码分析
const User = resolve => { require.ensure(['@/views/User.vue'], () => { resolve(require('@/views/User.vue')) }) };
// 方式二: AMD写法
const User = resolve => require(['@/views/User.vue'], resolve);
// 方式三: 在ES6中, 我们可以有更加简单的写法来组织Vue异步组件和Webpack的代码分割.
const Home = () => import(/* webpackChunkName: "user" */ '../views/User.vue')
八.路由模式
vue中的路由默认时hash模式,使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载。如果不想要很丑的 hash,我们可以用路由的history 模式,这种模式充分利用 history.pushState
API 来完成 URL 跳转而无须重新加载页面。
hash 虽然出现URL中,但不会被包含在HTTP请求中,对后端完全没有影响,因此改变hash不会重新加载页面。
history模式提供了对历史记录进行修改的功能,只是当它们执行修改时,虽然改变了当前的URL,但你浏览器不会立即向后端发送请求。history模式,会出现404 的情况,需要后台配置。
404 错误:
1、hash模式下,仅hash符号之前的内容会被包含在请求中,如 http://www.xxx.com, 因此对于后端来说,即使没有做到对路由的全覆盖,也不会返回404错误;
2、history模式下,前端的url必须和实际向后端发起请求的url 一致,如http://www.xxx.com/book/id 。如果后端缺少对/book/id 的路由处理,将返回404错误。
九. 路由跳转方式
我们可以使用 router-link
标签来实现跳转,如:
<div id="nav">
<router-link to="/">Home</router-link> |
<router-link to="/user">User</router-link>
</div>
<router-view/>
然后通过 router-view
来显示页面。router-link
最终会被渲染为a标签。
router-link
默认会被解析为a标签,如果想让它转换成其他的标签,就需要添加tag属性:
<router-link tag="li" to="/user">User</router-link>
此时,router-link
就被解析为li标签。
路由跳转方式
methods:{
handleClick(){
// 通过 $router对象的方式跳转
this.$router.push('/user');
this.$router.push({name:'About'}); // 通过name跳转
this.$router.push({path:'/user'});
// 带查询字符串的方式跳转 query传参
this.$router.push({name:'User' , query:{page:1, size:8}}); // http://localhost:8081?page=1&size=8
this.$router.push({path:'/user', query:{page:2, size:9}});
// 传递参数 pathinfo参数
this.$router.push('/user/999'); // this.$router.push('/路径/参数') http://localhost:8081/user/999
this.$router.push({name:'User',params:{id:999}});
// this.$router.push({path:'/user' , params:{id:999}}); // 错误写法
}
}
// src/router/index.js 文件中
{
path: '/user/:id', // path:'/路径/:参数名'
name: 'User',
component: () => import(/* webpackChunkName: "about" */ '../views/User.vue')
}
// 组件中获取参数
created(){
// this.$route.params.参数名
console.log(this.$route.params.id); // 获取url的id的参数
this.id = this.$route.params.id; // 修改data定义的id的值
}
编程式导航中,使用name进行路径跳转,携带参数可以通过params和query,其中query会将参数携带在导航路径上,而使用path进行路径跳转,无法携带params,只能携带query。
十. 重定向 redirect
const routes = [
{
path: '/',
redirect: '/home' // 这就是路由的重定向,重新定义跳转路径
},
{
path: '/home', // 改成这个之后,原来的/就没有对用的组件了
component: () => import('@/views/Home.vue')
},
... ...
{
path: '*', // 匹配所有剩余的路由,只要不是上面提及的页面,全部跳转到404页面
component: () => import('@/views/Page404.vue')
}
]
十一.路由嵌套
const routes = [
{
path:'/',
redirect:'/tab/category' // 重定向
},
{
path:'/tab',
name:'Tab',
component: () => import(/* webpackChunkName: "tab" */ '../views/Tab.vue'),
// 路由嵌套
children:[
{
path: '/tab/category',
component: () => import(/* webpackChunkName: "category" */ '../components/Category.vue')
},
{
path: '/tab/cart',
component: () => import(/* webpackChunkName: "category" */ '../components/Cart.vue')
},
{
path: '/tab/user',
component: () => import(/* webpackChunkName: "category" */ '../components/User.vue')
}
]
}
十二. 关于重复点击同一个路由出现的报错问题解决
-
vue-router降级处理(但不推荐)
npm i vue-router@3.0.7
-
直接在push方法最后添加异常捕获,例如:
this.$router.push('/user').catch(err=>{});
-
直接修改原型方法push(推荐)
// 把这段代码直接粘贴到router/index.js中的Vue.use(VueRouter)之前 const originalPush = VueRouter.prototype.push; VueRouter.prototype.push = function(location) { return originalPush.call(this, location).catch(err => {}) };
十二. vue-axios
在Vue和React等大型项目中,我们常用的数据请求方式,就是Axios。Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。
官网地址:http://www.axios-js.com/
一. 安装
yarn add axios
npm install axios
二. 使用
// 引入
import axios from 'axios'
created(){
// axios发送get请求,如果要携带参数,必须加上 params
axios.get('/getdata',{
params:{
page:1,
size:10
}
}).then(res=>{
console.log(res);
console.log(res.data);
})
// axios发送post请求
axios.post('/postdata',{
data:{
num:123
}
}).then(res=>{
console.log(res);
console.log(res.data);
})
}
axios.get(url[, config])
axios.post(url[, data[, config]])
// 直白一点表示:
// 发送get请求 带参数的话一定通过params传递,获取之后带在url后面
faaxios
.get(url, {
params: {}
})
.then(res=>{})
.catch(err=>{})
axios
.post('/user', {})
.then(res=>{})
.catch(err=>{})
三.解决跨域:代理请求
解决方案是创建一个 vue.config.js
:
module.exports = {
devServer: {
proxy: "http://localhost:3000"
}
}
十三. 图片的使用
<template>
<div>
<!-- 图片的引入 -->
<img src="../assets/logo.png" alt="">
<img src="@/assets/logo.png" alt="">
<!-- 模块化引入图片 -->
<img :src="img01" alt="">
<img :src="img02" alt="">
<!-- 做背景图片 -->
<div class="box"></div>
<!-- 行内样式图片 -->
<div class="box1" :style="{background:`url(${img01}) no-repeat`}"></div>
<div class="box1" :style="{background:bgc}"></div>
</div>
</template>
<script>
import img01 from '@/assets/logo.png' // ES6模块化
export default {
data () {
return {
img01,
img02:require('@/assets/logo.png'), // common JS
bgc:`url(${img01}) no-repeat`
}
}
}
</script>
<style lang="less" scoped>
.box {
width: 200px;
height: 200px;
border: 1px solid #000;
background-image: url('../assets/logo.png');
}
.box1 {
width: 300px;
height: 300px;
border: 1px solid #000;
}
</style>
十四. $nextTick应用场景
在挂在阶段之前,我们是拿不到dom标签上及其数据的,如果我们想要在挂载阶段之前的函数中获取挂载后的数据,就需要使用$nextTick
$nextTick有两种基本使用方法:
// 1、CallBack形式
this.$nextTick(()=>{
console.log(this.$refs.title.innerHTML);
})
// 2、Promise形式
this.$nextTick().then(()=>{
console.log(this.$refs.title.innerHTML);
})
Comp1.vue中:
<template>
<div>
<p ref="op">文字</p>
</div>
</template>
<script>
export default {
data () {
return {
}
},
created(){
// 在该阶段,我们获取不到元素
console.log(this.$refs.op); // undefined
this.$nextTick(()=>{
console.log(this.$refs.op.innerHTML); //文字
})
this.$nextTick().then(()=>{
console.log(this.$refs.op.innerHTML); //文字
})
}
}
</script>