v2-lt

目录

VUE2

vue框架的两大核心:

一、前端开发历史

二、MV*模式

库 VS 框架

MVC架构模式

MVP架构模式

MVVM架构模式

vue是MVVM

三、开发工具

四、vue框架的初识

vue的介绍

vue的示例代码

vue框架的理解

数据驱动和声明式渲染

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

五、vue语法(view层)

data选项(model层)

数据绑定

条件渲染(指令)

列表渲染(循环指令)

事件绑定(指令)

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

指令总结

样式操作

非响应式情况

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

data属性:

methods:

计算属性(computed)

属性检测(侦听属性)watch

自定义指令

过滤器

自定义指令和过滤器的使用场景区别

混入(mixins)

自定义指令、过滤器、混入的使用场景区别

虚拟DOM和diff算法(原理)(面试题)

为什么使用虚拟DOM:

真实DOM和虚拟DOM的区别:

diff算法:

diff算法和虚拟dom结合起来,如何提升性能的思路:

面试题:请问你怎么理解虚拟DOM和diff算法

vue-dev-tools安装

七、组件

八、vue对象

类和实例(对象)API

生命周期(面试题)

项目环境(脚手架)

单文件组件

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

脚手架里使用sass

单页面应用(SPA)

mock数据

json-server

mock.js(自行研究)

数据交互

解决方案

fetch

axios

swiper

$nextTick(); (面试题)

路由

一、路由的作用

二、路由的基本使用

三、动态路由匹配

四、命名路由

五、重定向

路由传参:

六、路由传参和props

七、嵌套路由

八、路由模式

九、扩展

转场效果:transition

状态(数据)管理(VueX)

vuex是什么?

抛出一个问题

解决方案(vueX)

vueX的作用

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

vueX的数据流转

vueX的基本使用步骤

vueX的核心概念详解:

酌情扩展

反向代理

postman

跟后端联调项目

移动端事件

1、click事件

2、touch类事件

3、touch的优先级高于click

4、 阻止click事件的触发

5、点透(面试题)

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

第三方(UI)组件

elementUI

mintUI

vant

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

介绍

安装和使用

在vue脚手架里使用(数据写死)

在vue脚手架里使用(数据来自后端)

在vue脚手架里使用(封装了)

构造函数的选项

对象的属性:

对象的方法:

对象的事件

vue项目打包上线

1、打包前的修改

2、打包命令

3、远程服务器上线(nginx)

vue脚手架项目从开发到打包完整的流程(从零开始做一个项目)

查漏补缺:

1、vue-cli3/4的配置

2、axios的封装

3、脚手架里使用sass


VUE2

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架构模式

MVC的出现是用在后端(全栈时代)

M:model,模型,主要完成业务功能,在数据库相关的项目中,数据库的增删改查属于模型(重点)。(nodeJS,不含html的php文件),没有页面,是纯粹的逻辑

V:view,视图,主要负责数据的显示(HTML+CSS,动态网页(jsp,含有html的php文件))页面的展示和用户的交互。

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

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

MVP架构模式

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

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

MVP模式与MVC模式的区别:

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

MVVM架构模式

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

vue是MVVM

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

总结:

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

三、开发工具

vscode,webstorm,HbuilderX等等

四、vue框架的初识

vue的介绍

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

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

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

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

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

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

  • Vue 不支持 IE8 及以下版本

vue的示例代码

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

vue框架的理解

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

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

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

数据驱动和声明式渲染

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

2、声明式渲染

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

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

vue2.×的 数据绑定是通过 数据劫持观察者模式 的方式来实现的

1、数据劫持: vue2.× 使用Object.defineProperty(); Vue3使用的是proxy。 当你把一个普通的 JavaScript 对象(json)传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。

目的是:感知属性的变化。当给属性赋值时,程序是能够感知的(知道的)。如果知道的话,在Vue中可以去改变模板上的显示(使用发布订阅者模式)。

Object.defineProperty()函数:JavaScript中的Object.defineProperty()函数_田江的博客-CSDN博客

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

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

五、vue语法(view层)

data选项(model层)

功能:

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

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

数据绑定

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

模板语法 — Vue.js

插值表达式

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

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

示例:

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

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

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

内容指令

内容指令:让标签的内容变成动态的。

指令名:v-text 和 v-html

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

1、v-text="数据名" 转义输出

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

示例:

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

对比v-text和插值({{}})表达式:

