vue的知识点

VUE

vue框架的两大核心:

数据驱动组件化

数据驱动(数据的变化会驱动页面发生变化,不用操作DOM)

转变编程思维的习惯:组件化

一、前端开发历史

1994年可以看做前端历史的起点(但是没有前端的叫法)
1995年网景推出了JavaScript
1996年微软推出了iframe标签, 实现了异步的局部加载
1999年W3C发布第四代HTML标准,微软推出用于异步数据传输的 ActiveX(ActiveXObject),各大浏览器厂商模仿实现了 XMLHttpRequest(这是前端的真正的起始)
2006年,XMLHttpRequest被W3C正式纳入标准(这是前端正式的起始)
2006年, 发布了jQuery
2008年问世的谷歌V8引擎,发布H5的草案
2009年发布了第五代JavaScript
2009年 AngularJS 诞生

2010年 backbone.js 诞生
2011年React和Ember诞生
2014年Vue.js诞生
2014年,第五代HTML标准发布
2014年左右,4G时代到来,混合开发(js, android, ios混合开发)
2016年 微信小程序诞生
2017年 微信小程序正式投入运营
2017年底年 微信小游戏
以前的三大框架: angular, react, vue,现在: react, vue, 小程序(微信、支付宝、百度、头条)
以后: js ----- ts (typescript)

二、MV*模式

库 VS 框架

​ 把一小部分通用的业务逻辑进行封装(函数),多个封装形成一个模块或者文件,多个模块或者文件就发展成为库或者框架

:函数库,不会改变编程的思想,如:jQuery。

框架:框架改变了编码思想,代码的整体结构,如:vue,react,小程序等等。

MVC架构模式

M:model,模型,主要完成业务功能,在数据库相关的项目中,数据库的增删改查属于模型(重点)。(nodeJS)

V:view,视图,主要负责数据的显示(HTML+CSS)

C:controller,控制器,主要负责每个业务的核心流程,在项目中体现在路由以及中间件上(nodeJS)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5Jk6GCAb-1615547574517)(C:\Users\何小仙\AppData\Roaming\Typora\typora-user-images\1615536744729.png)]

优点:耦合度低、复用性高、生命周期成本低、部署快、可维护性高、有利软件工程化管理
缺点:由于模型和视图要严格的分离,这样也给调试应用程序带来了一定的困难。

MVP架构模式

​ MVP是单词Model View Presenter的首字母的缩写,分别表示数据层、视图层、发布层,它是MVC架构的一种演变。作为一种新的模式,

M:model,模型,主要完成业务功能,在数据库相关的项目中,数据库的增删改查属于模型(重点)。
V:view,视图,主要负责数据的显示
P:Presenter负责逻辑的处理,Presenter是从Model中获取数据并提供给View的层,Presenter还负责处理后端任务。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FNKOC5HH-1615547574519)(C:\Users\何小仙\AppData\Roaming\Typora\typora-user-images\1615536916385.png)]

MVP模式与MVC模式的区别:

​ 在MVP中View并不直接使用Model,而在MVC中View可以绕过Controller从直接Model中读取数据。

MVVM架构模式

MVVM是Model-View-ViewModel的缩写,MVVM模式把Presenter改名为ViewModel,基本与MVP模式相似。
唯一区别是:MVVM采用数据双向绑定的方式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3tRmWkK3-1615547574526)(C:\Users\何小仙\AppData\Roaming\Typora\typora-user-images\1615536941144.png)]

vue是MVVM

MVC衍生出很多变体,MVP,MVVM,MV*, VUE是MVVM,M----V----VM,M数据,V视图, VM是负责M与V相互通信

总结:

​ 架构只是一种思维方式,不管是MVC,MVP,还是MVVM,都只是一种思考问题解决问题的思维,其目的是要解决编程过程中,模块内部高内聚,模块与模块之间低耦合,可维护性,易测试等问题。
​ 架构在于,做好代码的分工,配合

三、开发工具

vscode,webstorm,HbuilderX等等

四、vue框架的初识

vue官网](https://cn.vuejs.org/)

作者:尤雨溪 ( 华人 ) , 前Google员工

vue的介绍

  • 构建数据驱动的web应用开发框架

  • Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架

  • Vue 被设计为可以自底向上逐层应用

  • Vue 的核心库只关注视图层

  • vue框架的核心:数据驱动组件化

  • 便于与第三方库或既有项目整合,另一方面,当与单文件组件和 Vue 生态系统支持的库结合使用时,
    Vue 也完全能够为复杂的单页应用程序提供驱动。

  • Vue 不支持 IE8 及以下版本

vue的示例代码

<body>
  V
  <div id="app">
    要被控制的html{{key}}
  </div>
</body>
<script src="vue.js"></script>
<script>
	let vm = new Vue({
    	el:'#app'  //要控制的那片html代码  
    	data:{key:value}//数据  M
  })
</script>

vue框架的理解

1、首先:一个页面包括:结构(HTML模板),表现(css),行为(js)
2、其次:原生JS中的做法:写好HTML的模板和css样式,用js产生数据(逻辑),通过dom的方式控制数据显示在HTML中的那个位置,包括如何显示(DOM的方式改变样式)
3、vue框架:vue中,写好HTML模板,声明式地告知vuejs库,数据显示在何处,在vue对象中处理数据,不用做DOM操作(vuejs库负责)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L89GY5jr-1615547574529)(C:\Users\何小仙\AppData\Roaming\Typora\typora-user-images\1615537086646.png)]

简单理解,new出来一个Vue的实例,传一堆配置参数,控制一片html

记住一点:有了vue,不用程序员操作dom了,因为,vue把dom操作都做好了。

数据驱动和声明式渲染

1、数据驱动
数据驱动,就是通过控制数据的变化来改变(驱动)DOM。背后使用了观察者模式,靠数据的变化来控制页面的变化。这是vue框架的核心,第一周,一定要把数据驱动的思路和编程习惯养成。

2、声明式渲染

声明的意思就是告知,广而告之,即告知程序(Vue框架),在何处渲染什么数据

Vue 实现数据绑定的原理(面试题)

vue数据绑定是通过 数据劫持观察者模式 的方式来实现的
1、数据劫持:使用Object.defineProperty();
当你把一个普通的 JavaScript 对象(json)传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。
目的是:感知属性的变化。当给属性赋值时,程序是能够感知的(知道的)。如果知道的话,就可以控制属性值的有效范围,也可以改变其它属性的值等。
Object.defineProperty()函数:$jiang

​ 2、观察者模式(发布订阅模式):
目的:当属性发生变化时,所有使用该数据地方跟着变化

数据绑定:涵盖了:数据绑定,响应式。

五、vue语法(view层)

data选项

功能:

​ data选项中存储的是页面中需要显示的数据(当然数据可以进行一定的加工后再显示)

是初始化数据的位置,是元数据,是vue实例的一个实例属性,可以接受的数据类型: number/string/boolean/array/json/undefined/null/function

数据绑定

把data中的数据,显示在html中的某处。

插值表达式

功能:可以让html标签里的内容变成动态的(从data中获取), 使用 mustache语法

格式:{{变量|属性名|表达式|函数调用等等}};

示例:

<div id="app">
    <span>Message: {{ msg }}</span>
</div>

new Vue({
    el:"#app"
    msg:"hello 哥们!"
});

这种写法叫作:声明式渲染,即:只需要告知vue,数据在何处显示。

内容指令

指令名:v-text和v-html

指令是什么? 指令就是html标签的自定义属性

1、v-text="数据名"

功能:可以让html标签里的内容变成动态的(从data中获取),相当于innerText。

​ 示例:

<p v-text="str">内容:</p>

对比v-text和插值表达式:

​ 1)、当网络速度慢的时候,插值表达式会在页面上出现 {{}} 的显示,但是指令v-text不会,因为,v-text是自定义属性,最多不做解读。当,标签中的内容全部来自于属性,那么,可以使用v-text。

​ 2)、插值表达式更加灵活,可以在标签里面的某个地方显示,但是v-text会让整个标签的内容全部变化。

2、v-html="数据" 非转义输出

​ 功能:可以让html标签里的内容变成动态的(从data中获取),但是不会对内容中的html标签进行转义。相当于innerHTML。

​ 示例:

 输出真正的 HTML
 <p  v-html="str">内容:</p>

双花括号和 v-text(相当于innerText)会将数据解释为普通文本。
v-html相当于innerHTML

属性指令

​ 指令名:v-bind

​ 功能:可以让html标签里的**属性(名和值)**变成动态的(从data中获取)