1)、当网络速度慢的时候(vueJs还没有处理自己的语法(v-text和{{}}),插值表达式会在页面上出现 {{}} 的显示,但是指令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> 

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

<input type="button" v-bind:disabled="isButtonDisabled" value="测试">

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

javascript表达式

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

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

示例:

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

注意:

不能使用语句 ,条件语句可以使用三元表达式代替

条件渲染(指令)

条件渲染 — Vue.js

指令名: 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的操作(如:创建和删除)是非常耗性能的。为什么? 请看:页面的重排和回流(提升移动端网页性能)_田江的博客-CSDN博客

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

示例:

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

列表渲染(循环指令)

列表渲染 — Vue.js

指令名: v-for

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

格式:

用in或者用of都可以:

<li v-for="值 in 数据" v-bind:属性="值">{{值}}</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 字符串">

注意:

1、空数组,null,undefined不循环

2、也可以进行循环嵌套

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

面试题:为什么不建议把v-for和v-if连用?

VUE面试题系列03_田江的博客-CSDN博客

列表渲染时的key:

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

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

2、如果你希望进行dom操作,就用key(key不要使用下标),因为key的目的是为了唯一标识一个元素

有了key后,可以跟踪每个节点的身份,从而重用和重新排序现有元素

建议尽可能在使用 v-for 时提供 key attribute,除非遍历输出的 DOM 内容非常简单,或者是刻意依赖默认行为(就地复用)以获取性能上的提升。

注意:

key不要使用(数组)下标,并不是会报错,而是失去了唯一标识的意义


事件绑定(指令)

事件处理 — Vue.js

指令名: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({
    el:,
    data:{}
  methods:{
    方法1:function(ev,参数){
        业务
        这里面的this是vue对象本身
    }
    方法2(ev,参数){
         业务
	}
  }
})

获取事件对象:

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

如:

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

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

如:

<button type="button" @click="fn03('qq',$event)">修改数据</button>
………………
fn03(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.stop.self 会阻止所有的点击,而 `v-on:click.self.stop 阻止自身的点击,stop就不起作用了

<div class="greenbox" @click="fnGreen">
     <!-- <div class="redbox" @click.self.stop="fnRed"> -->
     <div class="redbox" @click.stop.self="fnRed">
             <div class="bluebox" @click="fnBlue"></div>
      </div>
</div> 

按键修饰符

<!--普通键-->
<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	鼠标中键

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

表单输入绑定 — Vue.js

指令名: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数据">

其它表单元素的双向绑定

表单输入绑定 — Vue.js

表单修饰符

<input v-model.修饰符="数据" />
			  .number 	把标签的值转成数字赋给vue的数据
			  .trim 	删除前后空格
			  .lazy   	确认时才修改model数据(文本框,用的是change事件)

示例:

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

 <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和v-if 是例外情况)。指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM,以下是官方的指令,程序员也可以自定义指令。

常见指令:

  • v-text: 更新元素的 textContent。如果要更新部分的 textContent ,需要使用 {{ Mustache }} 插值。

  • v-html:更新元素的 innerHTML

  • v-bind:动态地绑定一个或多个属性(特性),或一个组件 prop 到表达式。

  • v-on:绑定事件监听器。事件类型由参数指定。

  • v-model:在表单控件或者组件上创建双向绑定

  • v-show:根据表达式值的真假,切换元素的 display (CSS 属性)。

  • v-if:根据表达式的值的真假条件渲染元素(与编程语言中的if是同样的意思)

  • v-else:表示否则(与编程语言中的else是同样的意思)

  • v-else-if:(与编程语言中的else if是同样的意思)

  • v-for:可以循环数组,对象,字符串,数字,

  • v-pre:跳过这个元素和它的子元素的编译过程(vue处理的过程)。可以用来显示原始 Mustache 标签。跳过大量没有指令的节点会加快编译。

  • v-cloak:防闪烁,模板没编译完,电脑配置差,网速慢等等,有可能会看到{{}},体验不佳,不如用css先隐藏,之后再显示,包括被包裹的子元素。这个指令保持在元素上直到关联实例结束编译。和 CSS 规则,如 [v-cloak] { display: none } 一起用时,这个指令可以隐藏未编译的 Mustache 标签直到实例准备完毕。

  • v-once:只渲染元素和组件一次。随后的重新渲染,元素/组件及其所有的子节点将被视为静态内容并跳过。这可以用于优化更新性能。

指令缩写:

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


样式操作

Class 与 Style 绑定 — Vue.js

操作样式,就是属性绑定,只不过绑定的属性是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 练习的实现

为了让大家体会vue是数据驱动,根据班级情况尝试扩展如下: 二阶段的运动 全选反选(购物车)

非响应式情况

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

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

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

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

  • 使用数组的索引(根索引)的方式改变数组的元素时

  • 修改数组的长度时

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

    所以,强烈建议:不要用下标的方式修改数组的元素,不要修改数组的长度,可以把未来需要的数据都声明在data选项内部,不要对数组使用非变异的api(数组的变异方法:列表渲染 — Vue.js),如果使用数组的非变异方法,需要把结果再赋给数组。

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

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

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

this.$forceUpdate() 强制刷新



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

data属性:

不再说了

methods:

也暂时不说了,这里面放的是函数的定义,就是普通函数的定义。这些函数可以作为事件处理函数,也可以在其它地方调用。这些函数里的this是vue对象本身。

计算属性(computed)

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

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

语法

//定义
//1、计算属性默认是只读的
computed:{
  计算属性名: function(){return 返回值}		
}

//2、如果想修改计算属性的值
computed:{
    计算属性名:{
      //当读取计算属性的值时,会调用get函数  
      get:function(){        
        return 返回值
      },
      //当修改计算属性的值时,会调用get函数
      set:function(newVal){//newVal:是计算属性的新值。
        
      }
    }
}

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

面试题:

computed VS methods

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

属性检测(侦听属性)watch

计算属性和侦听器 — Vue.js

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

定义一个选项

watch:{
  被侦听的属性名:'methods的里函数名'    //数据名==data的key
  被侦听的属性名:函数体(new,old){}
  被侦听的属性名:{
    handler:function(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
            }
        }
    });

面试题:

VUE面试题系列01(带数字,带序号),前端面试题_田江的博客-CSDN博客

07vue_计算属性_setter和getter_田江的博客-CSDN博客_vuejs 计算属性的getter和setter

计算属性 VS 函数 (方法)VS 属性检测

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

自定义指令

自定义指令 — Vue.js

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

全局定义

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

  • 钩子函数的参数:

    钩子函数的参数 (即 el、binding、vnode 和 oldVnode)。 自定义指令 — Vue.js

[el]  使用指令的DOM元素

[binding]  是个对象 含有调用指令时传入的 参数

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>
    

    

模拟v-bind指令的功能:

 Vue.directive("mybind",{
     bind:function(el,binding){
         el[binding.arg] = binding.value;
     },
     update:function(el,binding){
         el[binding.arg] = binding.value;
     }
 });

全局定义格式(简写)

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

模拟v-bind指令的功能:

Vue.directive("mybind",function(el,binding){
    el[binding.arg] = binding.value;
});

钩子函数的详解:

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

局部定义

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

示例:

回到顶部

//自定义指令的代码
// 注册一个全局自定义指令 `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="position:fixed;bottom:10px;right:10px;width: 100px;height: 100px;background-color:red;">
            回到顶部
        </div>
</div> 

拖拽:

./js/mydirections.js

Vue.directive("drag", {
    inserted: function (el, binding) {
        el.style.position = "absolute";
        let offsetX;
        let offsetY;
        let parentLeft = el.offsetParent.offsetLeft;
        let parentTop = el.offsetParent.offsetTop;
        let maxLeft = el.offsetParent.offsetWidth - el.offsetWidth;
        let maxTop = el.offsetParent.offsetHeight - el.offsetHeight;

        // 鼠标按下的函数
        function mousedownFn(event) {
            let e = event || window.event;
            offsetX = e.offsetX
            offsetY = e.offsetY
            // onmousemove
            // offsetParent: 找到最近的有定位属性的那个元素
            el.offsetParent.addEventListener("mousemove", mousemoveFn);
        };

        // 鼠标移动的函数
        function mousemoveFn(event) {
            let e = event || window.event;
            // 一、数据处理
            let left1 = e.pageX - parentLeft - offsetX;
            let top1 = e.pageY - parentTop - offsetY;
            if (left1 < 0) {
                left1 = 0;
            }
            if (left1 > maxLeft) {
                left1 = maxLeft;
            }
            if (top1 < 0) {
                top1 = 0;
            }
            if (top1 > maxTop) {
                top1 = maxTop;
            }
            // 二、外观呈现
            el.style.left = left1 + "px";
            el.style.top = top1 + "px";
        }

        // onmousedown
        el.addEventListener("mousedown", mousedownFn);

        // onmouseup
        document.addEventListener("mouseup", function () {
            el.offsetParent.removeEventListener("mousemove", mousemoveFn);
        });

    }
    }
);


<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title></title>
    <style>
        .redbox {
            width: 200px;
            height: 200px;
            border-radius: 10px;
            background-color: red;
        }
        #box{
            position: relative;
            border: 1px solid black;
            width: 500px;
            height: 400px;
        }
    </style>
</head>

<body style="height: 1200px">
    <div id="box">
        <div class="redbox" v-drag>我可以被拖拽</div>
    </div>
</body>

</html>
<script src="./js/vue.js"></script>
<script src="./js/mydirections.js"></script>
<script>
    let vm = new Vue({
        el: "#box",
        data: {}
    });

</script>

过滤器

过滤器 — Vue.js

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

全局定义

Vue.filter('过滤器名称',函数(要过滤的元数据,参数1,参数n){
           		过滤器的功能
           		return 过滤的结果           
           })

使用

{{数据名 | 过滤器名(参数1,参数2)}}

v-bind:属性="数据| 过滤器名(参数1,参数2)"

[|]  管道符

局部定义

new Vue({
    ……………………
    filters:{
      过滤器名称:函数(要过滤的元数据,参数){
            过滤器的功能
            return 过滤的结果      
      }	//函数必须要有返回值
    }
})

示例:

阿拉伯数字的金额转为大写的金额: 12.56

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title></title>
    <style>
       
    </style>
</head>

<body>
    <div id="box">
        <input type="number" v-model.number="money"><br/>
        <p>¥:{{money}}</p>
        <p>大写:{{money | toChina}}</p>
    </div>
</body>

</html>
<script src="./js/vue.js"></script>
<script>

    Vue.filter("toChina",function(value){
            let money = value.toFixed(2);//保留两位小数
             //汉字的数字
            var cnNums = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖'];
            //基本单位
            var cnIntRadice = ['', '拾', '佰', '仟'];
            //对应整数部分扩展单位
            var cnIntUnits = ['', '万', '亿', '兆'];
            //对应小数部分单位
            var cnDecUnits = ['角', '分', '毫', '厘'];
            //整数金额时后面跟的字符
            var cnInteger = '整';
            //整型完以后的单位
            var cnIntLast = '元';
            //最大处理的数字
            var maxNum = 999999999999999.9999;
            //金额整数部分
            var integerNum;
            //金额小数部分
            var decimalNum;
            //输出的中文金额字符串
            var chineseStr = '';
            //分离金额后用的数组,预定义
            var parts;
            if (money == '') { return ''; }
            money = parseFloat(money);
            if (money >= maxNum) {
                //超出最大处理数字
                return '';
            }
            if (money == 0) {
                chineseStr = cnNums[0] + cnIntLast + cnInteger;
                return chineseStr;
            }
            //转换为字符串
            money = money.toString();
            if (money.indexOf('.') == -1) {
                integerNum = money;
                decimalNum = '';
            } else {
                parts = money.split('.');
                integerNum = parts[0];
                decimalNum = parts[1].substr(0, 4);
            }
            //获取整型部分转换
            if (parseInt(integerNum, 10) > 0) {
                var zeroCount = 0;
                var IntLen = integerNum.length;
                for (var i = 0; i < IntLen; i++) {
                var n = integerNum.substr(i, 1);
                var p = IntLen - i - 1;
                var q = p / 4;
                var m = p % 4;
                if (n == '0') {
                    zeroCount++;
                } else {
                    if (zeroCount > 0) {
                    chineseStr += cnNums[0];
                    }
                    //归零
                    zeroCount = 0;
                    chineseStr += cnNums[parseInt(n)] + cnIntRadice[m];
                }
                if (m == 0 && zeroCount < 4) {
                    chineseStr += cnIntUnits[q];
                }
                }
                chineseStr += cnIntLast;
            }
            //小数部分
            if (decimalNum != '') {
                var decLen = decimalNum.length;
                for (var i = 0; i < decLen; i++) {
                var n = decimalNum.substr(i, 1);
                if (n != '0') {
                    chineseStr += cnNums[Number(n)] + cnDecUnits[i];
                }
                }
            }
            if (chineseStr == '') {
                chineseStr += cnNums[0] + cnIntLast + cnInteger;
            } else if (decimalNum == '') {
                chineseStr += cnInteger;
            }
            return chineseStr;
        }
    );

    let vm = new Vue({
        el: "#box",
        data: {
            money:12.56
        }
    });

</script>

自定义指令和过滤器的使用场景区别

1、相同点:自定义指令和过滤器都是封装

2、不同的:

1)、自定义指令使用在,dom操作上

2)、过滤器使用在:不需要做逻辑处理,只做数据转换。

混入(mixins)

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

用法

1、定义格式

let mixin1 = {
  data: ...
  methods:{
    sort(){
        
    }
  } ...
}

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

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

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

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

3、全局混入:

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

混入对象和vue对象配置项出现冲突:

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

2、 如果是生命周期钩子函数,那么都会调用(混入的钩子先调用),所以,不推荐使用全局混入。

自定义指令、过滤器、混入的使用场景区别

1、相同点:自定义指令、过滤器、混入都是封装

2、不同的:

1)、自定义指令使用在 dom操作上

2)、过滤器使用在:不需要做逻辑处理,只做数据转换。

3)、混入的功能比较强大,比较综合。因为,它里面包含了vue配置项的所有配置项。

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

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

域名购买

链接


虚拟DOM和diff算法(原理)(面试题)

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

为什么使用虚拟DOM:

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

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

真实DOM和虚拟DOM的区别:

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

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

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

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

diff算法:

1、 diff算法的作用:

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

2、diff算法的原理:

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

diff算法和虚拟dom结合起来,如何提升性能的思路:

总结:

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

2、oldVDom和真实DOM保持一致

3、数据变化时,影响的是(操作的是)newVDom

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

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

面试题:请问你怎么理解虚拟DOM和diff算法

1)、什么是虚拟dom和diff算法:

虚拟DOM: 用JSON对象模拟的真实dom,用来提升性能

diff算法:用来比较两个虚拟dom的不同之处。

2)、步骤(思路,流程)

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

2.2)、oldVDom和真实DOM保持一致

2.3)、数据变化时,影响的是(操作的是)newVDom

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

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

3)、diff算法的解释:

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

vue-dev-tools安装

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

方案2: vue调试工具vue-devtools安装及使用_田江的博客-CSDN博客_vue.devtools

七、组件

组件封装的是完整的页面功能(包括:HTML、CSS、JS),而函数只封装JS(逻辑)

组件的概念:

组件是自定义标签,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 组件变量名={
    template:'<div class="header">{{msg}},我是header组件</div>'
    data:function(){
        return {
            msg:”hi”
        }
    },
}; 

2、注册组件:

全局注册:

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

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

局部注册:

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

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

3、使用组件:

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

<标签名></标签名>

示例代码:

<body >
    <div id="box">
        <!--使用组件(组件就是自定义标签,所以,就是使用标签)-->
       <chat></chat>
    </div>
</body>

</html>
<script src="./js/vue.js"></script>

<script>

// 定义组件
let chatObj = {
    template:`
        <div>
            <div style="width:200px;height:300px;border:1px solid black" v-html="msg"></div><br/>
            <input type="text" v-model="str" />
            <input type="button" value="发送" @click="send" />
        </div>
    `,
    data:function(){
        return{
            str:"",
            msg:""
        }
    },
    methods:{
        send(){
            this.msg += this.str+"<br/>"
            this.str ="";
        }
    }
};

// 2、全局注册
Vue.component("chat",chatObj);
     
let vm = new Vue({
        el: "#box",
        data: {
        },
    	//局部注册:
        components:{
            "chat":chatObj
        }
    });

</script>

4、组件嵌套:

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

如:

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

   //父组件
    let myComParent = {
        template:`<div>
                        <p>我是p:{{msg}}</p>
                        <mycomson></mycomson>                    
                    </div>`,
        data:function(){
            return {
                msg:"hi"
            }
        },
        components:{
            // 局部注册了另外一个组件
            "my-com-son":myComSon
        }    
    };

5、组件编写方式与 Vue 实例(对象)的区别:

1、组件的标签名不可和html官方的标签名同名,标签名如果小驼峰,那么使用时,用短横线(羊肉串的写法),或者组件名首字母大写(这个规则是在未来的单文件组件,模块化的写法里使用)。

2、组件没有el选项,只有根实例存在el,组件里使用template定义模板

3、组件模板(html代码)只能有一个根标签

4、data是个函数(面试题)

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

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

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

使用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声明的属性,相当于封装函数时声明的形参。

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

props是外部给组件传入的数据(相当于函数中的参数)。data是组件内部的数据(相当于函数里的局部变量)

动态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),相当于函数的形参是引用类型

补充说明:

如果说在自定义标签里写的自定义属性,没有用props声明,那么,vue会默认把属性继承给根标签。所以,在在定义属性里写的class和style是可以起作用。

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、绑定事件(父组件里写):

HTML(标签)里的绑定方式:v-on

JS(Vue)里绑定方式: vue对象.$on(“事件名”,事件处理函数)

2、触发事件(子组件里写) : vue对象.$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, 子---> 父:事件