1、 属性动态绑定:v-bind:html属性="数据" 简写 :html属性=“数据”`

2、 属性动态绑定: v-bind:[属性名]="数据"

示例:

<div v-bind:id="dynamicId"></div> 

注意: 属性是布尔值 的情况

   <button v-bind:disabled="isButtonDisabled">Button</button>

​ 如果 isButtonDisabled 的值是 null、undefined 或 false,则 disabled 属性甚至不会被包含在渲染出来的 元素中

javascript表达式

在dom里面插入数据,除了可以写原始的数据,还可以使用javascript表达式,

格式:{{数据+表达式}}v-指令="数据+表达式"

示例:

{{ number + 1 }}     
{{ ok ? 'YES' : 'NO' }}     
{{ 'i love you'.split('').reverse().join('') }}
<div  v-bind:id="'list-' + id"></div>

注意:

不能使用语句 ,流程控制(条件,循环)可以使用三元表达式代替

条件渲染(指令)

https://cn.vuejs.org/v2/guide/conditional.html

指令名: v-if 和 v-show

功能:一段dom可以根据条件进行渲染

<div v-if="false">box2</div>

<div v-show="true">box1</div>

v-show VS v-if

v-show=“布尔”v-if=“布尔”
(原理上)区别操作css的display属性操作dom(添加和删除dom完成的)
使用场景适合频繁切换适合不频繁切换
性能消耗初始渲染消耗频繁切换消耗

面试题: v-if和 v-show的区别?

相同点:
v-show和 v-if都是 控制 dom元素 的 显示和隐藏 的。

不同点:
1、原理:

​ v-show是通过控制元素的样式属性display的值,来完成显示和隐藏;
​ v-if是通过对dom元素的添加和删除,完成显示和隐藏

​ 2、使用场景:由原理(做法)得出使用场景的区别

​ v-show:使用在dom元素频繁切换的场景
​ v-if:当dom元素的切换不频繁,可以使用。特别是,首次元素处于隐藏的情况下。

补充: dom的操作(如:创建和删除)是非常耗性能的。为什么?
请看:$jiang页面的重排和回流(提升移动端网页性能)

另外,v-if指令还可以结合v-else-if , v-else一起使用。

示例:

<p v-if="age<10"> 宝宝 </p>
<p v-else-if="age<18">大宝宝</p>
<p v-else>非宝宝</p>

列表渲染(循环指令)

​ https://cn.vuejs.org/v2/guide/list.html

指令名: v-for

功能:把数据进行循环显示在html里(渲染)。推荐操作的数据类型:数组、对象、字符串、数字

​ 格式:

用in或者用of都可以:

<li v-for="值 in 数据">{{值}}</li>
<li v-for="值 of 数据">{{值}}</li>

各种情况:

<li v-for="(值,索引) in 数组">{{值}}/{{索引}}</li>
<li v-for="(对象,索引) in 数组">{{对象.key}}/{{索引}}</li>
<li v-for="(值,键) in 对象">
<li v-for="(数,索引) in 数字">
<li v-for="(单字符,索引) in 字符">

注意:

空数组,null,undefined不循环

也可以进行循环嵌套

v-for和v-if使用在同一个标签里时,v-for 的优先级比 v-if 更高,即:v-for套着v-if,先循环再判断

列表渲染时的key:

​ 在标签里使用属性key,可以唯一标识一个元素。

 1、当 Vue.js 用 v-for **更新**已渲染过的元素列表时,它默认用“就地复用”策略。即:**尽量不进行dom元素的操作,只替换文本**。

​ 2、如果你希望进行dom操作,就用key,因为key的目的是为了唯一标识一个元素。

​ 有了key后,可以跟踪每个节点的身份,从而重用和重新排序现有元素
​ 建议尽可能在使用 v-for 时提供 key attribute,除非遍历输出的 DOM 内容非常简单,或者是刻意依赖默认行为以获取性能上的提升。

注意:

key不要使用(数组)下标

事件绑定(指令)

https://cn.vuejs.org/v2/guide/events.html

指令名:v-on

​ v-on指令可以简写为:@

功能:绑定事件,vue通过v-on指令把事件和处理函数进行绑定。

​ 事件处理函数需要放在methods选项当中去,事件名 不带on,函数可以按照ES5的写法,也可以按照ES6的写法。

格式:

<input type="button" v-on:事件名="方法"  >
<input type="button" v-on:事件名="方法(参数)" >
<input type="button" v-on:事件名="方法($event,参数)"	>
new Vue({
  methods:{
    方法1:function(ev,参数){
        业务
        这里面的this是vue对象本身
    }
    方法2(ev,参数){
         业务
	}
  }
})

获取事件对象:

1、 不传参,默认第一个参数,就是事件对象

​ 如:

<button type="button" @click="checkData4">修改数据</button>
………………
checkData4(ev){
	console.log('事件对象',ev);
},

2、 传参,事件对象需要手动传入(使用官方提供的 $event)

如:

<button type="button" @click="checkData5('qq',$event)">修改数据</button>
………………
checkData5(str,ev){
    console.log('事件对象',ev);
    console.log('参数',str);
},

事件处理函数的this:

1、methods选项里的函数里的this是vue对象本身,所以,事件处理函数里的this也是vue对象本身

2、vue提供的选项的值如果是函数时,不可用箭头函数 , 会造成this丢失

事件修饰符

<div @click.修饰符="函数"></div>
					 .stop          阻止单击事件继续传播(阻止事件流)
					 .prevent       阻止默认行为
     			     .capture 		使用事件捕获模式
					 .self			点到自己时才触发,不是从其它地方(事件流的流)流过来的
					 .once  		只会触发一次
					 .passive 		onScroll事件 结束时触发一次,不会频繁触发,移动端使用

注意:

使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。因此,用 v-on:click.prevent.self 会阻止所有的点击,而 v-on:click.self.prevent 只会阻止对元素自身的点击。

按键修饰符

<!--普通键-->
<div @keyup.修饰符="函数"></div>
     			.left 				上下左右
				.enter				回车
				.13  				可以使用键码

<!--系统(组合)键-->
<div @keyup.修饰符="函数"></div>
     			.ctrl 				
				.alt					
				.shift  				
				.exact  精准控制,@键盘事件.修饰符1.修饰符2.exact  只有1+2才可触发 1+2+3不可以

<!--鼠标-->
<div @mousedown.修饰符="函数"></div>
     			 .left 				
				 .right					
				 .middle	鼠标中键

双向绑定(指令)_表单控件绑定

https://cn.vuejs.org/v2/guide/forms.html

指令名:v-model

功能:视图控制数据,数据也可控制视图,这就是双向绑定,可通过属性+事件绑定实现。而vue中直接提供了一个指令v-model直接完成(v-model 本质上不过是语法糖)。v-model指令只能使用在表单元素上。

不使用v-model完成双向绑定
<input type="text" :value="data数据" v-on:input="changeIptValue">

使用v-model完成双向绑定
<input type="text" v-model="data数据">

其它表单元素的双向绑定

https://cn.vuejs.org/v2/guide/forms.html

表单修饰符

<input v-model.修饰符="数据"></div>
			  .number 	把标签的值转成数字赋给vue的数据
			  .trim 	删除前后空格
			  .lazy   	确认时才修改model数据

示例:

//需求:    
//在下拉框里选择 房间类型(一个房子住多少人)
//动态产生 相应数量的文本框

 <select v-model.number="num"  >
    <option value="1">1人间</option>
    <option value="2">2人间</option>
    <option value="3">3人间</option>
</select>

<hr>
    
<input v-for="i in num" type="text"  :value="i" >

指令总结

​ 指令 (Directives) 是带有 v- 前缀的特殊属性。其实就是html标签的里的自定义属性。指令属性的值预期是单个 JavaScript 表达式 (v-for 是例外情况)。指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM,以下是官方的指令,程序员也可以自定义指令。

常见指令:

  • v-text: 更新元素的 textContent。如果要更新部分的 textContent ,需要使用 {{ Mustache }} 插值。
  • v-html:更新元素的 innerHTML
  • v-bind:动态地绑定一个或多个属性(特性),或一个组件 prop 到表达式。
  • v-show:根据表达式值的真假,切换元素的 display (CSS 属性)。
  • v-if:根据表达式的值的真假条件渲染元素(与编程语言中的if是同样的意思)
  • v-else:表示否则(与编程语言中的else是同样的意思)
  • v-else-if:(与编程语言中的else if是同样的意思)
  • v-for:可以循环数组,对象,字符串,数字,
  • v-on:绑定事件监听器。事件类型由参数指定。
  • v-model:在表单控件或者组件上创建双向绑定
  • v-pre:跳过这个元素和它的子元素的编译过程。可以用来显示原始 Mustache 标签。跳过大量没有指令的节点会加快编译。
  • v-cloak:防闪烁,模板没编译完,电脑配置差,有可能会看到{{}},体验不佳,不如用css先隐藏,之后再显示,包括被包裹的子元素。这个指令保持在元素上直到关联实例结束编译。和 CSS 规则,如 [v-cloak] { display: none } 一起用时,这个指令可以隐藏未编译的 Mustache 标签直到实例准备完毕。
  • v-once:只渲染元素和组件一次。随后的重新渲染,元素/组件及其所有的子节点将被视为静态内容并跳过。这可以用于优化更新性能。

指令缩写:

v-bind 缩写 是冒号
v-on 缩写是 @

样式操作

https://cn.vuejs.org/v2/guide/class-and-style.html

操作样式,就是属性绑定,只不过绑定的属性是class和style,vue在class和style属性上做了加强,给样式属性赋的值还可以是对象,数组

绑定姿势(格式)

<div v-bind:class="数据|属性|变量|表达式"></div>
<div v-bind:style="数据|属性|变量|表达式"></div>

属性值的类型支持

字符串/对象 / 数组


<div :class="{active:true,t1:false}"></div>
<div :style="[{css属性名:值},{css属性名小驼峰:值}]"></div>

class的属性值(对象的方式)示例:

 //
 <div class='big-box' :class="{active:isActive,borderbox:isBorder}"></div>
  let vm = new Vue({
        el: "#app",
        data: {
              isActive:true,
              isBorder:true
        }
    });

注:vue绑定的class属性和普通的class属性可以共存

class的属性值(数组的方式)示例:

<div v-bind:class="[activeClass, errorClass]"></div>
data: {
     activeClass: 'active',
     errorClass: 'text-danger'
}

style的属性值(对象的方式)示例:

<div id="app">
  <p v-bind:style="str">我是个p</p>
 <p v-bind:style="{ width:widthVal+'px',height:'100px',backgroundColor:'blue'}">我是p</p>
 <input type="button" value="测试" @click="fn">
</div>


 let vm = new Vue({
        el: "#app",
        data: {
            // style属性的值是个对象
            str: { width:"200px",height:"100px",backgroundColor:"blue"},
            widthVal:200
        },
        methods:{
            fn(){
                this.str.width="250px";
                this.widthVal ++;
            }
        }
    });
<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>

data: {
      activeColor: 'red',
      fontSize: 30
}

style的属性值(数组的方式)示例:

<div :style="[obj1,obj2]"> 我是div</div>
 data: {
            obj1:{
                    width:"200px",
                    height:"150px"
            },
            obj2:{
                   "background-color":"red"
            }
        }

示例:

todoList 练习的实现

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FDHEXawK-1615547574532)(C:\Users\何小仙\AppData\Roaming\Typora\typora-user-images\1615537614396.png)]

数据驱动练习:
二阶段的运动
全选反选(购物车)
增删改查(或者留言板,或者员工信息)

非响应式情况

​ 什么是响应式?数据a发生变化了,那么界面使用a的地方也会变化(依赖a的所有的数据和界面都会发生变化),背后使用了观察者模式。

​ 什么是非响应式?数据发生变化了,而依赖该数据的地方没有任何变化。

​ 在vue中,使用某些方式改变数据(model层)时,vue不会把结果呈现在页面上(view层),这就是vue出现的非响应式的情况:

  • 对数组使用了 非变异 (non-mutating method) 方法。因为,没有改变原始数组的内容(如:concat)

  • 修改数组索引上的值(根索引)时

  • 修改数组的长度时

  • 给对象添加新的属性时。

    ​ 所以,强烈建议:不要修改数组的根键,不要修改数组的长度,可以把未来需要的数据都声明在data选项内部,不要对数组使用非变异的api(数组的变异方法:$jiang

vue也提供了非响应式问题 解决方案(尽量不要使用)

Vue.set|this.$set(数组, index, value)

Vue.set|this.$set(对象, key, value)

this.$forceUpdate() 强制刷新


六、vue语法(model层)vue的配置项

data属性:

不再说了

计算属性

​ 在模板中放入太多的逻辑会让模板过重且难以维护,而且不好阅读。计算属性computed来帮忙。

​ 计算属性是一个函数,是经过元数据(data里)进一步运算后的数据,计算属性的优点是:当元数据不发生变化时,不会再做计算(即:缓存),只有元数据发生变化时,才做计算。是响应式的,需要在模板中渲染才可调用

语法

//定义
computed:{
  计算属性: function(){return 返回值}		
}

//使用
使用:	{{计算属性}} |  v-指令="计算属性"

computed VS methods

methodscomputed
每次调用都会执行函数里的代码基于它们的响应式依赖进行缓存的,如果依赖没有变化,就不再
一般性能高
{{methodname()}}{{computedname}}
适合强制执行和渲染适合做筛选

属性检测(侦听属性)

guangwang//

​ 需要在数据变化时执行异步或开销较大的操作时,这个时候需要属性检测watch。而不要使用计算属性,因为计算属性是同步的(需要立即返回结果)

定义一个选项

watch:{
  属性名:'methods的里函数名'    //数据名==data的key
  属性名:函数体(new,old){}
  属性名:{
    handler:fn(new,old){},
    deep: true //深度检测,当侦听的属性是个对象,修改对象的某个键时,就能检测到
    immediate: true //首次运行,要不要监听
  }
}

示例:

请输入您的问题:<input type="text" v-model.lazy="question" /><br/>
         答案:<input type="text" v-model="answer" /><br/>
        
 let vm = new Vue({
        el:"#app",
        data:{
            question:"",
            answer:""
        },
        watch:{
            question:function(){
                setTimeout(()=>{
                    this.answer = "吃的是草"
                },2000);
            }
        }
    });

深度检测:

<div id="app">
    <input type="button" value="测试" @click="fn" />
</div>

let vm = new Vue({
        el:"#app",
        data:{
            person:{
                name:"张三疯",
                wife:{
                    name:"梅超风"
                }
            },
        },
        methods:{
            fn(){
                this.person.name="李思峰";
            }
        },
        watch:{
            person:{
                deep: true, //深度检测,当侦听的属性是个对象,修改对象的某个键时,就能检测到
                handler:function(){ 
                   console.log("person变了");
                },
                immediate: true
            }
        }
    });

计算属性 VS 函数 VS 属性检测

计算属性(computed)函数(methods)属性检测(侦听)(watch)
为了显示而用只是处理逻辑,跟普通的函数一样属性变化的检测(相当于事件),当属性的值发生变化时,可以调用函数
依赖模板调用√(在模板上调用的)-(可以在模板使用)×(不能在模板上使用)
是否缓存×
异步×(必须是同步)

自定义指令

https://cn.vuejs.org/v2/guide/custom-directive.html

系统指令在不够用的情况下,考虑自定义,指令是个函数|对象,用来操作dom的, 里面的this 返回window

全局定义

Vue.directive('指令名',{
	bind:function(el,binding){}//指令第一次绑定到元素时调用,此时DOM元素还没有显示在页面上
	inserted:function(el,binding){} //绑定指令的DOM元素插入到父节点时调用。DOM已经渲染(显示)
	update:function(el,binding){}  //指令所在的元素的model层的数据,view有更新请求时
	componentUpdated:function(el,binding){}  //更新完成时
})
  • **指令名:**定义指令时,不用加 v- , 使用指令时,加 v-

  • 钩子函数的参数:

    钩子函数的参数 (即 el、binding、vnode 和 oldVnode)。
    guanwang

​ name: 指令名(不带v-)

​ arg:写在指令名后面的参数,如:v-myfocus:id , arg就是id 。 v-bind:value ,arg就是value

​ expression: 等号后面的表达式,如:v-myfocus:id=“msg+‘1’” ,expression就是msg+‘1’。

​ value:绑定数据的值,如:v-myfocus:id=“msg+‘1’” , value就是msg的值和1拼接的结果

示例:

//定义指令:
Vue.directive('myfocus', {
  inserted: function (el) {   // 当被绑定的元素插入到 DOM 中时……
    // 聚焦元素
    el.focus()
  }
})
使用指令
<div>
    <input type="text" v-myfocus />
</div>

全局定义格式(简写)

Vue.directive('指令名', function(el,binding){ //等价于:bind + update

})

钩子函数的详解:

bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新
componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
unbind:只调用一次,指令与元素解绑时调用。

局部定义

new Vue({
	directives:{
    指令名	: function(el,binding){},//简写方式: bind + update
  	指令名(el,binding){},
    指令名:{
				inserted:fn(el,binding){}		//绑定指令的元素插入到父节点时调用  v-focus
				bind:fn	//指令第一次绑定到元素时调用	v-drag
				update:fn	//指令所在的元素的model层的数据,view有更新请求时
				componentUpdated:fn	//更新完成时
    }
  }
})

示例:

回到顶部

//自定义指令的代码
// 注册一个全局自定义指令 `v-pagetop`
Vue.directive('pagetop', {
    inserted: function (el) {
        el.onclick = function(){
            document.body.scrollTop = 0;
            document.documentElement.scrollTop = 0;
        }
    }
})

//使用指令
<div id="app" >
        <div v-pagetop
        style="width: 100px;height: 100px;background-color:red;">
            回到顶部
        </div>
    </div> 

过滤器

https://cn.vuejs.org/v2/guide/filters.html

​ 对数据在模板中的表现过滤,符合预期,比如数据是0和1,想要表现成对错、成功失败、数据需要过滤器来格式化,vue1.x版本有系统自带过滤器,vue2.x之后完全需要自定义,没有自带过滤器

全局定义
Vue.filter('过滤器名称',函数(要过滤的元数据,参数1,参数n){
           		过滤器的功能
           		return 过滤的结果           
           })
使用
{{数据名 | 过滤器名(参数1,参数2)}}
v-bind:属性="数据| 过滤器名(参数1,参数2)
局部定义
filters:{
  过滤器名称:函数(要过滤的元数据,参数){
        过滤器的功能
        return 过滤的结果      
  }	//函数必须要有返回值
}

预购阿里云服务器(ecs云服务器)

链接 -》个人云 -》突发性能型 t5(74/年)-》系统(centOs系统,认准ecs云服务器-》控制台做一下实名认证

域名购买

链接

混入

​ 意义在于分发 Vue 组件(对象)中的可复用功能,混入对象就是一个json对象,json对象的属性就是 Vue对象的配置项(data,methods等等,但是没有el)

用法

1、定义格式

let mixin1 = {
  data: ...
  methods: ...
}
let mixin2 = {
  data: ...
  methods: ...
}

//2、局部混入(组件内部混入 组件选项内)

mixins: [mixin1,mixin2] //当混入的键与引入键冲突时以组件内的键为主

new Vue({ 
      el:"#app",
      data:{
      	msg:"hello"
  	  }
      mixins: [mixin1,mixin2]
})

3、全局混入:

 Vue.mixin(mixin1)//绝对不推荐的

​ 混入普通选项与组件(vue对象)选项合并,遇冲突,以组件(Vue对象)为主,即:就近原则。

​ 如果是生命周期钩子函数,那么都会调用(混入的钩子先调用)

虚拟DOM和diff算法(原理)

什么是虚拟DOM(virtual DOM):
所谓的虚拟 dom,也就是我们常说的虚拟节点,它是通过JS的Object对象模拟DOM中的节点,然后再通过特定的render(渲染)方法将其渲染成真实的DOM的节点。

为什么使用虚拟DOM:
使用js操作DOM时(增删改查等等),那么DOM元素的变化自然会引起页面的回流(重排)或者重绘,页面的DOM重绘自然会导致页面性能下降,那么如何尽可能的去减少DOM的操作是框架需要考虑的一个重要问题!
页面的重排和回流(提升移动端网页性能)$jiang

​ vue中,使用虚拟DOM 来提高性能。

真实DOM和虚拟DOM的区别:

虚拟DOM不会进行排版与重绘操作

真实DOM频繁排版与重绘的效率是相当低

虚拟DOM进行频繁修改,然后一次性比较并修改真实DOM中需要改的部分,最后并在真实DOM中进行排版与重绘,减少过多DOM节点排版与重绘损耗

虚拟DOM有效降低的重绘与排版,因为最终与真实DOM比较差异,可以只渲染局部

diff算法:
虚拟DOM,是一种为了尽可能减少页面频繁操作DOM的方式,那么在虚拟DOM中,通过什么方式才能做到呢? 就是Diff算法进行对比

diff算法的原理:
逐步解析newVdom的节点,找到它在oldVdom中的位置,如果找到了就移动对应的DOM元素,如果没找到说明是新增节点,则新建一个节点插入。遍历完成之后如果oldVdom中还有没处理过的节点,则说明这些节点在newVdom中被删除了,删除它们即可。

总结:

1、产生两个虚拟DOM树:newVDom,oldVDom。

2、oldVDom和真实DOM保持一致

3、操作的newVDom

4、操作完毕后,通过diff算法对比newVDom和oldVDom的差异,并在oldVDom标注哪些节点要删除,哪些节点要增加,修改

5、根据oldVDom操作真实的DOM,让真实Dom和oldVDom保持一致

vue-dev-tools安装

方案1: 登录谷歌应用商城->索引vue-dev-tools->安装->重启浏览器

方案2:vue调试工具vue-devtools安装及使用$jiang

七、组件

组件的概念:

​ 组件是自定义标签,vueJS提供的组件可以让程序员自定义标签,对页面进行模块化。每个标签里包含HTML,CSS,JS。

​ vue的组件就是一个vue对象。(vue的两大核心:数据驱动,组件化) 。vue对象的配置项,在vue组件里也可以使用。
​ 组件的配置项如下:
​ 没有el属性。
​ template:html模板代码,只能有一个根标签
​ data:必须是个函数
​ methods:

​ ………………………………

一个完整的标签格式是: <标签名 属性名=“属性值" 事件=”函数“>内容</标签名>

vue组件的基本使用(标签名):

1、定义组件:

​ 第一种:

let 组件变量名= Vue.extend({
        template:'<div class="header">{{msg}},我是header组件</div>'
        data:function(){
			return {
				msg:”hi”
			}
		},
  });

第二种(简化写法):

let 组件变量名={
        配置项
}; 
2、注册组件:

全局注册:

Vue.component('组件名',组件变量名);

全局注册的组件,在任何vue对象里都可以使用

局部注册:

//在vue对象的components选项里进行注册
new Vue({
     el:
     components:{
    	 组件名:组件变量名
     }     
});

局部注册的组件,只能在当前vue对象(组件)里使用。

3、使用组件:

组件就是自定义标签,所以,使用组件,就跟使用标签是同样的。

<组件名></组件名>
4、组件嵌套:

把一个组件的标签写在另外一个组件的template里,就是组件嵌套。

如:

  //子组件 
  let myComSon = {
        template:"<div>我是son里的div:{{msg}}</div>",    
        data:function(){
            return {
                msg:"hi:son"
            }
        }
    };

   //父组件
    let myComParent = {
        template:`<div>
                        <p>我是p:{{msg}}</p>
                        <my-com-son></my-com-son>                    
                    </div>`,
        data:function(){
            return {
                msg:"hi"
            }
        },
        components:{
            // 局部注册了另外一个组件
            "my-com-son":myComSon
        }    
    };
5、组件编写方式与 Vue 实例的区别:

​ 1、 data是个函数(面试题)
​ 一个组件的 data 选项必须是一个函数,且要有返回object,只有这样,每个实例(vue组件对象)就可以维护一份被返回对象的独立的拷贝,否则组件复用时,数据相互影响,也就是说,组件的作用域是独立的。

​ 简单回答:如果不是函数,那么,复用的组件的data共享同一块内存空间。

​ 2、组件模板(html代码)只能有一个根标签
​ 3、组件名不可和html官方的标签名同名
​ 4、组件没有el选项,只有根实例存在el
​ 5、书写:组件名如果小驼峰,那么使用时,用短横线(羊肉串的写法),或者组件名首字母大写。

组件的属性(标签的属性)

​ 使用props(property的简写)来完成组件属性的声明。 props是外部给组件传入的数据。而组件的data是组件内部的数据。

使用 Prop 传递静态数据

1)、在组件内部增加配置项 props来声明组件里的属性。props里面可以声明多个属性,所以,是个数组。

  let myComParent = {
        props:["name","sex"], //声明了两个自定义属性
        template:`<div>
                        <p>我是p:{{msg}}</p>     
                        <p>人的信息:</p>             
                        <p>姓名:{{name}}</p>             
                        <p>性别:{{sex}}</p>             
                    </div>`,
        data:function(){
            return {
                msg:"hi"
            }
        } 
    };

2)、使用组件时,给属性传入数据:

<!-- 使用组件时,给属性传入值,传入的值,就可以在组件内部使用 -->
<my-com-parent name="张三疯他哥" sex=""></my-com-parent>

总结提升认识:

把封装组件和封装函数进行类比:

​ 在组件内部用props声明的属性,相当于封装函数时声明的形参。

​ 使用组件时,相当于调用函数,传递实参。

动态Prop

组件属性和官方标签的属性是同样的道理,所以,给组件的属性也可以v-bind 数据。即:绑定动态的数据

<my-com-parent v-bind:name="name" sex=""></my-com-parent>	

​ 如果你想把一个对象的所有属性作为 prop 进行传递,可以使用不带任何参数的 v-bind (即用 v-bind 而不是 v-bind:prop-name)。例如,已知一个 todo 对象:

todo: {
  text: 'Learn Vue',
  isComplete: false
}

<todo-item  v-bind="todo"></todo-item>

等价于:
<todo-item 
  v-bind:text="todo.text"
  v-bind:is-complete="todo.isComplete"
></todo-item>

奇葩情况

​ 在 JavaScript 中对象和数组是通过引用传入的(传的是地址),所以对于一个数组或对象类型的 prop 来说,在子组件中改变这个对象或数组本身将会影响到父组件的状态(data),相当于函数的形参是引用类型

Prop 验证

vueJS还提供了对属性类型的验证,包括属性默认值,是否必须等等。这时候,props不能使用数组,而需要使用对象。

如:

  props:{
            "name":{
                type:String, //限制name属性的类型是字符串
                required:true //限制name属性是必须传入值的。
            },
            "sex":[String,Number], //限制sex属性的值可以为字符串,也可以为数字
            "age":{
                type:Number, //限制age属性的类型是数字型
                default:10 // age属性的默认值是10 。如果没有给age传值,那么age就是10。
            },
            "isadult":Boolean
        },
单向数据流

​ Prop 是单向绑定的:当父组件的属性变化时,将传导给子组件,但是反过来不会。这是为了防止子组件无意间修改了父组件的状态,来避免应用的数据流变得难以理解。
​ 另外,每次父组件更新时,子组件的所有 prop 都会更新为最新值。这意味着你不应该在子组件内部改变 prop。如果你这么做了,Vue 会在控制台给出警告。

总结:

组件就是标签,prop是标签的属性。

prop是外部传入的数据,data是组件内部的数据。

组件的事件

​ 1、绑定事件: v-on:绑定 ;或 vue对象. o n ( “ 事 件 名 ” ) ​ 2 、 触 发 事 件 : v u e 对 象 . on(“事件名”) ​ 2、触发事件 : vue对象. on()2vue.emit(“事件名”,参数);

示例:

//子组件:
Vue.component('button-counter',{
	template: "<input type='button' v-on:click='incrementCounter' v-model='counter' />",
	data:function(){
		return {
			counter:0
		}
	},
	methods:{
		incrementCounter:function(){
			this.counter += 1
			this.$emit('increment')
		}
	}
});

//父组件:

<div id="app1">
    <p>{{ total }}</p>
    <button-counter v-on:increment="incrementTotal"></button-counter>
</div>

var app1 = new Vue({
	el: '#app1',
	data:{
		total:0
	},
	methods:{
		incrementTotal:function(){
			this.total += 1;
		}
	}
});

组件的内容

​ 组件的内容就是标签的innerHTML。vueJS里使用**Slot(插槽)**分发内容。

​ props用来处理标签的属性 ,slot用来处理标签的内容。
​ 将父组件的内容(DOM)放到子组件指定的位置叫作内容分发。

单个插槽
 //子组件
 let person = {      
        template:`<div>
                        <hr/>
                            <p>我是上p</p>
                            <slot></slot>
                            <p>我时下p</p>
                        <hr/>
                    </div>`
    };

//父组件:
   <div id="app">
        <person>
            <div>我是div</div>
        </person>        
    </div>

    new Vue({
        el:"#app",
        components:{
            person
        }
    });
具名插槽

​ 如果父级给子级传来了好多DOM(HTML元素),而且需要把不同的DOM放在子组件不同位置时,就需要给slot起名字,这就是具名插槽。slot元素可以用一个特殊的属性name 来配置如何分发内容。

格式:

<slot name="插槽名"></slot>

示例:

//子组件
     let person = {      
        template:`<div>
                        <hr/>
                            <p>我是上p</p>
                            <slot name="s1"></slot>
                            <p>我是中p</p>
                            <slot name="s2"></slot>
                            <p>我是下p</p>
                        <hr/>
                    </div>`
    };
//父组件
  <div id="app">
        <person>
            <div slot="s1">我是div1</div>
            <div slot="s2">我是div2</div>
        </person>        
    </div>
   new Vue({
        el:"#app",
        components:{
            person
        }
    });
编译作用域

​ 父组件模板的内容在父组件作用域内(父组件对应的对象的作用域)编译;子组件模板的内容在子组件作用域内编译。

示例:

//子组件
let person = {      
        template:`<div>
                        <hr/>
                            <p>我是上p:{{t}}</p>
                            <p>我是下p</p>
                        <hr/>
                    </div>`,
        data(){
            return{
            	t:"我是子组件的数据"
        	}
        }
    };
    
//父组件:
  <div id="app">
        <input :value="msg" />         
        <person v-show="s">
            <p>{{msg}}</p>
            <div>我是div1:{{msg}}</div>
        </person>
  </div>

    new Vue({
        el:"#app",        
        data:{
            msg:"hi",
            s:true
        },
        components:{
            person
        }
    });

组件(间)的通信

​ vue组件之间的通信(传递数据)是必然的,依据vue组件之间的关系(父子,兄弟,或者无关组件)会有不同的做法:

1)、父子组件:
父—> 子: props, 子—> 父:事件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VQr1bqfT-1615547574533)(C:\Users\何小仙\AppData\Roaming\Typora\typora-user-images\1615538851432.png)]

2)、父子组件:refs

3)、兄弟组件,或者无关组件:事件总线,集中管理,vueX等

refs

​ 首先,需要知道:用标签的方式使用组件(如:),实际就是创建了组件对象。只要拿到组件对象,那么组件对象里的methods就可以使用。refs是vue中获取dom的一种方式,dom也就是标签,标签就是组件。即就是:拿到了dom,就相当于拿到了组件对象(这段话需要慢慢品……)

​ 如果某个元素使用ref属性,那么,在vue对象里,就能用this.$refs 访问。可以认为是给元素增加了个id属性,在vue里就可以操作该dom了

​ 格式:

如:<p ref = "pId"> {{msg}}</p>

 methods:{
     testf:function(){
       	this.$refs.pId.innerHTML = "hi";
     }
}

示例:


let myCom = {
    template:`
        <div>            
        </div>
    `,
    data(){
        return {

        }
    },
    methods:{
        focusFn(str){
            console.log(str);
        }
    }
}

Vue.component("my-com",myCom);

new Vue({
    el:"#app",
    data:{
        msg:"hi"
    },
    methods:{
        fn(){
            this.$refs.comId.focusFn(this.msg);//focusFn()是子组件里的函数。
        }
    }
});
事件总线(vue-bus)

​ vue-bus实质就是创建一个vue实例,通过一个空的vue实例作为桥梁实现vue组件间的通信。它是实现非父子组件通信的一种解决方案。

步骤:

1、单独new一个Vue空对象:  let bus= new Vue();
2、在组件A里,绑定一个自定义事件(相当于定义了一个自定义事件):
	    bus.$on('eclick',target => {
          console.log(target) 
        })
3、在组件B里,触发事件
      bus.$emit('eclick',"b传给a的");

示例:

// 定义一个vue对象(作为事件总线的对象)
let bus = new Vue();

let myCom1 = {
    template:`
        <div>
            <hr>
            组件com1
            <hr>
        </div>
    `,
    created(){
        // 注册了一个事件
        bus.$on("eclick",(target)=>{
            console.log(target);
        });        
    }
}

let myCom2 = {
    template:`
        <div>
            <input type="button" value="  传 " @click="fn" />
        </div>
    `,
    methods:{
        fn(){
            //触发事件
            bus.$emit("eclick","hello");
        }
    }
}

Vue.component("my-com1",myCom1);
Vue.component("my-com2",myCom2);

new Vue({
    el:"#app"
});

集中管理($root)

把数据存到根实例的data选项,其他组件直接修改或者使用

定义

new Vue({
  data:{a:1}
})

使用

//子组件内部
this // 子组件本身
this.$root // vm 根实例
this.xx //组件内部数据
this.$root.a //根实例数据

作业

完成微博的数据读取,页面渲染,有能力的完成组件的拆分

动态组件

​ 有的时候,在不同组件之间进行动态切换是非常有用的。即页面的某个位置要显示的组件是不确定的,是会变化的。

​ vue中使用的方式实现。

示例:

<div id="app">
        <span @click="show(0)">娱乐</span>|<span  @click="show(1)">八卦</span>|<span @click="show(2)">体育</span>
        <div>
            <component :is="currCom"></component>
        </div>
</div>

let yuLe = {
    template:"<div>娱乐新闻</div>"
}
let eightGua = {
    template:"<div>八卦新闻</div>"
}
let sports = {
    template:"<div>体育新闻</div>"
}

new Vue({
    el:"#app",
    data:{
        currCom:"yuLe",
        coms:["yuLe","eightGua","sports"]
    },
    methods:{
        show(index){
            this.currCom = this.coms[index];
        }
    },
    components:{
        yuLe,eightGua,sports
    }
});

八、vue对象

类和实例API

https://cn.vuejs.org/v2/api/#%E5%85%A8%E5%B1%80-API

https://cn.vuejs.org/v2/api/#%E5%AE%9E%E4%BE%8B-property

Vue类的属性和方法

​ Vue 是个类,所以Vue.属性名,是类属性|静态属性,Vue.方法名() 是类方法(也叫静态方法)
​ 如:Vue.extend(),Vue. mixin(),Vue.component()……………………

Vue实例(对象)属性和方法

​ let vm = new Vue() 返回的是实例,所以vm. 属 性 名 是 实 例 属 性 , v m . 属性名 是实例属性,vm. vm.方法名()是实例方法,同时vue类内部的this指向的是实例vm,实例vm. 属 性 名 对 等 v u e 选 项 的 k e y ​ 如 : v m . 属性名对等vue选项的key ​ 如:vm. vuekeyvm.data, vm.$el ……………………。

生命周期(面试题)

​ 每个 Vue 实例在被创建时都要经过一系列的初始化过程——例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会

链接

$jiang

vue对象生命周期有四个阶段和八个钩子函数:

1)、数据挂载 阶段 :把传入的data属性的内容,赋给vue对象。即:把形参中data的属性赋给vue对象。

​ 前后分别的钩子函数是:beforeCreate、created

2)、模板渲染阶段:把vue对象中data渲染到dom对象上(模板上,视图上)。

​ 前后分别的钩子函数是:beforeMount、mounted

3)、组件更新阶段:当数据(必须是在模板上使用的数据)发生变化时,会触发组件的更新,组件会重新渲染。

​ 前后分别的钩子函数是:beforeUpdate、updated

4)、组件销毁阶段:

​ 前后分别的钩子函数是:beforeDestroy、destroyed

​ 如果组件在缓存的话,那么,组件切换时,会调用的钩子函数是:

activated 和 deactivated

总结:

1、vue组件(对象)从创建到(初次)显示在用户眼前:经历了 beforeCreate,created,beforeMount,mounted

2、数据(必须在模板中使用的数据)更新:调用beforeUpdate和updated函数

3、为什么叫钩子函数

思考题:

1、如果一打开组件,就需要显示数据,那么请求,应该在哪个钩子函数里写?为什么?

2、如果组件在keep-alive里,而且有定时器(比如:轮播图),如果说deactivated时,可以停止定时器。

项目环境(脚手架)

单文件组件

xx.vue,内部组成(script + template + style)

​ 概念: 把一个组件的所有代码(HTML模板,样式,js(vue对象))写在一个文件里,扩展名是.vue。这就是单文件组件。
基本结构:

<template>
      HTML代码
</template>

<script>
      vue对象 (使用模块化导出vue对象):可以使用es6的写法
</script>

<style scoped> //scoped: 表示局部作用域,表示当前style标签的样式只会应用在当前vue文件里的模板里
     样式
</style>

示例:

<template>
     <p>{{msg}}</p>
</template>
<script>
	export default {
        data(){
 		 return {  
 		 	msg:”hello” 
 		 }
 	   }
	}
</script>
<style scoped>
	p{
          color:red;
          font-size:12px
    }
</style>

脚手架(vue-cli)环境搭建

  • 通过官方脚手架,(命令行的方式)搭建模块化,工程化,自动化开发环境
//1、查看版本(这是查看脚手架的版本) 
vue -V  

//2、如果版本是低于3.X,那么卸载脚手架,安装最新的。
npm uninstall vue-cli -g  

//3、安装(脚手架工具)
//1)、装3.x/4.x
npm install -g @vue/cli
//2)、桥接2.X(兼容脚手架2.X)
npm install -g @vue/cli-init 


//4、创建项目:使用脚手架搭建项目(文件夹)

//1)、如果想搭建版本 v3.x/4.x
vue create 项目目录
//2)、如果想搭建版本 2.X
vue init webpack 项目目录


注意:项目目录,不要和官方的或者第三方的模块重名,也不要使用汉字(每一级文件夹尽量不要使用汉字)

  • HbuildX自动搭建模块化,工程化,自动化开发环境
工作区-》右键-》创建项目-》普通项目-》vue-cli项目
  • 直接copy他人生成的项目环境也可以。

脚手架搭建好的项目的讲解

1)、vue-cli2.X

build文件夹(暂时不需要关注): webpack的相关配置

config文件夹(暂时不需要关注):项目环境的配置。

src文件夹:源代码的文件夹,程序员就在此目录下写代码

src/assets文件夹: 存放静态资源(css,图片,字体文件,视频,音频等等),这些静态资源会做(webpack)打包处理

src/components文件夹: 所有的组件

src/App.vue:main.js所引入的App.vue是模板(main.js里的Vue对象的模板)

src/main.js: 就是唯一的html文件所对应的唯一的Vue对象(根实例)。入口文件。

static文件夹:存放静态资源(但是该文件夹下的文件不做(webpack)打包处理)。

index.html:唯一的HTML文件。

2)、vue-cli3+

区别:

隐藏了build和config文件夹

把static文件夹变成了public文件夹:放置静态资源,把唯一的index.html也放在了public下。

main.js

Vue.config.productionTip = false//设置为 false 以阻止 vue 在启动时生成生产提示。
render: h => h(App)
//↓
render: function(createElement){
  // return createElement('h3','内容')
  // return createElement(组件)
  return createElement(App)
}

es6模块化复习

1)、export

输出

//定义一个文件: a.js

//export 可以在一个模块里面多次使用
//export可以对外导出任意类型的数据
//export 导出的数据要有名字(变量名,函数名)

export const a = 100
export var b = 200
export var person ={
   
}

输入(方式一)

//引入:需要解构
import {a,b,person} from './a.js'
import {a,person} from './a.js'

输入(方式二)

可以把模块输出的所有变量包装成一个对象
import * as obj from './a.js'  
即:obj就是有三个属性的对象
{
	a,
	b,
	person
}

2)、export default

输出

//定义一个文件: a.js

//export default 在一个模块里面只能出现一次
//export default可以对外导出任意类型的数据
//export default导出的数据是匿名的(不能写变量名),即就是,只导出值。

export default 100;

export default {  };

输入

import a from "./a.js";

a就是export default导出的数据。
a其实就是个变量名。

这种写法其实就是,把变量名和赋的值分在了两个模块里。

css 规则

style-loader 插入到style标签,style标签多了选择器冲突问题就出来了,解决方案如下

/* 方案1:	命名空间 不太推荐BEM命名风格*/
/*B__E--M  block 区域块  element 元素  midiler 描述|状态 */
.search{}
.search__input{}
.search__input--focus{}
.search__input--change{}
.search__button{}

//  B__E--M
//  b-b-b__e-e-e--m-m-m
<!--方案2 css模块化 -->
<template>
	<div :class="$style.box">
    ...
  </div>
</template>

<script>
export default {
  mounted(){
    this.$style //返回一个对象 {选择器:'混淆后的选择器',xx:oo}
  }
}
</script>

<style module>
  /* 注意: 选择器名(支持 id,类,或者id、类开头其他选择器,不支持标签名(标签名开头的)) */
  .box{}
  #box2{}
  .box3  div{}
  #box3 div{}
</style>

模块化的好处:使用实例属性$style可以访问到样式。
<!--方案3 独立样式作用域-->
<template></template>
<script></script>
<style scoped></style>

注意:在vue脚手架里,图片(静态资源)的路径问题:

​ 1、如果img标签src使用的是静态的写法(就是纯粹html代码),那么,文件来自于 assets 下

​ 2、如果img标签的src使用的动态的写法(vue去给它赋值),那么,文件来自于 public下,但是,src后面的值,不能带“public”

明确一些名词(叫法):

1、vue.js :是一个js框架。现在讲的版本是 2.×.× ,最新的版本是3.×.×

2、vue-cli:vue脚手架,脚手架是个工具,帮程序员自动搭建项目环境(创建了一些文件夹和文件,并写了最基础的代码)。 现在市面上使用的版本有 2.×.×和 4. ×.×

坑:

1、不要在单文件组件里使用

单页面应用(SPA)

单页面应用的概念

SPA:single page application,单页面应用。

​ 就是整个项目就只有一个html页面(文件),首次加载时,把所有的html,css,js全部加载下来。

单页面应用优缺点

优点:

1,局部刷新,所以,用户体验好
2,前后端分离
3,页面效果会比较炫酷(比如切换页面内容时的转场动画)

缺点:
1,不利于seo
2,导航不可用,如果一定要导航需要自行实现前进、后退。
3,初次加载时耗时多
4,页面复杂度提高很多

SPA和MPA的区别

SPA:single page application。只有一个主页面的应用,浏览器一开始要加载所有必须的 html, js, css。所有的页面内容都包含在这个所谓的主页面中。但在写的时候,还是会分开写(页面片段),然后在交互的时候由路由程序动态载入,单页面的页面跳转,仅刷新局部资源。vue后来做了改进,有些组件按需加载

MPA:multiple page application 。就是指一个应用中有多个页面,页面跳转时是整页刷新。

插槽页面模式多页面模式(MPA Multi-page Application)单页面模式(SPA Single-page Application)
页面组成多个完整页面, 例如page1.html、page2.html等由一个初始页面和多个页面模块组成, 例如:index.html
公共文件加载跳转页面前后,js/css/img等公用文件重新加载js/css/img等公用文件只在加载初始页面时加载,更换页面内容前后无需重新加载
页面跳转/内容更新页面通过window.location.href = "./page2.html"跳转通过使用js方法,append/remove或者show/hide等方式来进行页面内容的更换
数据的传递可以使用路径携带数据传递的方式,例如:http://index.html?account=“123”&password=123456"",或者localstorage、cookie等存储方式直接通过参数传递,或者全局变量的方式进行,因为都是在一个页面的脚本环境下
用户体验如果页面加载的文件相对较大(多),页面切换加载会很慢页面片段间切换较快,用户体验好,因为初次已经加载好相关文件。但是初次加载页面时需要调整优化,因为加载文件较多
场景适用于高度追求高度支持搜索引擎的应用高要求的体验度,追求界面流畅的应用
转场动画不容易实现容易实现

单页面模式:相对比较有优势,无论在用户体验还是页面切换的数据传递、页面切换动画,都可以有比较大的操作空间 多页面模式:比较适用于页面跳转较少,数据传递较少的项目中开发,否则使用cookie,localstorage进行数据传递,是一件很可怕而又不稳定的无奈选择

mock数据

​ mock这词本意是虚拟,模拟的意思。mock server工具,通俗来说,就是模拟服务端接口数据,一般用在前后端分离后,前端人员可以不依赖API开发,而在本地搭建一个JSON服务,自己产生测试数据。即:json-server就是个存储json数据的server
​ json-server 支持CORS和JSONP跨域请求。

json-server

使用步骤:

初始化项目:
npm init -y
安装json-server
npm i json-server -D

打开项目编写数据

在项目根目录下创建db.json,并写上合法的json数据,如下:

{
	"books":[
		{"id":"878911","name":"语文","author":"啥啥啥"},
		{"id":"878912","name":"数学","author":"啦啦啦"}
	],
	"readers":[
		{"id":"007","name":"小红","age":35},
		{"id":"008","name":"小兰 ","age":32}	
	]
}

启动配置

在package.json下增加如下代码:
“scripts”: {
“server”:“json-server db.json”
},

运行

在命令行运行: npm run server,提示如下图。.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2rxB46qY-1615547574534)(C:\Users\何小仙\AppData\Roaming\Typora\typora-user-images\1615539388274.png)]

访问

在浏览器访问:http://localhost:3000/books/ 能够得到books的内容
在浏览器访问:http://localhost:3000/books/878911能得到books里id为878911 的书籍信息。

在浏览器访问:http://localhost:3000/books?id=878911也能得到books里id为878911 的书籍信息。

在浏览器访问:http://localhost:3000/readers 能得到readers的信息。

或者用postman等工具测试也可以。

插入数据

用axios发送请求时,使用post的方式就可以插入数据。

josn-server的路由

需求场景:当使用mock数据,并且需要反向代理时:

1、在json-server的文件夹下新建route.json文件,写上如下代码:

{
       "/api/*": "/$1"         //   /api/posts => /posts
}

上面route.json的意思就是,当请求/api/posts时,重定向到/posts。
2、命令行中输入如下命令( 路由文件通过–routes 参数来指定):
json-server --routes route.json db.json

mock.js(自行研究)

数据交互

向服务器发送ajax请求,抓取数据

解决方案

  • 自行通过XMLHttpRequest对象封装一个ajax(已经讲过)
  • 使用第三方自带的ajax库,如:jquery(已经讲过)
  • ES6新增的 fetch
  • 使用第三方ajax封装成promise习惯的库,如:vue-resourceaxios

fetch

ES6新增的的前后端交互方案。

格式:Promise fetch(String url,配置);

Promise fetch(url,{
  method:
  headers:
  body
})

功能:发送请求
返回值:一个 Promise,resolve 时回传 Response 对象
参数:
URL: 请求地址。
配置:(包括请求方式,请求参数等等)
method: 请求使用的方法,如 GET、POST。
headers: 请求的头信息,形式为 Headers 对象或 ByteString。
body: 请求的 body 信息:可能是一个 Blob、BufferSource、FormData、URLSearchParams 或者 USVString 对象。注意 GET 或 HEAD 方法的请求不能包含 body 信息。

GET方式:

fetch(url?id=001,{method:'GET',})
.then(response => response.json())
.then(data => console.log(data))
.catch(e => console.log("Oops, error", e))

fetch(url,{
		method:"POST",
		headers: new Headers({
		   'Content-Type': 'application/x-www-form-urlencoded' // 指定提交方式为表单提交
		}),
	    body: "id=001&name=张三疯" // post请求的参数
	})
.then(response => response.json())
.then(data => console.log(data))
.catch(e => console.log("Oops, error", e))

asyncawait的写法:

try {
     let response = await fetch(url);
     let data = response.json();
     console.log(data);
} catch(e) {
     console.log("Oops, error", e);
}

特点:

  • 语法简洁,更加语义化

  • 基于标准 Promise 实现,支持 async/await

  • 同构方便,使用 isomorphic-fetch

  • Fetch 请求默认是不带 cookie 的,需要设置 fetch(url, {credentials: ‘include’})

  • 服务器返回 400,500 错误码时并不会 reject,只有网络错误这些导致请求不能完成时,fetch 才会被 reject。

axios

概念:

一个基于 promise 的 第三方 库,可以用在浏览器(前端)和 node.js(后端) 中

格式:

Promise axios(配置)

Promise axios({
        url : “地址“ 
		method: “ 提交方式”
		params:{} 地址栏携带的数据(get方式)
		data:{} 非地址栏携带数据(如:post,put等等),
		baseURL:如果url不是绝对地址,那么将会加在其前面。当axios使用相对地址时这个设置非常方便
}).then(res=>{
    console.log(res.data);
})

axios的完整格式,和axios的别名(get和post)

axios({配置}).then(成功回调(res)).catch(失败回调(res))
axios.get(url,{配置}).then(成功回调(res)).catch(失败回调(res))
axios.post(url,{配置}).then(成功回调(res)).catch(失败回调(res))

res: 响应体 数据是在res.data内部

示例:

get请求:

axios({
        url:'getGoodsListNew.php',
       	 // method:'get',  默认是get请求
        params:{
			count:3
		}
})
.then(res=>this.goodslist=res.data);

post请求示例:

1)、data是字符串

​ 当data是字符串时,请求头里的content-type是 application/x-www-form-urlencoded,network中看到的数据类型是:formData

data 是字符串类型

    axios(
      {
       	 method:'post',
       	 url:'regSave.php', 
         data:'username=jzmdd&userpass=123'
      })
    .then(res=>{  ……………………     });


2)、data是URLSearchParams对象

​ 当data是URLSearchParams对象时(跟data是字符串是一样的),请求头里的content-type是 application/x-www-form-urlencoded,network中看到的数据类型是:formData

  var params = new URLSearchParams();
    params.append('username', 张三疯);  
    params.append('userpass', '123');  

    axios(
      {
       	 method:'post',
       	 url:'regSave.php', 
         data:params
      })
    .then(res=>{  ……………………     });

3)、data是json对象

​ 当data是json对象时,请求头里的content-type是 application/json,network中看到的数据类型是:request payload

axios({
        url:"/vips",
        method:"post",
        data:{
            name:this.name,
            pass:this.pass,
            sex:this.sex
        },
        baseURL:"http://localhost:3000"                
        })
        .then(res=>console.log(res.data))

在google浏览器的network里能够看到传输的数据格式

注意:

使用post方式,在和后端联调时,如果后端接不到数据,需要看network中的

Content-type和数据的格式;

1)、如果data是字符串或者是URLSearchParams

​ content-type:application/x-www-form-urlencoded

​ 数据格式:form data

2)、如果data是json对象

​ content-type:application/json

​ 数据格式:request payload

读取php接口get 示例:

axios({
  url: 'http://localhost:80/php7/get.php',
  params: {//地址栏数据
    a: 11,
    b: 22
  }
}).then(
  res => this.list = res.data
)

读取php接口post示例:

let params = new URLSearchParams(); //创建一个url传输的实例
  params.append("a", "1"); //添加一个key/value
  params.append("b", "22"); //添加一个key/value
  // params.delete("a")  删除
  // params.set("a","11");//修改
  // params.has("a")  返回 true/false

axios({
  url: 'http://localhost:80/php7/post.php',
  method: 'POST', 
  // data: {//非地址栏数据 ***
  //   a: 11,
  //   b: 22
  // },
  // data: "a=11&b=22",
  data: params,//URLSearchParams类型
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded'
  } //php 默认要求传递请求头
}).then(
  res => this.list = res.data
)

处理并发

axios.all(iterable)//all函数执行所有的请求
axios.spread(callback)//处理响应回来的回调函数

	function getbooks(){  
	   return axios.get('api/books');
	}
	
	function getreaders(){  
		return axios.get('api/readers'); 
    }
	
	axios.all([getbooks(),getreaders()]).then(
		axios.spread(function (books, readers) { 
			//所有请求完毕后,调用该函数,books是第一个请求的结果,readers是第二个请求的结果
			console.log(books.data);
			console.log(readers.data);
		})
	);
	
	对比一下,发送一个请求和发送多个请求:
	//1)、发送一个请求
	axios.get('api/books').then(books=>books.data);
	//2)、发送两个请求
	axios.all([axios.get('api/books'),axios.get('api/readers')]).then(
		axios.spread(function(books,readers){
			books.data
			readers.data
		});
	);

全局配置

所有axios请求的基础地址:
axios.defaults.baseURL = 'https://api.example.com';
所有post请求的默认content-type的值
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';

拦截器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MMT1YDG5-1615547574535)(C:\Users\何小仙\AppData\Roaming\Typora\typora-user-images\1615539496888.png)]

请求拦截器

请求拦截器是在请求到达后端之前,可以进行“拦截”(所有请求的全局处理),可以对配置进行修改,如:url,检查并在请求头携带token

axios.interceptors.request.use(function(config){ //config是请求时的配置信息。
	//一、可以修改baseURL
    // config.baseURL = "http://localhost:3000";
  
	//二、可以增加token的携带
	
    // token:是验证身份的。    
    // token的逻辑:    
    
    //1、token什么时候存储的?当登录成功时,后端会产生一个token,发给前端,前端拿到token,
        // 保存在cookie或者localStorage。

    //2、在请求拦截器里,就需要携带token给服务器端。
    
    // 1)、(假定存储在localStorage中)从localStorage中获取token
    let token = localStorage.getItem("token");
    if(token){
        config.headers.authorization = token;
    }
    
    //三、显示loading窗口
    // Loading = true;
    
    return config;
},function(){

});			
响应拦截器

​ 给返回的数据增加公共信息,或者,根据后端返回的状态码,来决定让用户进行再次登录,也可以改变loading的状态为false

axios.interceptors.response.use(
function (response) {//response参数是响应对象
   
   response.data.unshift({“goodsid”:“商品编号”,“goodsname”:“商品名称”,“goodsprice”:“商品价格”});//给响应的数据增加一个对象
   
   //隐藏loading窗口
   
   return response;
}, function (error) {
	console.log('响应出错')
	return Promise.reject(error)
})

特点:

从浏览器中创建 XMLHttpRequest/从 node.js 创建 http 请求
支持 Promise API
拦截请求和响应
转换请求数据和响应数据
取消请求
自动转换 JSON 数据
客户端支持防御 CSRF

面试题:

Ajax、jQuery ajax、axios和fetch的区别

Ajax:
ajax:最早出现的前后端交互技术,是原生js,核心使用XMLHttpRequest对象,多个请求之间如果有先后关系的话,就会出现回调地狱。

Jquery Ajax:
jquery Ajax是原生ajax的封装

Fetch:
fetch是ES6新增的,Fetch是基于promise设计的。fetch不是ajax的进一步封装,而是原生js。Fetch函数就是原生js,没有使用XMLHttpRequest对象。

axios:
axios是原生ajax的封装,基于promise对象的。Axios也可以在请求和响应阶段进行拦截。它不但可以在客户端使用,也可以在nodejs端使用。

路由

一、路由的作用

vue的路由使用在SPA应用中的组件跳转,相当于多页面的 a标签。官网

二、路由的基本使用

1、引入js文件的方式

1)、引入vue-router.js文件

<script src="js/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>

2)、定义若干个组件(为了跳转用)

let goodlist = {
   template:"<div>商品列表</div>"
}

let goodsdetail = {
   template:"<div>商品详情</div>"
}

3)、定义路由对象

3.1)路由配置(json数组)

let routes = [
	{path:'/goodslist',component:goodlist},
	{path:'/goodsdetail',component:goodsdetail}
];

3.2)、实例化一个vueRouter对象

let router = new VueRouter({
	   routes:routes
});

4)、挂载vueRouter对象

实例化vue对象,(把vueRouter对象,挂载(注册)到vue对象里)

let vm = new Vue({
    el:"#app",
    router
});

5)、跳转代码(声明式)

  <h1>路由跳转</h1>
  <hr/>
  <router-link to='/goodslist'>商品列表</router-link>
  <router-link to='/goodsdetail'>商品详情</router-link>
  <hr/>
  <router-view></router-view>

解释:

: 超链, 相当于标签a 。

: 组件显示的位置。

2、模块化的方式(脚手架里)

脚手架安装时,会默认安装vue-router。

1)、安装

npm i vue-router -S

2)、定义组件(单文件组件)

如:HelloWorld.vue 、 Home.vue

3)、路由配置和引入

3.1)、创建vueRouter对象(定义路由对象,配置路由)

// src/router/index.js

import Vue from 'vue'

//1. 引入路由包
import Router from 'vue-router'

//2. 安装插件包到Vue上, 
Vue.use(Router)

//3. 路由配置
let routes = [
  {path: '/', component: HelloWorld}
  {path: '/home',component: Home}, //route  一条路由的配置
]

//4.路由实例
let router = new Router({ //插件路由对象
  // routes:routes
  routes,
});

//5.导出路由实例,让它去控制vue根
export default router

3.2)、在main.js中引入vueRouter对象,并植入到根属性

// src/main.js
import router from './router/index';

new Vue({
  el: '#app',
  router,
  ………………
})

4)、跳转

4.1)、声明式跳转
<router-link to="/home">声明式跳转</router-link>
<router-link to="/home" tag='li' active-class='类名' >声明式跳转</router-link>

<router-view>展示区</router-view>

router-link 组件属性:

​ to:跳转的路径

​ tag=‘li’ 指定编译后的标签,默认是 a 标签。

​ active-class=‘类名’ 指定激活后的样式 模糊匹配
​ exact-active-class=‘类名’ 指定激活后的样式 严格匹配

​ router-link和router-view组件是vue-router插件提供的

4.2)编程式跳转(编程式导航)
先了解 r o u t e r 和 router和 routerroute

** r o u t e r : ∗ ∗ 表 示 v u e R o u t e r 对 象 , 由 于 我 们 在 v u e 对 象 里 把 v u e R o u t e r 对 象 植 入 到 根 属 性 里 , 所 以 , 在 组 件 内 部 是 可 以 使 用 router :** 表示vueRouter对象,由于我们在vue对象里把vueRouter对象植入到根属性里,所以,在组件内部是可以使用 router:vueRoutervuevueRouter使router拿到该对象的。

**$route:**表示匹配到的当前路由,可以简单粗暴的理解为,路由配置中的某个json对象。当然,这个对象里的信息比路由配置的更多。

编程式导航

**this.$router.push(字符串/对象):**添加一个路由 (记录到历史记录)

// 字符串
$router.push('home')

// 对象
$router.push({ path: 'home' })

2)、this.$router.replace({name:’…’}) //替换一个路由 (不记录到历史记录)

3)、this.$router.go(-1|1)|back()|forward() //回退/前进

三、动态路由匹配

1、路由传参

路由配置

const router = new VueRouter({
  routes: [
    // 动态路径参数 以冒号开头,相当于在path里声明了一个变量 id
    { path: '/user/:id', component: User }
  ]
})

跳转

//匹配上 '/user/:id' 路径,01001的值会赋给id
<router-link to="/user/01001">用户01001的信息</router-link>
<router-link to="/user/01002">用户01002的信息</router-link>

组件中获取id的值

 模板里的写法:
 $route.params.id 
 
 脚本里的写法:
 this.$route.params.id

2、捕获所有路由或 404 Not found 路由

1)、通配符 *

'*'  会匹配所有路径
'/user-*'  会匹配以 `/user-` 开头的任意路径

注意:路由匹配的顺序是按照路由配置的顺序进行的,所以,你肯定不能把 * 的路径放在最前面,否则,后面的路由配置都不起作用了。

当使用一个通配符时,$route.params 内会自动添加一个名为 pathMatch 参数。它包含了 URL 通过通配符被匹配的部分。

如:

路由配置:

{
  // 会匹配以 `/user-` 开头的任意路径
  path: '/user-*'
}

路由跳转:

this.$router.push('/user-admin')

组件里:

this.$route.params.pathMatch // 'admin'

404

{
  path: '*',
  component: NoPage组件
}

四、命名路由

给路由起个名字,就可以使用名字来指定路由

1、路由配置

const router = new VueRouter({
  routes: [
    {
      path: '/user/:userId',
      name: 'user',
      component: User
    }
  ]
})

2、跳转

<router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>

五、重定向

{
  path: '/',  //默认页
  redirect: '/home' //配置型跳转
}, 

总结:路由传参:

一、params

1、传:

1)、动态路由匹配(路由配置里写)

{ path: '/user/:id', component: User }

2)、跳转时传参

1)、跳转时,使用字符串
//声明式
<router-link to="/user/01001">用户01001的信息</router-link>
//编程式
$router.push("/user/01001");

2)、跳转时,使用对象
//声明式: 命名的路由,同时传参
<router-link :to="{ name: 'user', params: { id: 123 }}">User</router-link>
//编程式:
$router.push({ name: 'user', params: { id: '123' }})

2、接:

 //模板里的写法:
 $route.params.参数名
 
 //脚本里的写法:
 this.$route.params.参数名

二、query

1、传:

1)、路由配置不用改(不用动态路由匹配)

{ path: '/user', component: User }

2)、跳转时,使用 path

//声明式: 
<router-link :to="{ path: '/user', query: { id: '01001' }}">User</router-link>
//编程式:
$router.push({ path: '/user', query: { id: '01001' }})
注意:如果提供了 path,那么params 会被忽略
// 带查询参数,变成 /user?id=01001

name 对应params

path对应query

2、接

 //模板里的写法:
 $route.query.参数名
 
 //脚本里的写法:
 this.$route.query.参数名

在传递多个参数时,query方式对应的地址栏上好阅读。

params方式 使用在传递一个参数时

六、路由传参和props

​ 一个组件在项目中,有两种使用方式(组件显示在浏览器上):

​ 1、自定义标签的方式,外部给组件传数据,用props

​ 2、使用路由跳转的方式,外部给组件传数据,用params或者query。

​ 如果, 一个组件需要从外部传入数据, 并且在项目中,这两种方式的使用都会出现,那么,在组件内部就需要适应这两种情况。

如何使用 props 属性将组件和路由解耦

props 被设置为 trueroute.params 将会被设置为组件属性。

路由配置:

{ path: '/user/:id', component: User, props: true },

组件:

const User = {
  props: ['id'],
  template: '<div>User {{ id }}</div>'
}

七、嵌套路由

子路由嵌套

1、路由配置

// src/router/index.js

const router = new VueRouter({
  routes: [
    { 
        path: '/user/:id',
        component: User,
        children: [
       		 {
          		// 当 /user/:id/profile 匹配成功,
          		// UserProfile 会被渲染在 User 的 <router-view> 中
          		path: 'profile',
          		component: UserProfile //子组件UserProfile显示在父组件User里的<router-view>
        	},
        	{
          		// 当 /user/:id/posts 匹配成功
          		// UserPosts 会被渲染在 User 的 <router-view> 中
          		path: 'posts',
          		component: UserPosts//子组件UserPosts显示在父组件User里的<router-view>
        	}
      	]
    }
  ]
})

后台管理系统里,经常要使用子路由嵌套

2、组件的展示:

​ 子组件会展示在父组件里的 的位置。

八、路由模式

路由模式分为两种:hash和history。

区别(面试题):

1)、外观上

hash模式时,路径上有#。

history模式时,路径上没有#。

2)、跟后端有关的区别

hash模式不会给后端发请求

history模式会给后端发请求,需要后端配合。要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。否则,就会返回404。如果后端有同名的url,那么就会找后端的url。

// src/router/index.js

let router = new VueRouter({ //插件路由对象
   routes,
   // mode:'hash'//哈希模式   location.href
   mode:'history'//历史记录   history.pushState
});

九、扩展

路由守卫

全局守卫

// src/router/index.js

//前置钩子
router.beforeEach((to, from, next) => {
  
  //	to: 目标路由
  //	from: 当前路由
  
  // next() 跳转  一定要调用
  next(false);//不让走
  next(true);//继续前行
  next('/login')//走哪
  next({path:'/detail/2',params:{},query:{}})//带点货
  
  // 守卫业务
  if(to.path=='/login' || to.path=='/reg' ){    
    next(true)
  }else{
      //是否登录的判断
      if(没有登录过){
          next('/login');
       }else{
          next(true);
       }
  }
})

//后置
router.afterEach((to,from)=>{
  //全局后置守卫业务
})

//过程:
1、请求一个路径:如:/Index
2、经历前置守卫(路由配置前)
   决定了能去哪个路径
3、根据去的路径,找对应component(路由配置)
4、经过后置守卫(路由配置后)
5、创建组件

路由独享守卫(只有前置)

// src/router/index.js
{
  path: '/user',
  component: User,
  //路由独享守卫
  beforeEnter: (to,from,next)=>{ //路由独享守卫 前置 
    console.log('路由独享守卫');
    if(Math.random()<.5){
      next()
    }else{
      next('/login')
    }
  }
 }

独享,没有后置

组件内部守卫

//在组件内部写:

//组件内部钩子
beforeRouteEnter (to, from, next) {//前置
  // 不!能!获取组件实例 `this`
  // 因为当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate (to, from, next) {
  // 在当前路由改变,但是该组件被复用时调用
  // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
  // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
  // 可以访问组件实例 `this`
},
beforeRouteLeave (to, from, next) {//后置
  // 导航离开该组件的对应路由时调用
  // 可以访问组件实例 `this`
}

注意:

路由独享守卫,守的是path

组件内部守卫,守的是component

路由元信息

定义路由的时候配置 meta 字段

//src/plugins/router.js
{
  path: '/home',
  component: Home,
  meta: { requiresAuth: true }
}

访问 meta 字段

this.$route.meta
to.meta

路由懒加载

​ 当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。

$jiang

转场效果:transition

第三方动画库( V3.5.2——CSS3动画库 )的地址: https://www.jq22.com/yanshi819

状态(数据)管理(VueX)

官网

大型项目中使用,不是必须的,但是,咱们在学习阶段,在项目中一定要用。

抛出一个问题

​ vue单页面应用中,每个组件内部的数据在data中存放,供vue组件内部使用,但是,vue组件之间的数据共享怎么办?即A组件要使用B组件里的数据怎么办?

传统的处理方案:父子组件传值、平行组件在跳转时,利用url,路由里的传值等等。

传统处理方案的问题:

1、兄弟组件传值:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8Vdjfa9n-1615547574536)(C:\Users\何小仙\AppData\Roaming\Typora\typora-user-images\1615277406006.png)]

缺点:
1、数据传递复杂,容易出错
2、浪费内存

2)、平行组件,无关组件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rO5NCpN2-1615547574537)(C:\Users\何小仙\AppData\Roaming\Typora\typora-user-images\1615277433940.png)]

缺点:
1、数据传递复杂,容易出错
2、浪费内存

解决方案(vueX)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4APFs3lp-1615547574537)(C:\Users\何小仙\AppData\Roaming\Typora\typora-user-images\1615277789204.png)]

vueX的作用

1、vuex能够保存全局数据,供整个应用使用

2、vuex保存的数据是响应式的

3、vuex保存的数据可以跟踪状态的变化

vueX的核心概念(创建vueX.store对象里的配置项):

state : 数据仓库 ,存储所有的 共享数据 ,相当于vue组件里的data
Getter : 在state的基础上 派生的数据, 相当于vue组件里 computed
Mutation:修改state的数据时,用mutation,这与跟踪状态 有关系
Action:解决mutation里只能有同步代码的问题,action里可以有异步代码

modules:模块化

vueX的数据流转

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dsQjeZ3T-1615547574538)(C:\Users\何小仙\AppData\Roaming\Typora\typora-user-images\1615278957366.png)]

vueX的基本使用步骤

1)、安装

npm安装vueX:  npm install vuex –save

2)、创建vueX.store对象

./src/store/index.js

import Vue  from 'vue'
//引入vueX
import Vuex from 'vuex'
//把vueX安装到Vue里
Vue.use(Vuex)

export default new Vuex.Store({
    state:{
          id: '01'
    },
    getters:{},
    mutations:{},
    actions:{}
})

3)、把vueX.store对象植入到vue的根属性

./src/main.js

import store from './store'

new Vue({
  	el: '#app',
  	store,//把store对象植入到vue的根属性,把store通过vue实例注入到每个子组件    
    router
  		……………………	
})

4)、组件里获取数据:

//模板里:
$store.state.id
//脚本里
this.$store.state.id

5)、组件里保存数据

this.$store.state.id = '02' //这个方式虽然可以,但是不推荐,因为,它不能跟踪状态,推荐,强烈推荐使用mutation来修改数据。

vueX的核心概念详解:

state : 数据仓库 ,存储所有的 共享数据 ,相当于vue组件里的data,是一个单一状态树

使用:
在组件中使用:this.$store.state.属性名。

export default new VueX.Store({
    state:{
        age:12,
        isAdult:"未成年",
        isAllowMarry:false
    }
    …………………………
});

组件里获取:

{{$store.state.age}}
{{$store.state.isAdult}}
{{$store.state.isAllowMarry?"可以结婚了":"不要着急,再等等"}}

Getter : 在state的基础上 派生的数据, 相当于vue组件里 computed


export default new VueX.Store({
    state:{
        age:12,
        isAdult:"未成年",
        isAllowMarry:false
    },
    getters:{
        ageChina:state=>{
            let shi = parseInt(state.age/10); //1
            let ge = state.age%10;//2
            let str = "";
            switch(shi){
                case 1:str='一';break;
                case 2:str='二';break;
            }
            str+='十'
            switch(ge){
                case 1:str+='一';break;
                case 2:str+='二';break;
                case 3:str+='三';break;
                case 4:str+='四';break;
                case 5:str+='五';break;
            }
            return str+'岁';
        }
    },
    …………………………
});

组件里获取

{{$store.getters.ageChina}}

Mutation:修改state的数据时,用mutation,这与跟踪状态 有关系。

在vuex中,强烈建议使用mutation改变state中的值。可以在vuex对象中使用严格模式来检测:
const store = new Vuex.Store({ strict: true })


export default new VueX.Store({
    state:{
        age:12,
        isAdult:"未成年",
        isAllowMarry:false
    },
    getters:{
        ageChina:state=>{
            let shi = parseInt(state.age/10); //1
            let ge = state.age%10;//2
            let str = "";
            switch(shi){
                case 1:str='一';break;
                case 2:str='二';break;
            }
            str+='十'
            switch(ge){
                case 1:str+='一';break;
                case 2:str+='二';break;
                case 3:str+='三';break;
                case 4:str+='四';break;
                case 5:str+='五';break;
            }
            return str+'岁';
        }
    },
    // mutations:是跟踪状态。这里面只能有同步代码,这是必须的。
    mutations:{
    //    incAge(state,num){
    //        state.age+=num;
        incAge(state,payload){
            state.age+=payload.num;
           if(state.age>=18){
              state.isAdult = "已成年";
           }else{
              state.isAdult = "未成年";
           }
           if(state.age>=22){
               state.isAllowMarry = true;
           }else{
               state.isAllowMarry = false;
           }
       }
    },
   …………………………………………
});

提交mutation(如果有异步就在action提交,如果没有异步就在组件里提交)

//组件里提交
this.$store.commit('incAge',num);

//action提交
incAge(context,num){
     context.commit('incAge',num);
}

Action:解决mutation里只能有同步代码的问题,action里可以有异步代码

​ Action 类似于 mutation,不同在于:
​ Action 提交的是 mutation,而不是直接变更状态。
​ Action 可以包含任意异步操作。


export default new VueX.Store({
    state:{
        age:12,
        isAdult:"未成年",
        isAllowMarry:false
    },
    getters:{
        ageChina:state=>{
            let shi = parseInt(state.age/10); //1
            let ge = state.age%10;//2
            let str = "";
            switch(shi){
                case 1:str='一';break;
                case 2:str='二';break;
            }
            str+='十'
            switch(ge){
                case 1:str+='一';break;
                case 2:str+='二';break;
                case 3:str+='三';break;
                case 4:str+='四';break;
                case 5:str+='五';break;
            }
            return str+'岁';
        }
    },
    // mutations:是跟踪状态。这里面只能有同步代码,这是必须的。
    mutations:{
    //    incAge(state,num){
    //        state.age+=num;
        incAge(state,payload){
            state.age+=payload.num;
           if(state.age>=18){
              state.isAdult = "已成年";
           }else{
              state.isAdult = "未成年";
           }
           if(state.age>=22){
               state.isAllowMarry = true;
           }else{
               state.isAllowMarry = false;
           }
       }
    },
    // 如果出现异步操作,就需要使用action,action不是必须的。
    actions:{
    //    incAge(context,num){
    //        context.commit('incAge',num);
    //    }
        // incAge(context,payload){
        //     context.commit('incAge',payload);
        // }
        incAge(context,payload){
            // context.commit('incAge',payload);
            context.commit(payload);
        }
    }
});

Action和mutation的区别
在代码的角度上,action是来提交mutation的
在用途(意义)上:区分 actions 和 mutations 并不是为了解决竞态问题,而是为了能用 devtools 追踪状态变化。 vuex 真正限制你的只有 mutation 必须是同步的这一点。
当然actions可以包含任何异步操作,如果程序员自己想处理异步操作,也可以不使用actions。

Module
当项目比较大时,所有的全局数据存放在state里,会非常混乱,怎么办?使用module,把数据分门别类的进行处理,即:模块化。
每个模块是一个独立的store。然后由总体的store引入所有的分模块store。

如下:

两个模块分别管理不同类型的数据:

./src/store/moduleA.js

export default {
	state: {count:1}, 
    mutations: { ... }, 
    actions: { 
         incCount(context){
            console.log("moduleA的action");
            setTimeout(()=>{
                context.commit("incCount");
            },2000);
        }
    }, 
    getters: { ... } 
}
                                                   
./src/store/moduleB.js
export default {
	state: {count:2}, 
    mutations: { ... }, 
    actions: { 
         incCount(context){
            console.log("moduleB的action");
            setTimeout(()=>{
                context.commit("incCount");
            },2000);
        }
    }, 
    getters: { ... } 
}

在总的store里包含所有的模块:

./src/store/index.js

import Vue from "vue";
import vueX from "vuex";
import moduleA from "./moduleA";
import moduleB from "./moduleB";

Vue.use(vueX);

export default new vueX.Store({
    modules:{
        moduleA:moduleA,
        moduleB:moduleB
    }
    //简写:
    modules:{
        moduleA,moduleB
    }
});

组件里使用数据时,多加个模块名即可,如:

$store.state.moduleA.count // -> moduleA 的状态
$store.state.moduleB.count // -> moduleB 的状态

组件里派发action时,如果,直接写action的名字,那么就会找到所有同名的action。

//如:
//在组件内容,派发action
this.$store.dispatch({
          type:"incCount"
        });
那么就会,派发给moduleA和moduleB里的incCount。即:moduleA和moduleB里的incCount都被执行了。如果不希望这样,那就用命名空间

模块(Module)里的命名空间(namespaced:true)。

默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的——这样使得多个模块能够对同一 mutation 或 action 作出响应。

如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced: true 的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。

1)、模块定义时,加上namespaced:true

export default {
    namespaced:true,
    state:{
        count:1
    },
    mutations:{
       ……………………
    },
    actions:{        
        incCount(context){
            console.log("moduleA的action");
            setTimeout(()=>{
                context.commit("incCount");
            },2000);
        }
    }
}

2)、派发action时,加上模块名

this.$store.dispatch('moduleA/incCount');

酌情扩展: mapState, mapGetters, mapActions, mapMutations

反向代理

在前后端分离开发的场景,前端有个服务器(提供页面)。后端也有个服务器(提供接口)。

1、开发环境,前端要连后端的接口,就会出现跨域问题。

2、生产(发布)环境:

​ 1)、如果还是前后端分离(在不同的服务器上)。依然有跨域问题(nginx)

​ 2)、如果前后端代码在一起(一个服务器),不存在跨域问题

跨域的解决方案:

1)、jsonp

2)、CORS(后端配合) :cross origin resource sharing 后端解决跨域的方案

​ php 中,这么写:header(“Access-Control-Allow-Origin:*”);

3)、反向代理(前端webpack的devServer)

反向代理

一、为啥要叫反向代理

正向代理隐藏真实客户端,反向代理隐藏真实服务端。

二、反向代理解决的问题
就是跨域,在前后端联调的情况下,前端有服务器,后端也有服务器,那么联调时,就存在跨域问题

三、反向代理的原理
通过伪造请求使得http请求为同源的,然后将同源的请求发送到反向代理服务器上,由反向代理服务器去请求真正的url,这样就绕过直接请求真正的url导致跨域问题。

四、vue-cli2.x里反向代理使用步骤和配置

1、安装axios与http-proxy-middleware(vue-cli中默认已经安装)

2、在config/index.js中的dev中配置

3、在dev配置对象中找到proxyTable:{ }

4、添加如下配置(把对“/api”开头的访问转换成对 http://www.itzhihu.cn的访问):

​      proxyTable: {'/api': {  // 要替换的地址,在本地新建一个/api的访问路径 。
​     				   target: 'http://www.itzhihu.cn',  // 替换成啥,要访问的接口域名 。
​    				    changeOrigin: true,  // 是否跨域 
  				      pathRewrite: {'^/api': ''    //重写接口,去掉/api, 在代理过程中是否替换掉/api/路径 }
  		    }}

这样以后访问以“/api”开头的地址都会转向http://www.itzhihu.cn的访问

五、图示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-snxknqdp-1615547574539)(C:\Users\何小仙\AppData\Roaming\Typora\typora-user-images\1615539624744.png)]

六、vue-cli3+配置反向代理

1.打开(新建)项目根/vue.config.js

​ 写上如下代码:

module.exports = {
  devServer:{
    //设置代理
    proxy: { //代理是从指定的target后面开始匹配的,不是任意位置;配置pathRewrite可以做替换
      '/api': { //axios访问 /api ==  target + /api
       		 target: 'http://localhost:3001',
       		 changeOrigin: true, //创建虚拟服务器 
          	pathRewrite: {
        			 	 '^/api': ''    //重写接口,去掉/api, 在代理过程中是否替换掉/api/路径 
     		}
      	}
      }
   }
}

注意:

1、修改完 vue.config.js文件后,必须要重启服务器

2、如果你复制了笔记上的代码,而且出现问题,那么,删掉看不见的中文空格和注释。

postman

跟后端联调项目

一、联调前:保证前后端各自没有问题

1.后端用postman测试一下(跟接口文档一致)

2.前端连接的jsonserver,(跟接口文档一致)

目的:说明前后端的程序逻辑没有问题。

二、前后端的计算机连在同一个局域网

可以在cmd 里,用命令 ping 对方的ip地址,来测试是否在同一个局域网里。

目的:保证前后端能够连通

三、前端通过postman测试接口(这一步很重要)

切记,切记:前端先用postman测试接口,如果响应的数据没有问题(能够返回数据,并且返回的数据格式跟接口文档一致),说明,和后端的链接没有问题,也能看出后端给的数据格式是否正确,前后端是否都根据接口进行了开发。

注意:前后端的content-type是否一致。

目的:验证后端的程序有没有问题。

四、跨域

1)、前端解决跨域(反向代理)

Vue-cli2.X:

把config/index.js中的反向代理改一下:

把target改成后端的ip地址和端口号


proxyTable: {'/api': {  //所有“/api”开头的请求都会进行代理(转)

​					target: 'http://10.35.167.126:8080',  //所有“/api”开头的请求都会转到 http://10.35.167.126:8080

​					changeOrigin: true,

​					pathRewrite: {'^/api': '' // 去掉"/api"}}

}

Vue-cli3/4

1.打开(新建)项目/vue.config.js

​ 写上如下代码:

module.exports = {
  devServer:{
    //设置代理
    proxy: { //代理是从指定的target后面开始匹配的,不是任意位置;配置pathRewrite可以做替换
      '/api': { //axios访问 /api ==  target + /api
       		 target: 'http://localhost:3001',
       		 changeOrigin: true, //创建虚拟服务器 
          	pathRewrite: {
        			 	 '^/api': ''    //重写接口,去掉/api, 在代理过程中是否替换掉/api/路径 
     		}
      	}
      }
   }
}

注意:一旦修改了配置(vue.config.js),必须重启服务器(npm run serve)

2)、后端解决跨域(cors)

四、发送请求,获取数据

​ 测试功能

五、如果跟后端的交互还有问题,需要查看network:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oo2CsEfI-1615547574540)(C:\Users\何小仙\AppData\Roaming\Typora\typora-user-images\1615539710296.png)]

移动端事件

1、click事件

Click:单击事件
类似于PC端的click,移动端也有click,在移动端中,连续click的触发有200ms ~ 300ms的延迟

为什么移动端不用click
移动端的click有300ms延迟的问题,在移动端浏览器中,连续两次点击是缩放操作,所以在用户点击屏幕后,浏览器会检查在300ms内是否有另一次点击,如果没有才触发click事件。因为有延迟,所以不使用。

2、touch类事件

触摸事件,有touchstart touchmove touchend touchcancel 四种

touchstart:手指触摸到屏幕会触发
touchmove:当手指在屏幕上移动时,会触发
touchend:当手指离开屏幕时,会触发
touchcancel:可由系统进行的触发,比如手指触摸屏幕的时候,突然alert了一下,或者系统中其他打断了touch的行为,则可以触发该事件
onorientationchange: 屏幕旋转事件

示例:

<div id="box">
</div>


window.onload = function () {
	
	//setInterval(()=>{alert("弹出东西了");},2000);
	
	document.getElementById("box").onclick = function(){
		//click有延迟300毫秒的情况,所以,先触发的是ontouchstart.
		//如果在300毫秒内不放开,则不触发click事件,只触发touchend事件
		//如果在300毫秒内放开了,就会触发click事件.
		this.innerHTML +="onclick:点击了<br/>";
	}
	document.getElementById("box").ontouchstart  = function(){
		this.innerHTML +="ontouchstart<br/>";//手指触摸到屏幕
	}
	document.getElementById("box").ontouchend    = function(){
		this.innerHTML +="ontouchend<br/>";//手指离开屏幕
	}
	document.getElementById("box").ontouchmove    = function(){
		this.innerHTML +="ontouchmove<br/>";//手指在屏幕上移动
	}    
    
	document.getElementById("box").ontouchcancel    = function(){
		console.log("touchcancel");//当两秒钟后弹出alert 后,touchcancel就被触发了。这个不常用
	}}

3、touch的优先级高于click

1)、如果touch的持续时间在300ms以上,就不会触发click事件

当你在红盒子里,按下300ms以上,再放开时,不触发click事件

​ 触发顺序: touchstart,touchend

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XO7cG2b8-1615547574541)(C:\Users\何小仙\AppData\Roaming\Typora\typora-user-images\1615542597647.png)]

2)、当你在红盒子里,按下不超过300ms,再放开时,触发顺序是:

​ 触发顺序: touchstart,touchend,click。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6Hd002CQ-1615547574542)(C:\Users\何小仙\AppData\Roaming\Typora\typora-user-images\1615542613763.png)]

3)、如果手指在屏幕上移动了,就不会触发click事件。

当你在红盒子里,按下,并移动,再放开,就算不超过300ms的时间,
也不会触发click事件,因为,移动时,就不触发click事件了
(移动事件的优先级高于click)。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D4vKmsHE-1615547574543)(C:\Users\何小仙\AppData\Roaming\Typora\typora-user-images\1615542638085.png)]

4、 阻止click事件的触发

我们在开发移动端时,肯定只希望触发touch类型的事件,所以,如何阻止click事件的触发。

​ 由于:当用户在点击屏幕的时候,系统会触发touch事件和click事件,touch事件优先级高,touch事件经过捕获, 冒泡 一系列流程处理完成后, 才会去触发click事件.

​ 所以:在touch事件里使用event.preventDefault()就可以阻止click事件的触发

示例:

	document.getElementById("box").ontouchstart  = function(event){
		this.innerHTML +="ontouchstart<br/>";//手指触摸到屏幕
		event.preventDefault();
	}

    document.getElementById("box").onclick = function(){
		this.innerHTML +="onclick:点击了<br/>";
	}

5、点透(面试题)

什么是点透
1、A 和 B两个html元素不是后代和继承关系(如果是后代继承关系的话,就直接是冒泡了), A的z-index大于B,把A显示在B之上
2、A元素绑定touch事件, 在touch事件处理函数里,让A立即消失,
3、B元素绑定click事件

示例:

<style type="text/css">
      #boxparent{
          position: relative;
      }
      #box1 {
        position: absolute;
        z-index: 1;
        background: red;
        width: 100px;
        height: 100px;
      }
      #box2{
        background: green;
        width: 200px;
        height: 200px;
      }
    </style>

    <div id="boxparent">
      <div id="box1">
      </div>
      <div id="box2">
      </div> 
    </div>

  <script type="text/javascript">
    var box1 = document.getElementById("box1");
    var box2 = document.getElementById("box2");
    box1.ontouchstart=function(e) {
       box1.style.display = 'none';
    };
    box2.onclick = function() {
      console.log('box2被点了');
    }
  </script>

为什么会这样?

​ 当用户在A元素上触摸屏幕时,先触发A元素的touch事件,然后隐藏了A,当click(延迟200-300ms)触发时,发现没有A了,只有B,所以,就触发了B元素的click事件。

点透和冒泡的区别:

冒泡:是元素之间存在父子关系。
点透:元素之间没有父子关系(叠放关系),只是由于click在移动端里有200-300ms的延时造成的。

6、第三方的移动端事件库

1)、zepto.js

​ Zepto是一个轻量级的针对现代高级浏览器的JavaScript库, 它与jquery有着类似的api。 如果你会用jquery,那么你也会用zepto。针对移动端开发的JavaScript库(不仅仅只是事件库)

  • tap — 元素 tap 的时候触发。(叩击)

  • singleTap and doubleTap — 这一对事件可以用来检测元素上的单击和双击。(如果你不需要检测单击、双击,使用 tap 代替)。

  • longTap — 当一个元素被按住超过750ms触发。

  • swipe, swipeLeft, swipeRight, swipeUp, swipeDown — 当元素被划过时触发。(可选择给定的方向)

示例:

	<div id="box">

    </div>
    
     $("#box").on("tap", function () {
        $('#box').append('<li>tap:单击屏幕</li>');
    });

    $("#box").on("singleTap", function () {
        $('#box').append('<li>singleTap:单击屏幕</li>');
    });

    $("#box").on("doubleTap", function () {
        $('#box').append('<li>双击屏幕</li>');
    });

    $("#box").on("longTap", function () {
        $('#box').append('<li>长安屏幕750ms</li>');
    });

    $("#box").on("swipe", function () {
        $('#box').append('<li>划过屏幕</li>');
    });


    $("#box").on("swipeLeft", function () {
        $('#box').append('<li>划过屏幕(左)</li>');
    });
    

    $("#box").on("swipeRight", function () {
        $('#box').append('<li>划过屏幕(右)</li>');
    });

    
    $("#box").on("swipeUp", function () {
        $('#box').append('<li>划过屏幕(上)</li>');
    });
    
    $("#box").on("swipeDown", function () {
        $('#box').append('<li>划过屏幕(下)</li>');
    });

</script>

2)、touch.js

介绍:

touch.js 是百度开发的一款插件,针对移动端应用的事件插件

绑定事件

touch.on( element, types, callback );
功能描述:事件绑定方法,根据参数区分事件绑定和事件代理。

解除事件绑定

touch.off( element, types, callback )

参数类型描述
elementelement或string元素对象、选择器
typesstring事件的类型(多为手势事件),多个事件用空格分开
callbackfunction事件处理函数,移除函数与绑定函数必须为同一引用;
事件类型

缩放:

事件名描述
pinchstart缩放手势起点
pinchend缩放手势终点
pinch缩放手势
pinchin收缩
pinchout放大

旋转

描述
rotateleft向左旋转
rotateright向右旋转
rotate旋转

滑动

参数描述
swipestart滑动手势起点
swiping滑动中
swipeend滑动手势终点
swipeleft向左滑动
swiperight向右滑动
swipeup向上滑动
swipedown向下滑动
swipe滑动

拖动

dragstart拖动屏幕起始
drag拖动屏幕中
dragend拖动屏幕结束

按键

hold长按屏幕
tap单击屏幕
doubletap双击屏幕

示例:

<div id="box">
</div>

<script type="text/javascript" src="js/zepto.min.js"></script>
<script type="text/javascript" src="https://cdn.bootcss.com/touchjs/0.2.14/touch.js"></script>
<script type="text/javascript">
    touch.on("#box", 'tap',  function () {
       console.log("tap");
    });

    touch.on("#box", 'swipestart',  function () {
       console.log("swipestart");
    });

</script>
touch的配置

touch.config(config)

config为一个对象


{
    tap: true,                  //tap类事件开关, 默认为true
    doubleTap: true,     //doubleTap事件开关, 默认为true
    hold: true,                 //hold事件开关, 默认为true
    holdTime: 650,         //hold时间长度
    swipe: true,                //swipe事件开关
    swipeTime: 300,             //触发swipe事件的最大时长
    swipeMinDistance: 18,       //swipe移动最小距离
    swipeFactor: 5,             //加速因子, 值越大变化速率越快
    drag: true,                 //drag事件开关
    pinch: true,                //pinch类事件开关
}

事件代理格式

1)、事件代理绑定
touch.on( delegateElement, types, selector, callback );
2)、解除事件代理绑定
touch.off( delegateElement, types, selector, callback )

参数类型描述
delegateElementelement或string事件代理元素或选择器
typesstring手势事件的类型**,** 可接受多个事件以空格分开**;**
selectorstring代理子元素选择****器
callbackfunction事件处理函****数

示例:

   <ul id="touchs">
        <li> 第一个</li>
        <li> 第二个 </li>
        <li> 第三个</li>
    </ul>
    
   
   touch.on("#touchs","tap","li",function(event){
        
        console.log("li被点击了");
        let e = event || window.event;
        console.log(e.target.tagName);

    });

事件处理对象(event)

​ 事件处理函数的第一个参数为事件对象,除了原生属性之外,百度手势库还提供了部分新属性

属性描述
originEvent触发某事件的原生对象
type事件的名称
rotation旋转角度
scale缩放比例
direction操作的方向属性
fingersCount操作的手势数量
position相关位置信息**,** 不同的操作产生不同的位置信息
distanceswipe****类两点之间的位移
distanceX, x手势事件x方向的位移值**,** 向左移动时为负数
distanceY, y手势事件y方向的位移值**,** 向上移动时为负数
anglerotate****事件触发时旋转的角度
durationtouchstart touchend****之间的时间戳
factorswipe****事件加速度因子
startRotate()启动单指旋转方法,在某个元素的touchstart触发时调用

第三方(UI)组件

使用一些别人开发好的组件、组件库、插件、ui库,来提高项目开发进度,组件库通常是某家公司开发并开源出来,获取方式可以通过npm、github查询,或者百度vue组件库,这里有一些推荐1,、推荐2推荐3

使用方式

//安装
npm i vue-swipe --save  安装

//引入
import './node_modules/vue-swipe/dist/vue-swipe.css'; 引入样式 
import { Swipe, SwipeItem } from 'vue-swipe'; 引入组件

Vue.component('swipe', Swipe);    //注册安装到全局
Vue.component('swipe-item', SwipeItem);

//注册到选项components 私有使用

组件类别

pc端、后台管理、客户端

  • element-ui 饿了么 √
  • iview 个人
  • ant design 蚂蚁金服

移动端、客户端

  • mint-ui 饿了么
  • vant 有赞 电商 √
  • vue-material
  • muse-ui
  • VUX
  • cube-ui
  • vonic
  • Vue-Carbon
  • YDUI

通用

  • bootstrap4/3
  • ameizi

elementUI

官网

安装

npm i element-ui -S

整体引入全局使用

import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);

按需引入

npm install babel-plugin-component -D

//修改babel配置 baberc/babel.config.js
//添加:
"plugins": [
    [
      "component",
      {
        "libraryName": "element-ui",
        "styleLibraryName": "theme-chalk"
      }
    ]
  ]
//全局使用:   所有应用内部组件直接使用 <el-button></..>
import { Button } from 'element-ui';
Vue.component(Button.name, Button); | Vue.use(Button)

//局部使用: 只有当前组件可使用
import { Select, Option } from 'element-ui';
components:{
  'bulala':Select,
  [Option.name]:Option,
},

mintUI

官网

安装

npm i mint-ui -S

整体引入

import Mint from 'mint-ui';
import 'mint-ui/lib/style.css'
Vue.use(Mint);

按需引入

//全局使用: npm install babel-plugin-component -D
import { Button } from 'mint-ui';
Vue.component(Button.name, Button);

//babel.config.js配置: 添加
"plugins": [
  [
    "component", 
    {
      "libraryName": "mint-ui",
      "style": true
    }
 ]
]

//组件内部引入,只有当前组件可使用
import { Button } from 'mint-ui';
components:{
  //'bulala':Button,
  [Button.name]:Button,
},

vant

官网

整体引入

import Vant from 'vant';
import 'vant/lib/index.css';
Vue.use(Vant);

//全局使用: <van-xxx></van-xxx>

按需引入

npm i babel-plugin-import -S

// 对于使用 babel7 的用户,可以在 babel.config.js 中配置
module.exports = {
  plugins: [
    ['import', {
      libraryName: 'vant',
      libraryDirectory: 'es',
      style: true
    }, 'vant']
  ]
};

//局部使用:
  import { Button, Cell } from 'vant';
  components:{Button,Cell}
  //<Button></Button>

//全局使用
  import { Button } from 'vant';
  Vue.use(Button); 
  //<van-xxx></van-xxx>

组件样式顺序

  1. 修改主题
  2. 使用props
  3. 添加 class/style
  4. 查看元素,查询相关样式名,修改编译后的样式
  5. scope 影响
    css解决: .a >>> .b { /* … */ } 深度选择器
    Sass解决: .a{ /deep/ .b{} }

举例

app组件

​ Tabbar 标签栏

home组件

​ Search 搜索/轮播/Tab 标签页

category组件

​ Tab 标签页

follow组件

​ pull-refresh 下拉刷新 / 单元格 cell

column组件

​ Popup 弹出层

detail组件

​ NavBar 导航栏/栅格布局

ShopCart组件

​ NavBar 导航栏/SwipeCell 滑动单元格>Card 卡片/SubmitBar 提交订单栏/stepper步进器

user组件

​ flex布局/van-tag标记/Panel 面板/cell-group 单元格组/ icon图标

login组件

​ NavBar 导航栏/field输入框

reg组件

​ van-uploader 文件上传

下拉刷新上拉加载(Better-scroll)

介绍

​ better-scroll 是一款重点解决移动端(已支持 PC)各种滚动场景需求的插件。它的核心是借鉴的 iscroll 的实现,它的 API 设计基本兼容 iscroll,在 iscroll 的基础上又扩展了一些 feature 以及做了一些性能优化,不仅可以做普通的滚动列表,还可以做轮播图、picker 等等。better-scroll 是基于原生 JS 实现的,不依赖任何框架。

http://ustbhuangyi.github.io/better-scroll/doc/api.html

安装和使用

1、安装:npm install better-scroll --save
2、引入:import BScroll from ‘better-scroll’
3、使用:

   let scroll = new BScroll('.wrapper',{
        scrollY: true,
        click: true
    }) 

注意:

​ better-scroll 的初始化时机很重要,因为它在初始化的时候,会计算父元素和子元素的高度和宽度,来决定是否可以纵向和横向滚动。因此,我们在初始化它的时候,必须确保父元素和子元素的内容已经正确渲染了。如果子元素或者父元素 DOM 结构发生改变的时候,必须重新调用 scroll.refresh() 方法重新计算来确保滚动效果的正常

在vue脚手架里使用

假定组件文件为:./src/components/Books.vue

//1、引入:

import BScroll from 'better-scroll'

//2、使用

<template>
  <div class="wrapper">
    <ul>
      <div v-show="downShow">加载中…………………………</div>
      <li v-for="(book,index) in books" :key="index">
        <p>编号:{{book.id}}</p>
        <img src="../assets/img/img1.jpg" />
        <p>书名:{{book.name}}</p>
        <p>价格:¥{{book.price}}</p>
      </li>
      <div v-show="upShow">加载中…………………………</div>
    </ul>
  </div>
</template>

<script>
import BScroll from "better-scroll";

export default {
  name: "Books",
  data: function() {
    return {
      downShow: false, //下拉时显示
      upShow: false,//上拉时显示
      books: [
        {
          id: "000001",
          name: "红楼梦",
          author: "曹雪芹",
          price: 52,
          type: "hot"
        },
        {
          id: "000002",
          name: "西游记",
          author: "曹雪芹",
          price: 52,
          type: "recommend"
        }
      ],
      scroll: null
    };
  },
  mounted() {
    //1、实例化 Better-scroll对象
    this.scroll = new BScroll(".wrapper", {
      scrollY: true, //开启纵向滚动。
      //click: true,
      pullDownRefresh: { //下拉刷新的配置
        threshold: 30 // 当下拉到超过顶部 30px 时,触发 pullingDown 事件
      },
      pullUpLoad: { //上拉加载的配置
        threshold: -50 // 在上拉到超过底部 50px 时,触发 pullingUp 事件
      }
    });
      
    //2、绑定事件 pullingDown
    this.scroll.on("pullingDown", () => {
      this.downShow = true; //显示加载中……的文字或者图片
      this.getDataUnshift(); //发送请求获取数据
    });
      
    //3、绑定事件 pullingUp
    this.scroll.on("pullingUp", () => {
      this.upShow = true; //显示加载中……的文字或者图片
      this.getDataPush(); //发送请求获取数据
    });
      
  },
 methods: {
    getDataUnshift() {
      setTimeout(() => {
        let arr = [];
        for (let i = 0; i < 5; i++) {
          arr.push({
            id: parseInt(Math.random() * 1000) + "",
            name: "三国",
            author: "罗贯中",
            price: (Math.random() * 100).toFixed(2)
          });
        }
        this.books = [...arr, ...this.books];
        this.downShow = false; //隐藏加载中……的文字或者图片
        this.$nextTick(() => {
          this.refresh(); //渲染后要重新计算父子元素的高度
        });
      }, 1000);
    },

    getDataPush(cb) {
      setTimeout(() => {
        let arr = [];
        for (let i = 0; i < 5; i++) {
          arr.push({
            id: parseInt(Math.random() * 1000) + "",
            name: "三国",
            author: "罗贯中",
            price: (Math.random() * 100).toFixed(2)
          });
        }
        this.books = [...this.books, ...arr];
        this.upShow = false;//隐藏加载中……的文字或者图片
        this.$nextTick(() => {
          this.refresh(); //渲染后要重新计算父子元素的高度
        });
      }, 1000);
    },
    refresh() {
      this.scroll.finishPullDown();
      this.scroll.finishPullUp();
      this.scroll.refresh(); //重新计算元素高度
    }
  }
};
</script>

<style scoped>
.wrapper {
  width: 100%;
  height: 800px;
}
img {
  width: 100%;
}
</style>

构造函数的选项

scrollX

  • 类型:Boolean
  • 默认值: false
  • 作用:当设置为 true 的时候,可以开启横向滚动。
  • 备注:当设置 eventPassthrough 为 ‘horizontal’ 的时候,该配置无效。

scrollY

  • 类型:Boolean
  • 默认值:true
  • 作用:当设置为 true 的时候,可以开启纵向滚动。
  • 备注:当设置 eventPassthrough 为 ‘vertical’ 的时候,该配置无效。

click

  • 类型:Boolean
  • 默认值:false
  • 作用:better-scroll 默认会阻止浏览器的原生 click 事件。当设置为 true,better-scroll 会派发一个 click 事件,我们会给派发的 event 参数加一个私有属性 _constructed,值为 true。但是自定义的 click 事件会阻止一些原生组件的行为,如 checkbox 的选中等,所以一旦滚动列表中有一些原生表单组件,推荐的做法是监听 tap 事件

probeType

  • 类型:Number
  • 默认值:0
  • 可选值:1、2、3
  • 作用:有时候我们需要知道滚动的位置。当 probeType 为 1 的时候,会非实时(屏幕滑动超过一定时间后)派发scroll 事件, 函数节流 ; 当 probeType 为 2 的时候,会在屏幕滑动的过程中实时的派发 scroll 事件;当 probeType 为 3 的时候,不仅在屏幕滑动的过程中,而且在 momentum 滚动动画运行过程中实时派发 scroll 事件。

对象的属性:

maxScrollY

  • 类型:Number
  • 作用:scroll 最大纵向滚动位置。
  • 备注:scroll 纵向滚动的位置区间是 0 - maxScrollY,并且 maxScrollY 是负值。

对象的方法:

refresh()

  • 参数:无
  • 返回值:无
  • 作用:重新计算 better-scroll,当 DOM 结构发生变化的时候务必要调用确保滚动的效果正常。

对象的事件

scroll

  • 参数:{Object} {x, y} 滚动的实时坐标
  • 触发时机:滚动过程中,具体时机取决于选项中的 [probeType](

vue项目打包

1、打包前的修改

在开发vue项目时,使用了反向代理的配置(在webpack中的配置),并使用了Axios的BaseUrl:
Axios.defaults.baseURL = ‘/api/’;
部署上线前,由于webpack的反向代理不起作用了,所以先把baseURL改为真正的服务器端地址。如果服务器的地址为:http://localhost:3000/,则配置如下。
Axios.defaults.baseURL = ‘http://localhost:3000/’;

2、打包命令

npm run build
命令行(项目目录下)运行此命令,就会在项目里产生dist目录。
dist目录就是打包后的结果,把该目录下的文件拷贝到服务器的根目录,记住一定是根目录(引入webpack打包时,会把很多文件路径设置为根路径,如:静态资源的目录static)

补充:vue脚手架项目打包前后的对比

在webpack打包时,会在项目目录创建dist/static,在该目录存放所有的静态资源,同时还会在dist/static目录下分别创建目录 img,css,js等目录存放不同类型的静态资源。

​ 对于assets目录下的文件,会分类放入不同的文件夹下。

​ 对于static目录下的文件,原封不动的复制到dist/stiatc下。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jKDgFRDh-1615547574544)(C:\Users\何小仙\AppData\Roaming\Typora\typora-user-images\1615543091911.png)]

远程服务器上线(nginx)

如果上线时,项目还是前后端分离(如:在两个服务器上),那么webpack中的反向代理怎么办?没事,nginx服务器可以。


查漏补缺:

1、vue-cli3+的配置

在项目根目录创建文件vue.config.js

1)、反向代理配置

module.exports = {
  devServer:{
    //设置代理
    proxy: { //代理是从指定的target后面开始匹配的,不是任意位置;配置pathRewrite可以做替换
      '/api': { //axios访问 /api ==  target + /api
        target: 'http://localhost:3001',
        changeOrigin: true, //创建虚拟服务器 
        ws: true, //websocket代理
      },
     ……………………
    }
  }
}

2)、别名配置:

npm  install  path  --save

vue.config.js配置

constpath = require("path");

function resolve(dir) {
	  return path.join(__dirname, dir);      
}

module.exports = {
      chainWebpack: config => {
    	config.resolve.alias
     	.set("@", resolve("src"))
      	.set("assets", resolve("src/assets"))
     	.set("components", resolve("src/components"))
  }
}

2、axios的封装

3、脚手架里使用sass

1、安装
npm install node-sass --save-dev //安装node-sass
npm install sass-loader --save-dev //安装sass-loader
npm install style-loader --save-dev //安装style-loader 或者 vue-style-loader !

2、打开build文件夹下面的webpack.base.config.js。增加如下代码:

module: { 
    rules: [ 
       ……………………………………
     {
                          test: /\.scss$/,
                          loaders: ["style", "css", "sass"]
                     } 
                  ] 
           }

3.在需要用到sass的地方添加lang=scss

 <style lang="scss" scoped> 

 </style>

4、可能出现的问题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3X3nE3hF-1615547574545)(C:\Users\何小仙\AppData\Roaming\Typora\typora-user-images\1615542738964.png)]
说明版本太高,安装指定版本即可:
npm install sass-loader@6.0.6 --save-dev

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4BQh0zOI-1615547574546)(C:\Users\何小仙\AppData\Roaming\Typora\typora-user-images\1615542784640.png)]

安装node-sass就行。
npm install node-sass --save-dev

coped>
.wrapper {
  width: 100%;
  height: 800px;
}
img {
  width: 100%;
}
</style>

构造函数的选项

scrollX

  • 类型:Boolean
  • 默认值: false
  • 作用:当设置为 true 的时候,可以开启横向滚动。
  • 备注:当设置 eventPassthrough 为 ‘horizontal’ 的时候,该配置无效。

scrollY

  • 类型:Boolean
  • 默认值:true
  • 作用:当设置为 true 的时候,可以开启纵向滚动。
  • 备注:当设置 eventPassthrough 为 ‘vertical’ 的时候,该配置无效。

click

  • 类型:Boolean
  • 默认值:false
  • 作用:better-scroll 默认会阻止浏览器的原生 click 事件。当设置为 true,better-scroll 会派发一个 click 事件,我们会给派发的 event 参数加一个私有属性 _constructed,值为 true。但是自定义的 click 事件会阻止一些原生组件的行为,如 checkbox 的选中等,所以一旦滚动列表中有一些原生表单组件,推荐的做法是监听 tap 事件

probeType

  • 类型:Number
  • 默认值:0
  • 可选值:1、2、3
  • 作用:有时候我们需要知道滚动的位置。当 probeType 为 1 的时候,会非实时(屏幕滑动超过一定时间后)派发scroll 事件, 函数节流 ; 当 probeType 为 2 的时候,会在屏幕滑动的过程中实时的派发 scroll 事件;当 probeType 为 3 的时候,不仅在屏幕滑动的过程中,而且在 momentum 滚动动画运行过程中实时派发 scroll 事件。

对象的属性:

maxScrollY

  • 类型:Number
  • 作用:scroll 最大纵向滚动位置。
  • 备注:scroll 纵向滚动的位置区间是 0 - maxScrollY,并且 maxScrollY 是负值。

对象的方法:

refresh()

  • 参数:无
  • 返回值:无
  • 作用:重新计算 better-scroll,当 DOM 结构发生变化的时候务必要调用确保滚动的效果正常。

对象的事件

scroll

  • 参数:{Object} {x, y} 滚动的实时坐标
  • 触发时机:滚动过程中,具体时机取决于选项中的 [probeType](

vue项目打包

1、打包前的修改

在开发vue项目时,使用了反向代理的配置(在webpack中的配置),并使用了Axios的BaseUrl:
Axios.defaults.baseURL = ‘/api/’;
部署上线前,由于webpack的反向代理不起作用了,所以先把baseURL改为真正的服务器端地址。如果服务器的地址为:http://localhost:3000/,则配置如下。
Axios.defaults.baseURL = ‘http://localhost:3000/’;

2、打包命令

npm run build
命令行(项目目录下)运行此命令,就会在项目里产生dist目录。
dist目录就是打包后的结果,把该目录下的文件拷贝到服务器的根目录,记住一定是根目录(引入webpack打包时,会把很多文件路径设置为根路径,如:静态资源的目录static)

补充:vue脚手架项目打包前后的对比

在webpack打包时,会在项目目录创建dist/static,在该目录存放所有的静态资源,同时还会在dist/static目录下分别创建目录 img,css,js等目录存放不同类型的静态资源。

​ 对于assets目录下的文件,会分类放入不同的文件夹下。

​ 对于static目录下的文件,原封不动的复制到dist/stiatc下。

[外链图片转存中…(img-jKDgFRDh-1615547574544)]

远程服务器上线(nginx)

如果上线时,项目还是前后端分离(如:在两个服务器上),那么webpack中的反向代理怎么办?没事,nginx服务器可以。

vue脚手架项目从开发到打包完整的流程

一、git版本管理

1、建立远程仓库,邀请成员,分配权限

2、每个程序员 克隆项目(就会有本地仓库)

二、脚手架搭建和开发

1、项目负责人搭建脚手架,并且把项目上传到远端

​ 1)、vue create 项目名 | vue init webpack 项目名

​ 2)、项目负责人把脚手架的代码拷贝到本地仓库所在目录下。

​ 3)、项目负责人把脚手架的代码上传到远端版本库。

​ git add .

​ git commit -m “”

​ git push 远端地址 main

4、各程序员下载远端的脚手架空项目

5、各程序员进行开发

​ 1)、建立分支

​ 2)、写代码

3)、上传:git add git commit git push

​ 4)、 循环以上三步,直到当前功能开发完毕

​ 5)、合并分支的代码到主分支

​ 6)、上传主分支的代码到远端。

​ 7)、每天重复以上六步,这就是程序员的开始。

6、注意点:

​ 每次push的时候,应该先pull。

三、打包上线

​ 1、项目负责人(或指派人员)打包

​ npm run build

​ 结果在dist目录下。

​ 1)、第一种情况:前后端不分离(在一台服务器上)

​ 不存在跨域问题,所以不需要反向代理。那么,打包前需要看看baseURL要不要改。 /api

​ 2)、第二种情况:前端后端分离

		   有跨域问题,但是,打包时,不需要做修改。

​ 2、上线:

​ 1)、第一种情况:前后端不分离(在一台服务器上)

​ 把dist的目录的代码发给后端,后端统一传到互联网(www服务器)服务器上

​ 2)、第二种情况:前端后端分离

​ 跨域问题(反向代理),使用nginx解决。

​ 前端www服务上使用nginx。把曾经在webpack中的反向代理配置,写在nginx里。使用ftp工具放在www服务器上。

​ 后端会把自己的代码放在另外一台(www)服务器上

参与评论 您还未登录,请先 登录 后发表或查看评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:游动-白 设计师:我叫白小胖 返回首页

打赏作者

何小仙

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值