2)、父给子组件:refs

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

refs

首先,需要知道:用标签的方式使用组件(如:<my-com/>),实际就是创建了组件对象。只要拿到组件对象,那么组件对象里的methods就可以使用。

refs是vue中获取dom的一种方式,dom也就是标签,标签就是组件对象。即就是:拿到了dom,就相当于拿到了组件对象(这段话需要慢慢品……)

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

格式:

如:
    <p ref = "pId"> {{msg}}</p>
    <p ref = "pId02"> {{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()是子组件里的函数。
        }
    }
});

事件总线(event-bus)

event-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中使用<component :is="组件名变量">的方式实现。

示例:

<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.$属性名 是实例属性,vm.$方法名()是实例方法,同时vue类内部的this指向的是实例vm。 实例 vm.$属性名对等 vue选项的 根key。

如:vm.$on() vm.$emit(),vm.$data, vm.$el ……………………。

生命周期(面试题)

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

链接

彻底理解vue的钩子函数,vue的生命周期理解,什么是vue的生命周期,钩子函数_田江的博客-CSDN博客_vue钩子函数生命周期

面试题:请问您对vue的生命周期是怎么理解的?

一、vue生命周期是什么

就是vue对象从创建,到使用,到销毁的过程。

二、vue对象生命周期经历了四个阶段,同时有八个钩子函数:

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

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

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

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

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

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

4)、组件销毁阶段:

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

如果组件在<keep-alive></keep-alive>缓存的话,那么,组件切换时,会调用的钩子函数是:

activated 和 deactivated

三、(当页面初始的数据来自后端)发送请求在哪个钩子函数,created

四、在beforeDestroy钩子函数里,会做什么事情?

1、清除事件总线绑定的事件

beforeDestroy() {
    // 移除 eventBus 监听器
    eventBus.$off('custom-event')
}

2、清除定时器。因为,定时器是属于window对象的,启动的定时器也属于window对象,只要网页不关闭,window对象就不会销毁。所以,组件销毁时,并不会销毁定时器:

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

</html>
<script src="./js/vue.js"></script>

<script>


let yuLe = {
    template:"<div>娱乐新闻</div>",
    data(){
        return {
            timer:null,
            msg:"hi"
        }
    },
    created(){
        console.log("娱乐组件创建");
        var ord=0;
        this.timer = setInterval(function(){
            console.log("定时器:"+ord++);
        },1000);
    },
    beforeDestroy(){
        window.clearInterval(this.timer);
        console.log("娱乐组件销毁前");
    }
}

let eightGua = {
    template:"<div>八卦新闻</div>",
    beforeCreate(){
        console.log("八卦组件创建");
    }
}


let vm = new Vue({
    el: "#box",
    data: {
        currCom:yuLe,
        coms:[yuLe,eightGua]
    },
    components:{
        yuLe,
        eightGua
    },
    methods:{
        show(idx){
            this.currCom = this.coms[idx];
        }
    }
});
</script>

总结:

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

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

3、为什么叫钩子函数:和回调函数是同样的道理,只不过钩子函数更多会强调(函数调用的)时机。

思考题:

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

created,

因为,一般来说,后端返回来的数据需要赋给vue对象的属性(this.属性名),在created里是最早能够拿到vue对象属性的。如果在beforeMount和mounted里就有点晚了。

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

项目环境(脚手架)

脚手架搭建的项目环境是一个模块化,工程化,自动化的环境。

单文件组件

xx.vue,内部组成(template + script +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)环境搭建

cli: command line interface

一、通过官方脚手架,(命令行的方式)搭建模块化,工程化,自动化开发环境

  • 搭建脚手架3/4+

    //1、安装(脚手架工具的3.x/4.x)
    npm install -g @vue/cli
    
    //1、创建项目:使用脚手架搭建项目(文件夹)
    //1)、如果想搭建版本 v3.x/4.x
    vue create 项目目录
    
  • 如果还想搭建脚手架2的项目

//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他人生成的项目环境也可以。

去掉eslint提示:

1、在项目的根目录下创建文件:vue.config.js。

2、在vue.config.js里增加如下代码:

module.exports = {

lintOnSave:false,

}

3、重启项目:

按 ctrl +C 把项目先结束了,重新运行 npm run serve

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

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模块化复习

ES6的模块化_田江的博客-CSDN博客

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 任何类型的变量的值(大部分时候是对象)

export default 100;

输入

格式:
import 变量名 from "模块所在的路径及其文件名"; (如果是自定义模块就需要写路径,如果是官方的或第三方的模块,不需要写路径)

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.box //返回一个对象 {选择器:'混淆后的选择器',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去给它赋值,使用v-bind),那么,文件来自于 public下,但是,src后面的值,不能带“public”

原因(原理):

1、开发目录和发布目录(二阶段用的gulp,三阶段用的webpack(vue脚手架里用的webpack))

2、在浏览器运行的是发布目录的代码

所以说,最终的路径应该以发布目录的路径为准。

二阶段能够直接看到发布目录的代码,webpack默认是在内存中放的。如果想看那就用 npm run build打包

webpack只做打包,并不执行js代码。

明确一些名词(叫法):

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

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

坑:

1、不要在单文件组件里使用 <script>引入别的js文件。

如:

<script>
	export default {

	}
</script>

<script src="./js/tools.js" ></script> //千万不样写

脚手架里使用sass

1、安装

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

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

<style lang="scss" scoped> </style>

3、可能出现的问题

说明版本太高,安装指定版本即可: npm install sass-loader@6.0.6 --save-dev

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

单页面应用(SPA)

单页面应用的概念

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

就是整个项目就只有一个html页面(文件),首次加载时,把所有的html,css,js全部加载下来。通过操作dom的删除和创建(添加)来完成页面的切换

单页面应用优缺点

优点:

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

使用步骤:

1、初始化项目:

npm init -y

2、安装json-server

npm i json-server -D

3、打开项目编写数据

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

{
  "inc": {
    "count": 3
  },
  "vips": [
    {
      "username": "李茂军",
      "userpass": "123666",
      "id": 2
    },
    {
      "username": "李茂军02",
      "userpass": "123666",
      "id": 3
    },
    {
      "username": "李茂军03",
      "userpass": "123666",
      "id": 4
    },
    {
      "username": "李茂军05",
      "userpass": "123666",
      "id": 5
    },
    {
      "username": "王翠霞",
      "userpass": "123888",
      "id": 6
    },
    {
      "username": "李家恒",
      "userpass": "123999",
      "id": 7
    }
  ],
  "bannerImgs": [
    "/imgs/1.jpg",
    "/imgs/2.jpg",
    "/imgs/3.jpg",
    "/imgs/4.jpg"
  ],
  "comments": [
    {
      "bookId": "878911",
      "id": "001",
      "username": "李茂军",
      "time": "2021-10-27 11:40:15",
      "content": "三国演义很有内涵"
    },
    {
      "bookId": "878911",
      "id": "002",
      "username": "李茂军02",
      "time": "2021-10-27 11:41:15",
      "content": "三国演义很有内涵,女朋友很喜欢"
    }
  ],      
  "books":[
    {
      "id": "878911",
      "name": "三国",
      "author": "罗贯中",
      "price": 51.2,
      "img": "/imgs/1.jpg",
      "type": "hot"
    },
    {
      "id": "878912",
      "name": "水浒",
      "author": "施耐庵",
      "price": 51.5,
      "img": "/imgs/2.jpg",
      "type": "hot"
    },
    {
      "id": "878913",
      "name": "红楼梦",
      "author": "曹雪芹",
      "price": 51.8,
      "img": "/imgs/3.jpg",
      "type": "hot"
    },
    {
      "id": "878914",
      "name": "西游记",
      "author": "吴承恩",
      "price": 51.8,
      "img": "/imgs/4.jpg",
      "type": "hot"
    },
    {
      "id": "878915",
      "name": "大学",
      "author": "李茂军",
      "price": 52.8,
      "img": "/imgs/img1.jpg",
      "type": "new"
    },
    {
      "id": "878916",
      "name": "中庸",
      "author": "王翠霞",
      "price": 52.9,
      "img": "/imgs/img2.jpg",
      "type": "new"
    },
    {
      "id": "878917",
      "name": "论语",
      "author": "王锐",
      "price": 53.8,
      "img": "/imgs/img3.jpg",
      "type": "new"
    },
    {
      "id": "878918",
      "name": "孟子",
      "author": "李家恒",
      "price": 54.8,
      "img": "/imgs/img4.jpg",
      "type": "new"
    },
    {
      "id": "878919",
      "name": "孟子2",
      "author": "李家恒",
      "price": 54.8,
      "img": "/images/img4.jpg",
      "type": "new"
    },
    {
      "id": "878920",
      "name": "孟子3",
      "author": "李家恒",
      "price": 54.8,
      "img": "/images/img4.jpg",
      "type": "new"
    },
    {
      "id": "878921",
      "name": "孟子4",
      "author": "李家恒",
      "price": 54.8,
      "img": "/images/img4.jpg",
      "type": "new"
    },
    {
      "id": "878922",
      "name": "孟子5",
      "author": "李家恒",
      "price": 54.8,
      "img": "/images/img4.jpg",
      "type": "new"
    },
    {
      "id": "878923",
      "name": "孟子6",
      "author": "李家恒",
      "price": 54.8,
      "img": "/images/img4.jpg",
      "type": "new"
    }
  ],
  "readers": [
    {
      "id": "007",
      "name": "张三疯",
      "age": 35
    },
    {
      "id": "008",
      "name": "张四疯",
      "age": 32
    }
  ]
}

注意:每个键后面的值,只能是对象或者数组。

4、启动配置

在package.json下增加如下代码:

"scripts": {
    + "server":"json-server db.json"
},

5、运行

在命令行运行: npm run server

JSON-SERVER的各种请求:

可以使用postman等工具测试以下请求。

  • GET 请求数据列表

    获取所有的书籍

    localhost:3000/books

  • GET 请求指定ID的数据

    localhost:3000/books/878911

    返回的是json对象。

  • GET 请求指定字段值的数据

    localhost:3000/books?id=878911&name=三国演义

    获取id为878911和name为三国演义的书籍,返回结果是数组(就算只有一条数据)

  • GET 数据分页

    localhost:3000/bookS?_page=1&_limit=2

    _page表示页码

    _limit表示每页的条数

  • GET 数据排序

    localhost:3000/bookS?_sort=price&_order=asc
    - asc 升序 desc 降序

  • GET 区间查询

    localhost:3000/bookS?price_gte=30 & price_lte=40

    查找price的值大于等于30 而 小于等于40的记录

  • GET 搜索(模糊查询)

    搜索所有属性值里包括“三”的记录,模糊查询。在所有属性中进行查询。

    localhost:3000/bookS?q=三

  • GET 关联查询

    http://localhost:3000/books/878911?_embed=comments

查询books中id为878911,并把comments中 bookId为878911的数据关联出来,结果是:

{
"id": "878911",
"name": "三国",
"author": "罗贯中",
"price": 51.2,
"img": "/imgs/1.jpg",
"type": "hot",
"comments": [
 {
   "bookId": "878911",
   "id": "001",
   "username": "李茂军",
   "time": "2021-10-27 11:40:15",
   "content": "三国演义很有内涵"
 },
 {
   "bookId": "878911",
   "id": "002",
   "username": "李茂军02",
   "time": "2021-10-27 11:41:15",
   "content": "三国演义很有内涵,女朋友很喜欢"
 }
]
}

  • POST :添加数据

    请求方式为:POST

    • localhost:3000/books

    • 设置请求头:

      ajax里:Headers:{ Content-Type:'application/json' }

      postman工具里:在body -> raw-->json

     {
         "name": "三国演义",
         "author":"罗贯中",
         "price":35
     }
  • delete 删除数据

    请求方式为:DELETE

    localhost:3000/books/878911

    删除id为878911的书籍

  • patch 更新数据

    请求方式为:PATCH

    localhost:3000/books/878911
    - Headers:{ Content-Type:'application/json' }
    - body -> raw
    
     {
         "name": "四国演义"
     }

    把id为878911的的书籍的name属性改为“

    body里要传入的数据就是要改变的那个数据。

  • put 更新数据

    localhost:3000/books/878911

    修改id为3的数据,

    body里要传入的数据是所有的数据。就算你只改了一个数据。

json-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。默认是GET 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))
​
POST方式:
​
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))
​
async和await的写法:
​
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的完整的格式

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

//axios的别名:

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

axios.post(url,data,{配置}).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([axios.get('api/books'),axios.get('api/readers')]).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';

拦截器

请求拦截器

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

​
​
axios.interceptors.request.use(function(config){ //config是请求时的配置信息。
    //一、可以修改baseURL
    // config.baseURL = "http://localhost:3000";
    
    // 二、 增加公共的参数
    //config.params?config.params.ttt = "hello,后端" : config.params = {ttt:"hello,后端"};
​
    // if(config.method=="get"){
    //   config.params?config.params.ttt = "hello,后端" : config.params = {ttt:"hello,后端"};
    // }else if(config.method=="post"){
    //     config.data?config.data.ttt = "hello,后端" : config.data = {ttt:"hello,后端"};
    // }
  
    //三、可以增加token的携带
    
    // token:是验证身份的。    
    // token的逻辑:    
    
    
    // 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 拦截请求和响应 转换请求数据和响应数据 取消请求 abort 自动转换 JSON 数据 客户端支持防御 CSRF

面试题(现在问的少了):

Ajax、jQuery ajax、axios和fetch的区别

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

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

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

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

可以把axios挂载到Vue的原型对象上,这样的话,可以省去每个组件里引用axios。

Vue.prototype.$http = axios;

swiper

$nextTick(); (面试题)

什么时候使用$nextTick()

答:当需要操作 ”数据更新影响的(新的)dom时“,就使用$nextTick()。而且,必须在数据更新后立即调用$nextTick();

换种说法:当需要操作dom,而且这个dom是由于数据更新(修改)后引起重新渲染的dom。

你可以这么认为,$nextTick(cb); 是updated,是某个数据引起的updated,而不是所有数据引起的updated。

深入响应式原理 — Vue.js 异步更新队列

javascript的事件循环(event loop)_田江的博客-CSDN博客 事件循环

特别注意:

$nextTick跟是否发送请求没有关系。只要你需要操作dom是由于数据更新的。那就需要使用$nextTick();

<template>
  <div>  
    <p>{{msg}}</p>
    <input type="button" value="测试" @click="fn">
  </div>
</template>
<script>
​
export default {
  name: "Banner",
  data: function () {
    return {
      msg:"hi"
    };
  },
  methods:{
    fn(){
      this.msg = "hello";
      this.$nextTick(()=>{
           console.log(p标签的innerHTML);
      });
    }
  }
};
</script>

路由

一、路由的作用

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>

解释:

<router-link></router-link>: 超链, 相当于标签a 。

<router-view></router-view>: 组件显示的位置。

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

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

1)、安装

npm i vue-router -S (--save的缩写)

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

如:HelloWorld.vue 、 Home.vue

3)、创建vueRouter对象,并做路由配置和引入(植入到根)

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, //植入根属性,在组件里就可以使用 this.$router
  ………………
})

4)、跳转

4.1)、声明式跳转

//1、路径使用字符串
<router-link to="/home">声明式跳转</router-link>
<router-link to="/home" tag='li' active-class='类名' >声明式跳转</router-link>
​
//2、路径使用对象
<router-link :to="{path:'/home'}">声明式跳转</router-link>

router-link 组件属性:

to:跳转的路径

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

active-class='类名' 指定激活后的样式 模糊匹配

exact-active-class='类名' 指定激活后的样式 严格匹配

4.2)编程式跳转(编程式导航)

1)、this.$router.push(字符串/对象):添加一个路由 (记录到历史记录) 到页面栈。

// 字符串
this.$router.push('/home')
​
// 对象
this.$router.push({ path: '/home' })

$router:表示vueRouter对象,由于我们在vue对象里把vueRouter对象植入到根属性里,所以,在组件内部是可以使用$router拿到该对象的。

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

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

5)、展示

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

三、动态路由匹配

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

$router和$route

$router:表示vueRouter对象,由于我们在vue对象里把vueRouter对象植入到根属性里,所以,在组件内部是可以使用$router拿到该对象的。

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

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

1)、通配符 *

{ 
    path:'*'  会匹配所有路径,即:所有的路径都会跳到当前对应组件
    component:
}
​
{
    path:'/user-*'  会匹配以 `/user-` 开头的任意路径
    component:
}
​

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

当使用一个通配符时,$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>

五、重定向

redirect:

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

路由传参:

一、params

1、传:

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

{ 
    name:"user",
    path: '/user/:id', 
    component: User
}   //id:相当于声明了变量

2)、跳转时传参

1)、跳转时,使用字符串
//声明式
<router-link to="/user/01001">用户01001的信息</router-link>
//编程式
this.$router.push("/user/01001");
​
2)、跳转时,使用对象
//声明式: 命名的路由,同时传参
<router-link :to="{ name: 'user', params: { id: '01001' }}">User</router-link>
//编程式:
this.$router.push({ name: 'user', params: { id: '01001' }})

2、接:

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

二、query

1、传:

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

{ path: '/Reg', component: Reg }

2)、跳转时,使用 path

//1)、跳转时,使用字符串
//声明式: 
<router-link to="/Reg?userid=007&username=mjl">User</router-link>
//编程式:
this.$router.push("/Reg?userid=007&username=mjl");
​
​
//2)、跳转时,使用对象:
//声明式: 
<router-link :to="{ path: '/user', query: { id: '01001' }}">User</router-link>
//编程式:
$router.push({ path: '/user', query: { id: '01001' }})
​
注意:如果提供了 path,那么params 会被忽略
// 带查询参数,变成 /user?id=01001

params和query的对象写法的区别:

name 对应params

path对应query

2、接

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

使用场景的区别:

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/01001/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、组件的展示:

子组件会展示在父组件里的<router-view> 的位置。

八、路由模式

路由模式分为两种: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 包会变得非常大,影响页面(特别是首次)加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。

vue面试题:vue路由按需加载(路由懒加载)_田江的博客-CSDN博客_vue路由按需加载

转场效果:transition

进入/离开 & 列表过渡 — Vue.js

第三方动画库( V3.5.2——CSS3动画库 )的地址:

官网: Animate.css 一款强大的预设css3动画库

CDN: https://cdn.jsdelivr.net/npm/animate.css@3.5.1

状态(数据)管理(VueX)

vuex是什么?

vuex是一个状态管理工具,它能够完成组件之间的数据共享。

官网

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

抛出一个问题

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

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

传统处理方案的问题:

1、兄弟组件传值:

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

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

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

解决方案(vueX)

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的数据流转

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的根属性,在vue组件里就可以使用 this.$store拿到vueX.store对象
    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:{
       // state:就是仓库的state,不用程序员处理,vuex已经处理好了 
        ageChina:function(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;
                case 6:str+='六';break;
                case 7:str+='七';break;
                case 8:str+='八';break;
                case 9:str+='九';break;
            }
            return str+'岁';
        }
    },
    …………………………
});

组件里获取

{{$store.getters.ageChina}}

Mutation:

修改state的数据时,用mutation,这与跟踪状态 有关系。换句话说,在vuex中,对mutation的定义(定位)是:修改状态的,即:在mutation提交的前后,状态应该是不一样的。当然了,也会在vue的dev-tools工具中看到跟踪状态的效果。

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

​
export default new VueX.Store({
    state:{
        age:12,
        isAdult:"未成年",
        isAllowMarry:false
    },
    getters:{
        ageChina:function(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;
                case 6:str+='六';break;
                case 7:str+='七';break;
                case 8:str+='八';break;
                case 9:str+='九';break;
            }
            return str+'岁';
        }
    },
    // mutations:是跟踪状态。这里面只能有同步代码,这是必须的。
    mutations:{
    //    incAge(state,num){
    //        state.age+=num;
        //state:就是store对象里的state,不用程序员传入
        //payload:就是形参,叫作载荷
        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

//组件里提交
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);
        // }
        //context:是store对象
        incAge(context,payload){
            // context.commit('incAge',payload);
            context.commit(payload);
        }
    }
});

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

补充:

vuex推荐:在派发action和 提交mutation时,参数使用对象的方式

//一、组件里派发action的代码 
 this.$store.dispatch({
     type:"incAge", //incAge  是action的名字
     username:"haha", 
     userpass:"123"
 });
​
​
//二、vuex里的代码:
​
//1、action的定义
actions:{
        incAge(context,payload){
            console.log("payload",payload);
            axios({
                url:"/inc",
                params:payload
            })
            .then(res=>{
                context.commit({
                    type:"incAge1", //mutation的名字
                    count:res.data.count
                });
​
            });
        }
    }
//2、mutation的定义:
  mutations:{        
        incAge1(state,payload){
            state.age+= payload.count;
            if(state.age>=18){
                state.isAdult = "已成年"
            }else{
                state.isAdult = "未成年"
            }
        }
    }
​

示例:loading

1、vuex中:

在vuex中定义一个全局的属性:isLoading,来控制loading图片的显示和隐藏

state:{
        isLoading:false,
}
        
 mutations:{
     changeLoading(state,payload){
          state.isLoading = payload.isLoading
     },
}

2、在App.vue

<van-loading class="loading" type="spinner" color="#1989fa" 
      v-show="isLoading" />
​
          
<script>
​
import {mapState} from "vuex";
​
export default {
  name: 'App',
  computed:{
    ...mapState(["isLoading"]),
  }
}
</script>  
​
​
<style scoped>
​
.van-loading__spinner{
  position: fixed;
  left:0;
  top:0;
  right: 0;
  bottom: 0;
  margin:auto;
}
​
</style>
​

3、在请求拦截器和响应拦截器里

import store from "../store";
​
// 请求拦截器:请求的全局工作,发生在到达后端之前
axios.interceptors.request.use(function(config){
​
    // 处理loading (把loading显示)
    store.commit({
        type:"changeLoading",
        isLoading:true
    });
​
    return config;
});
​
​
// 响应拦截器:响应全局工作,发生,后端响应完毕。在axios请求进入到then的回调函数之前
axios.interceptors.response.use(function(res){    
    // 处理loading(把loading隐藏)
    store.commit({
        type:"changeLoading",
        isLoading:false
    });
    return res;
​
});

Modules

当项目比较大时,所有的全局数据存放在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(或者提交mutation)时,如果,直接写action(mutation)的名字,那么就会找到所有同名的action(mutation)。

//如:
//在组件内容,派发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,mapMutations, mapActions

State | Vuex

1、mapState:

当一个组件需要获取多个状态的时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性,让你少按几次键:

mapState辅助函数会把仓库中的状态映射到组件的computed上。

//1、vueX
export default new vueX.Store({
   state:{
       count:10
   }
})
​
//2、组件里
import { mapState } from "vuex";
​
export default {
  name: "Store01",
  data() {
    return {};
  },
  //1)、把vueX中的state用计算属性,很麻烦
//   computed:{
//       count(){
//           return this.$store.state.count
//       }
//  },
//  2)、使用mapState(不能再写其它计算属性)
//   computed: mapState({
//     // 箭头函数可使代码更简练
//     count: state => state.count,
//   }),
//  3)、使用mapState继续简写(不能再写其它计算属性)
//   computed: mapState(['count']),
//  4)、使用mapState,这样写
    computed:{        
        ...mapState(['count']),
        a:function(){
            return 250;
        },
    }
};

补充:mapState映射模块里的数据,

//直接把整个模块映射过来
​
//1、计算属性里的映射
computed:{        
    ...mapState([模块名]),        
},
    
//2、模板上的使用
​
<p>{{模块名.属性}}</p>
​

2、mapGetters

mapGetters辅助函数,把仓库中的getter是映射到组件的computed里。

computed: {
    ...mapState(["count", "age", "isAdult"]),
    ...mapGetters(["ageChina"]),
​
  },

补充:mapGetters映射模块里的数据,

 computed:{        
        ...mapGetters({"属性名":"模块名/属性名"}),        
        ...mapGetters({"money":"a/money"}),
 },

3、mapMutations和mapActions

在methods里映射

 methods: {
    ...mapMutations(["incAge1"]),
    ...mapActions(["incAge"]),
 }

补充:mapMutations映射模块里的方法(mapActions是同样的道理)

methods:{
    ...mapMutations({"方法名":"模块名/mutation名字"}),
    ...mapMutations({"addBook":"a/addBook"}),
}

反向代理

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

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的访问

五、图示

六、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地址,来测试是否在同一个局域网里。

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:

移动端事件

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

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

触发顺序: touchstart,touchend,click。

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

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

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>

为什么会这样?

当用户在box1元素上触摸屏幕时,先触发box1元素的touch事件,然后隐藏了box1,当click(延迟200-300ms)触发时,发现没有box1了,只有box2,所以,就触发了box2元素的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: "878913",
          name: "红楼梦",
          author: "曹雪芹",
          price: 52,
          type: "hot"
        },
        {
          id: "01001",
          name: "西游记",
          author: "曹雪芹",
          price: 52,
          type: "recommend"
        },
        {
          id: "01002",
          name: "霸道总裁",
          author: "王馨",
          price: 50,
          type: "recommend"
        },
​
        {
          id: "01004",
          name: "侠客行",
          author: "金庸",
          img: "img/img7.jpg",
          price: 53,
          type: "武侠"
        },
        {
          id: "01005",
          name: "绝代双骄",
          author: "古龙",
          img: "img/img8.jpg",
          price: 58,
          type: "武侠"
        },
        {
          id: "01006",
          name: "三体",
          author: "王馨",
          img: "img/img9.jpg",
          price: 59,
          type: "科幻"
        },
        {
          id: "01007",
          name: "流浪地球",
          author: "魏瑞峰",
          img: "img/img10.jpg",
          price: 68,
          type: "科幻"
        }
      ],
      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>

在vue脚手架里使用(数据来自后端)

需要使用$nextTick()

<template>
  <div class="box">
    <ul>
      <div v-show="downShow">加载中…………………………</div>
      <li v-for="book in books" :key="book.id">
        <p>书号:{{ book.id }}</p>
        <p>书名:{{ book.name }}</p>
        <img :src="book.img" />
        <p>作者:{{ book.author }}</p>
        <p>价格:{{ book.price }}</p>
      </li>
      <div v-show="upShow">加载中…………………………</div>
    </ul>
  </div>
</template>
​
<script>
import axios from "axios";
import BScroll from "better-scroll";
​
export default {
  name: "BookJia",
​
  data() {
    return {
      downShow: false, //下拉时显示
      upShow: false,//上拉时显示
      books: [],
      pageIndex:0,
      scroll:null
    };
  },
  created() {
    this.pageIndex++;
    axios({
      url: "/booklist?_page="+this.pageIndex+"&_limit=4",
    }).then((res) => {
      this.books = res.data;
​
      this.$nextTick(() => {
  
        this.scroll = new BScroll(".box", {
          scrollY: true,
           pullDownRefresh: { //下拉刷新的配置
              threshold: 30 // 当下拉到超过顶部 30px 时,触发 pullingDown 事件
            },
            pullUpLoad: { //上拉加载的配置
              threshold: -50 // 在上拉到超过底部 50px 时,触发 pullingUp 事件
            }
        });
        
        this.scroll.on("pullingDown",()=>{
          this.downShow = true;
        })
        
        this.scroll.on("pullingUp",()=>{
          this.upShow = true;
          
          this.pageIndex++;
          axios({
             url: "/booklist?_page="+this.pageIndex+"&_limit=4",
          }).then((res) => {
            this.books = this.books.concat(res.data);
            // this.books = [...this.books,...res.data];
            this.upShow = false;
            this.$nextTick(()=>this.refresh());
          })
​
        })
​
      });
    });
  },
  methods:{
      refresh() {
         this.scroll.finishPullDown();
         this.scroll.finishPullUp();
         this.scroll.refresh(); //重新计算元素高度
    } 
  }
​
};
</script>
​
<style scoped>
.box {
  width: 100%;
  height: 612px;
  overflow: auto;
  border:2px solid red;
}
​
img {
  width: 100%;
  height: 200px;
}
</style>
​

在vue脚手架里使用(封装了)

<template>
  <div class="box">
    <ul>
      <div v-show="downShow">加载中…………………………</div>
      <li v-for="book in books" :key="book.id">
        <p>书号:{{ book.id }}</p>
        <p>书名:{{ book.name }}</p>
        <img :src="book.img" />
        <p>作者:{{ book.author }}</p>
        <p>价格:{{ book.price }}</p>
      </li>
      <div v-show="upShow">{{upMsg}}</div>
    </ul>
  </div>
</template>
​
<script>
import axios from "axios";
import BScroll from "better-scroll";
​
export default {
  name: "BookJia",
​
  data() {
    return {
      downShow: false, //下拉时显示
      upShow: false, //上拉时显示
      books: [],
      pageIndex: 0,
      maxPageIndex:4, //最大页码数
      scroll: null,
      upMsg:"加载中…………………………"
    };
  },
  created() {
    //1、获取最大的页码数。赋给 maxPageIndex
    this.maxPageIndex = 4;
​
    //2、获取数据,并且初始化better-scroll
    this.getBooks(this.initBScroll);
  },
  methods: {
    // 获取数据
    getBooks(cb) {
      console.log("发送请求");
      this.pageIndex++;
      axios({
        url: "/booklist?_page=" + this.pageIndex + "&_limit=4",
      }).then((res) => {
        cb(res.data);
      });
    },
    //初始化better-scroll;
    initBScroll(data) {
      this.books = data;
      this.$nextTick(() => {
        //1、 创建better-sroll;
        this.createBScroll();
        //2、 给better-scroll添加事件
        this.addEvent();
      });
    },
​
    // 创建better-sroll;
    createBScroll(){
      this.scroll = new BScroll(".box", {
          scrollY: true,
          pullDownRefresh: {
            //下拉刷新的配置
            threshold: 30, // 当下拉到超过顶部 30px 时,触发 pullingDown 事件
          },
          pullUpLoad: {
            //上拉加载的配置
            threshold: -50, // 在上拉到超过底部 50px 时,触发 pullingUp 事件
          },
        });
​
    },
​
    // 给better-scroll添加上拉和下拉的事件
    addEvent() {
      this.scroll.on("pullingDown", () => {
        this.downShow = true;
      });
​
      this.scroll.on("pullingUp", () => {       
        console.log(this.pageIndex); 
        this.upShow = true;
        if(this.pageIndex>=this.maxPageIndex){
          this.upMsg = "------我是有底线的----------";
          return;
        }
        // 再次获取数据,更新better-scroll。
        this.getBooks(this.updateBScroll);
      });
    },
​
    //更新better-scroll;
    updateBScroll(data){
      this.books = this.books.concat(data);
      this.upShow = false;
      this.$nextTick(() => this.refresh());
    },
    
    refresh() {
      this.scroll.finishPullDown();
      this.scroll.finishPullUp();
      this.scroll.refresh(); //重新计算元素高度
    },
  },
};
</script>
​
<style scoped>
.box {
  width: 100%;
  height: 612px;
  overflow: auto;
  border: 2px solid red;
}
​
img {
  width: 100%;
  height: 200px;
}
</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/';

1)、如果上线时,项目还是前后端分离(如:在两个服务器上)

Axios.defaults.baseURL = '/api/'; //不用改。互联网服务器上的反向代理可以使用nginx。

2)、如果上线时,项目不是前后端分离。就不存在跨域问题。

Axios.defaults.baseURL = ''; //去掉baseURL的配置。

把前端和后端的打包结果放在一起,就ok了(把前端代码发给后端,后端一起打包)

2、打包命令

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

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

???????

3、远程服务器上线(nginx)

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

nginx的反向代理配置:

location ~ ^/api/ {
    proxy_set_header        Host $host;
    proxy_set_header        X-Real-IP $remote_addr;
    proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header        REMOTE-HOST $remote_addr;

    rewrite ^/api/(.*)$   /$1 break;
    proxy_pass             http://localhost:3000;//后端服务器的地址
}

2)、如果上线时,项目不是前后端分离。就不存在跨域问题。

vue脚手架项目从开发到打包完整的流程(从零开始做一个项目)

一、git版本管理

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

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

二、脚手架搭建和开发

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

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

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

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

git add .

git commit -m ""

git push 远端地址 master

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里。

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


查漏补缺:

1、vue-cli3/4的配置

在项目根目录创建文件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-dev

vue.config.js配置

const path = 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的封装

//在公司里,都会对axios进行封装。如果说,我们不学这个,不执行,那么到公司中就看不懂别人的代码

1)、创建目录和文件:src/utils/service.js,封装axios的请求拦截器和全局的配置

import axios from "axios"

// 创建axios 赋值给常量service 
const service = axios.create({
    baseURL: process.env.VUE_APP_BASE_API,
    timeout: 100000
    headers: {
        "Content-Type": "application/json;charset=UTF-8",
        // "Content-Type": "application/x-www-form-urlencoded;charset=utf-8",        
    }
});

// 添加请求拦截器(Interceptors)
service.interceptors.request.use(function (config) {
  // 发送请求之前做写什么
  let token =  localStorage.getItem("token");
  // 如果有
  if(token){
    // 放在请求头(token跟后端沟通,他需要什么该成什么就可以了)
      config.headers.authorization = token;
  }

  return config;
}, function (error) {
  // 请求错误的时候做些什么
  return Promise.reject(error);
});

// 添加响应拦截器
service.interceptors.response.use(function (response) {
  // 对响应数据做点什么
  return response;
}, function (error) {
  // 对响应错误做点什么
  return Promise.reject(error);
});

export default service

2)、创建api文件夹,

2.1)、index.js封装get和post请求

import service from "../utils/service";

export function POST(url, params) {
    
    return new Promise((resolve, reject) => {
        service.post(url, params)
            .then(response => {
                if (!response.data) {
                    resolve(response);
                }
                resolve(response.data);
            }, err => {
                reject(err);
            })
            .catch((err) => {
                reject(err)
            })
    })
}
// get方法
export function GET(url, params) {
    return new Promise((resolve, reject) => {
        service.get(url, {
                params
            })
            .then(response => {
                resolve(response.data);
            }, err => {
                reject(err);
            })
            .catch((err) => {
                reject(err)
            })
    })
}

2.2)、根据实际情况进行分模块:

如:home.js存放首页的所有请求

//home.js

import * as axiosBase from '@/api/index.js';

export let getBooks = function (params) {
    params = params || {}
    return axiosBase.GET('/api/books', params)
}

export let getBannerImgs = function (params) {
    params = params || {}
    return axiosBase.GET('/api/imgs', params)
}

如:users.js 存放登录注册相关的请求

//user.js

import * as axiosBase from '@/api/index.js';

export let checkUser = function (params) {
    params = params || {}
    return axiosBase.GET('/api/checkUser', params)
}

export let reg = function (params) {
    params = params || {}
    return axiosBase.POST('/api/users', params)
}


export let loginCheck = function (params) {
    params = params || {}
    return axiosBase.POST('/api/loginCheck', params)
}

还有这么干的:

import { get, post } from "./utils/index";

Vue.prototype.$http = {
  get,
  post
};

发送请求时,直接使用:this.$http.get();  this.$http.post();

3、脚手架里使用sass

1、安装

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

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

<style lang="scss" scoped> </style>

3、可能出现的问题

说明版本太高,安装指定版本即可: npm install sass-loader@6.0.6 --save-dev

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

软件介绍: ArrayNetworksL3Setu是移动代理点客户端软件,也就是常说的那个红A。需要安装这个软件后才能登陆。BOSS 客户端更新说明为了解决现有BOSS系统BUG,现在需要升级各代办点终端的SSL 的插件,具体插件步骤如下:1.将附件中名称为:“ArrayNetworksL3OnlyWebSetup.zip”的安装包拷贝到代办终端上。 2.在代办终端上解压该文件3.点击“setup.exe”4.一步步安装首先remove现有的插件。点击“next”,点击“finish”,再点击“setup.exe”,点击“finish”完成安装。完成后开始使用,打开IE浏览器。输入移动 IP地址。IE版本可能会出现,点击“允许”,当右下角出现“A” 上面出现8.4.6.80确认为新的插件版本。出现红A,没有任何报错就表示安装正常。-----------------------------------------------------------------------------------------------------如果安装有问题或者不能正常访问,请单独安装客户端。安装的文件名称ArrayNetworksL3SetupX32.zip,ArrayNetworksL3SetupX64.zip请对应系统的版本安装1查看自己的系统的版本,32位,64位2.“计算机”-->“属性”查看自己的是32位的还是64位的操作系统。请对应版本安装。4.安装客户端软件的步骤,首先解压文件。点击 “setup.exe”安装完成。打开IE登陆SSL 如重启失败请重置浏览器的高级设置。点击---“还远高级设置”---“确定”再次登陆

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值