vue_t_v14

VUE

vue框架的两大核心:

数据驱动组件化

一、前端开发历史

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)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oGNUZoYm-1647875628960)(.\img\01mvc.png)]

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

MVP架构模式

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-APL4OT2p-1647875628962)(.\img\02mvp.png)]

MVP模式与MVC模式的区别:

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

MVVM架构模式

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cJGABu2e-1647875628964)(.\img\03mvvm.png)]

vue是MVVM

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

总结:

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

三、开发工具

vscode,webstorm,HbuilderX等等

四、vue框架的初识

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

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

vue的介绍

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

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

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

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

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

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

  • Vue 不支持 IE8 及以下版本

vue的示例代码

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

vue框架的理解

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xuVJMagr-1647875628965)(\img\04vuejs.png)]

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

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

数据驱动和声明式渲染

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

2、声明式渲染

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

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

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

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

目的是:感知属性的变化。当给属性赋值时,程序是能够感知的(知道的)。如果知道的话,就可以控制属性值的有效范围,也可以改变其它属性的值等,在Vue中也可以去改变模板上的显示。

Object.defineProperty()函数:https://blog.csdn.net/jiang7701037/article/details/102785223

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

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

五、vue语法(view层)

data选项(model层)

功能:

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

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

数据绑定

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

https://cn.vuejs.org/v2/guide/syntax.html#%E6%8F%92%E5%80%BC

插值表达式

功能:可以让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

​ 示例:

内容:

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

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

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

2、v-html="数据" 非转义输出(原封不动进行替换)

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

​ 示例:

 输出真正的 HTML
 

内容:

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

属性指令

​ 属性指令:让标签的属性部分变成动态的。

​ 指令名:v-bind

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

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

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

示例:

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

   

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

javascript表达式

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

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

示例:

{{ number + 1 }}     
{{ ok ? 'YES' : 'NO' }}     
{{ 'i love you'.split('').reverse().join('') }}

注意:

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

条件渲染(指令)

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

指令名: v-if 和 v-show

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

<div v-if="false">box2div>

<div v-show="true">box1div>

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的操作(如:创建和删除)是非常耗性能的。为什么?
请看:https://blog.csdn.net/jiang7701037/article/details/98516468

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

示例:

宝宝

大宝宝

非宝宝

列表渲染(循环指令)

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

指令名: v-for

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

​ 格式:

用in或者用of都可以:

  • {{值}}
  • {{值}}
  • 各种情况:

    <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连用?

    https://blog.csdn.net/jiang7701037/article/details/114954542

    列表渲染时的key:

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

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

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

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

    注意:

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


    事件绑定(指令)

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

    指令名:v-on

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

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

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

    格式:

    <input type="button" v-on:事件名="方法"  >
    <input type="button" v-on:事件名="方法(参数)" >
    <input type="button" v-on:事件名="方法($event,参数)">
    
    new Vue({
        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)

    如:

    
    ………………
    fn03(str,ev){
        console.log('事件对象',ev);
        console.log('参数',str);
    },
    

    事件处理函数的this:

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

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

    事件修饰符

    .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> 
    

    按键修饰符

    
    
    .left 左 .enter 回车 .13 可以使用键码
    .ctrl .alt .shift .exact 精准控制,@键盘事件.修饰符1.修饰符2.exact 只有1+2才可触发 1+2+3不可以
    .left .right .middle 鼠标中键

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

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

    指令名:v-model

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

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

    其它表单元素的双向绑定

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

    表单修饰符

    
    			  .number 	把标签的值转成数字赋给vue的数据
    			  .trim 	删除前后空格
    			  .lazy   	确认时才修改model数据
    

    示例:

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

    指令总结

    ​ 指令 (Directives) 是带有 v- 前缀的特殊属性。其实就是html标签的里的自定义属性。指令属性的值预期是单个 JavaScript 表达式 (v-for 是例外情况)。指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于 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 缩写是 @


    样式操作

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

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

    绑定姿势(格式)

    属性值的类型支持

    字符串/对象 / 数组

    
    

    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的属性值(对象的方式)示例:

    我是个p

    我是p

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

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

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

    示例:

    todoList 练习的实现

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6myV0IKo-1647875628967)(img\1594365849928.png)]

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

    非响应式情况

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

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

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

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

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

    • 修改数组的长度时

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

      ​ 所以,强烈建议:不要用下标的方式修改数组的元素,不要修改数组的长度,可以把未来需要的数据都声明在data选项内部,不要对数组使用非变异的api(数组的变异方法:https://cn.vuejs.org/v2/guide/list.html#%E5%8F%98%E6%9B%B4%E6%96%B9%E6%B3%95)

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

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

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

    this.$forceUpdate() 强制刷新



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

    data属性:

    不再说了

    methods:

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

    计算属性

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

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

    语法

    //定义
    computed:{
      计算属性: function(){return 返回值}		
    }
    
    //使用
    使用:	{{计算属性}} |  v-指令="计算属性"
    

    面试题:

    computed VS methods

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

    属性检测(侦听属性)

    https://cn.vuejs.org/v2/guide/computed.html#%E8%AE%A1%E7%AE%97%E5%B1%9E%E6%80%A7-vs-%E4%BE%A6%E5%90%AC%E5%B1%9E%E6%80%A7

    ​ 需要在数据变化时执行异步或开销较大的操作时,这个时候需要属性检测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
                }
            }
        });
    

    面试题:

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

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

    自定义指令

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

    系统(官方)指令在不够用的情况下,考虑自定义,指令是个函数|对象,用来操作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)。
      https://cn.vuejs.org/v2/guide/custom-directive.html#%E9%92%A9%E5%AD%90%E5%87%BD%E6%95%B0%E5%8F%82%E6%95%B0

    ​ 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()
      }
    })
    使用指令
    

    模拟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 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新
    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>
    

    过滤器

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

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

    全局定义
    Vue.filter('过滤器名称',函数(要过滤的元数据,参数1,参数n){
               		过滤器的功能
               		return 过滤的结果           
               })
    
    使用
    
    {{数据名 | 过滤器名(参数1,参数2)}}
    
    v-bind:属性="数据| 过滤器名(参数1,参数2)"
    
    局部定义
    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>
    

    混入

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

    用法

    1、定义格式

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

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

    mixins: [mixin1,mixin2] //当混入的键与引入键冲突时以组件内的键为主
    
    new Vue({ 
          el:"#app"data:{
          	msg:"hello"
      	  }
          mixins: [mixin1,mixin2]
    })
    

    3、全局混入:

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

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

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

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

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

    域名购买

    链接


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

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

    为什么使用虚拟DOM:
    使用js操作DOM时(增删改查等等),那么DOM元素的变化自然会引起页面的回流(重排)或者重绘,页面的DOM回流(重排)或者重绘自然会导致页面性能下降,那么如何尽可能的去减少DOM的操作是框架需要考虑的一个重要问题!
    https://blog.csdn.net/jiang7701037/article/details/98516468。

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

    真实DOM和虚拟DOM的区别:

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

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

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

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

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

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

    总结:

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

    2、oldVDom和真实DOM保持一致

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

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

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

    总结:

    虚拟DOM: 用JSON对象模拟的真实dom

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

    vue-dev-tools安装

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

    方案2: https://blog.csdn.net/jiang7701037/article/details/99708017

    七、组件

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

    组件的概念:

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

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

    ​ data:必须是个函数
    ​ methods:

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

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

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

    1、定义组件:

    ​ 第一种:

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

    第二种(简化写法):

    let 组件变量名={
        template:'
    {{msg}},我是header组件
    '
    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:`
            

    `
    , data:function(){ return{ str:"", msg:"" } }, methods:{ send(){ this.msg += this.str+"
    "
    this.str =""; } } }; // 2、全局注册 Vue.component("chat",chatObj); let vm = new Vue({ el: "#box", data: { }, //局部注册: components:{ "chat":chatObj } }); </script>
    4、组件嵌套:

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

    如:

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

    我是p:{{msg}}

    `
    , 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共享同一块内存空间。

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

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

    使用 Prop 传递静态数据

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

    
    let myComParent = {
            props:["name","sex"], //声明了两个自定义属性
            template:`

    我是p:{{msg}}

    人的信息:

    姓名:{{name}}

    性别:{{sex}}

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

    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: "",
    	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:`

    我是上p

    我时下p


    `
    }; //父组件: <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:`

    我是上p

    我是中p

    我是下p


    `
    }; //父组件 <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:`

    我是上p:{{t}}

    我是下p


    `
    , data(){ return{ t:"我是子组件的数据" } } }; //父组件: <div id="app"> <input :value="msg" /> <person v-show="s"> <p>{{msg}}</p> <div>我是div1:{{msg}}</div> </person> </div> new Vue({ el:"#app", data:{ msg:"hi", s:true }, components:{ person } });

    自行研究:作用域插槽

    作用域插槽

    组件(间)的通信(面试中经常问的)

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

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

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t2pALMIK-1647875628969)(\1599837423830.png)]

    2)、父给子组件:refs

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

    refs

    ​ 首先,需要知道:用标签的方式使用组件(如:),实际就是创建了组件对象。只要拿到组件对象,那么组件对象里的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:`
            
    `
    , 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:`
            

    组件com1
    `
    , created(){ // 注册了一个事件 bus.$on("eclick",(target)=>{ console.log(target); }); } } let myCom2 = { template:`
    `
    , methods:{ fn(){ //触发事件 bus.$emit("eclick","hello"); } } } Vue.component("my-com1",myCom1); Vue.component("my-com2",myCom2); new Vue({ el:"#app" });
    集中管理($root)

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

    定义

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

    使用

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

    动态组件

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

    ​ vue中使用的方式实现。

    示例:

    <div id="app">
            <span @click="show(0)">娱乐</span>|<span  @click="show(1)">八卦</span>|<span @click="show(2)">体育</span>
            <div>
                <component :is="currCom"></component>
            </div>
    </div>
    
    let yuLe = {
        template:"
    娱乐新闻
    "
    } let eightGua = { template:"
    八卦新闻
    "
    } let sports = { template:"
    体育新闻
    "
    } new Vue({ el:"#app", data:{ currCom:"yuLe", coms:["yuLe","eightGua","sports"] }, methods:{ show(index){ this.currCom = this.coms[index]; } }, components:{ yuLe,eightGua,sports } });

    根据班级情况尝试扩展如下:
    增删改查(或者留言板,或者员工信息)

    八、vue对象

    类和实例API

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

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

    Vue类的属性和方法

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

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

    ​ let vm = new Vue() 返回的是实例,所以vm. 属 性 名 是 实 例 属 性 , v m . 属性名 是实例属性,vm. vm.方法名()是实例方法,同时vue类内部的this指向的是实例vm。 实例 vm.$属性名对等 vue选项的 根key。

    ​ 如:vm. d a t a , v m . data, vm. datavm.el ……………………。

    生命周期(面试题)

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

    链接

    https://blog.csdn.net/jiang7701037/article/details/83118665

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

    一、vue生命周期是什么

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

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

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

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

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

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

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

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

    4)、组件销毁阶段:

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

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

    activated 和 deactivated

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

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

    ​ 清除定时器。因为,定时器是属于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:"
    娱乐新闻
    "
    , 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:"
    八卦新闻
    "
    , 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。一个文件里就只有一个组件。这就是单文件组件。
    基本结构:

    
    
    
    
    
    

    示例:

    
    
    
    
    

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

    cli: command line interface

    • 通过官方脚手架,(命令行的方式)搭建模块化,工程化,自动化开发环境
    //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提示

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

    module.exports = {

    ​ lintOnSave:false,

    }

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

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

    https://blog.csdn.net/jiang7701037/article/details/101215999

    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;
    
    export default {  };
    

    输入

    格式:
    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
    
    
    
    
    
    
    
    
    模块化的好处:使用实例属性$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、不要在单文件组件里使用

    单页面应用(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
      
    • GET 请求指定字段值的数据

      localhost:3000/users?name=李四&age=15
      
    • GET 数据分页

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

      _page表示页码

      _limit表示每页的条数

    • GET 数据排序

      localhost:3000/bookS?_sort=price&_order=asc
      - asc 升序 desc 降序
      
    • GET 区间查询

      搜索 age属性的值大于等于30 而 小于等于40的记录

      localhost:3000/users?age_gte=30&age_lte=40
      

      搜索 price 属性的值大于等于51.2而 小于等于51.8的记录

    http://localhost:3000/bookS?price_gte=51.2&price_lte=51.8
    
    • 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/users
      • Headers:{ Content-Type:‘application/json’ }
      • body -> raw
       {
           "name": "赵六",
           "age": 50,
           "companyId": 3
       }
      
    • delete 删除数据

      请求方式为:DELETE

      localhost:3000/users/1
      
    • patch 更新数据

      请求方式为:PATCH

      • localhost:3000/users/3
      • Headers:{ Content-Type:‘application/json’ }
      • body -> raw
       {
           "age": 100
       }
      

    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-resource、axios

    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))
    
    asyncawait的写法:
    
    try {
         let response = await fetch(url);
         let data = response.json();
         console.log(data);
    } catch(e) {
         console.log("Oops, error", e);
    }
    

    特点:

    • 语法简洁,更加语义化

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

    • 同构方便,使用 isomorphic-fetch

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

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

    axios

    概念:

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

    格式:

    Promise axios(配置)

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

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

    //axios的完整的格式
    
    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';
    

    拦截器

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

    请求拦截器

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

    axios.interceptors.request.use(function(config){ //config是请求时的配置信息。
    	//一、可以修改baseURL
        // config.baseURL = "http://localhost:3000";
      
    	//二、可以增加token的携带
    	
        // token:是验证身份的。    
        // token的逻辑:    
        
        //1、token什么时候存储的?当登录成功时,后端会产生一个token,发给前端,前端拿到token,
            // 保存在cookie或者localStorage。
    
        //2、在请求拦截器里,就需要携带token给服务器端。
        
        // 1)、(假定存储在localStorage中)从localStorage中获取token
        let token = localStorage.getItem("token");
        if(token){
            config.headers.authorization = token;
        }
        
        //三、显示loading窗口
        // Loading = true;
        
        return config;
    },function(){
        出错
    });			
    
    响应拦截器

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

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

    特点:

    从浏览器中创建 XMLHttpRequest/从 node.js 创建 http 请求
    支持 Promise API
    拦截请求和响应
    转换请求数据和响应数据
    取消请求 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端使用。

    swiper

    $nextTick();

    什么时候使用$nextTick()

    答:当需要操作 ”数据更新影响的(新的)dom时“,就使用$nextTick()。

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

    https://cn.vuejs.org/v2/guide/reactivity.html 异步更新队列

    https://blog.csdn.net/jiang7701037/article/details/95887439 事件循环

    特别注意:

    n e x t T i c k 跟 是 否 发 送 请 求 没 有 关 系 。 只 要 你 需 要 操 作 d o m 是 由 于 数 据 更 新 的 。 那 就 需 要 使 用 nextTick跟是否发送请求没有关系。只要你需要操作dom是由于数据更新的。那就需要使用 nextTickdom使nextTick();

    
    
    

    路由

    一、路由的作用

    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:"
    商品列表
    "
    } let goodsdetail = { template:"
    商品详情
    "
    }

    3)、定义路由对象

    3.1)路由配置(json数组)

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

    3.2)、实例化一个vueRouter对象

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

    4)、挂载vueRouter对象

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

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

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

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

    解释:

    : 超链, 相当于标签a 。

    : 组件显示的位置。

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

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

    1)、安装

    npm i vue-router -S--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、路径使用字符串
    声明式跳转
    声明式跳转
    
    //2、路径使用对象
    声明式跳转
    

    router-link 组件属性:

    ​ to:跳转的路径

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

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

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

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

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

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

    ** r o u t e r : ∗ ∗ 表 示 v u e R o u t e r 对 象 , 由 于 我 们 在 v u e 对 象 里 把 v u e R o u t e r 对 象 植 入 到 根 属 性 里 , 所 以 , 在 组 件 内 部 是 可 以 使 用 router:**表示vueRouter对象,由于我们在vue对象里把vueRouter对象植入到根属性里,所以,在组件内部是可以使用 routervueRoutervuevueRouter使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
    
    r o u t e r 和 router和 routerroute

    ** r o u t e r : ∗ ∗ 表 示 v u e R o u t e r 对 象 , 由 于 我 们 在 v u e 对 象 里 把 v u e R o u t e r 对 象 植 入 到 根 属 性 里 , 所 以 , 在 组 件 内 部 是 可 以 使 用 router:**表示vueRouter对象,由于我们在vue对象里把vueRouter对象植入到根属性里,所以,在组件内部是可以使用 routervueRoutervuevueRouter使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、跳转

    User
    

    五、重定向

    redirect:

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

    路由传参:

    一、params

    1、传:

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

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

    2)、跳转时传参

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

    2、接:

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

    二、query

    1、传:

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

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

    2)、跳转时,使用 path

    //1)、跳转时,使用字符串
    //声明式: 
    <router-link to="/Reg?userid=007&username=mjl">User</router-link>
    //编程式:
    this.$router.push("/Reg?userid=007&username=mjl");
    
    
    //跳转时,使用对象:
    //声明式: 
    <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: '
    User {{ id }}
    '
    }

    七、嵌套路由

    子路由嵌套

    1、路由配置

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

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

    2、组件的展示:

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

    八、路由模式

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

    区别(面试题):

    1)、外观上

    hash模式时,路径上有#。

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

    2)、跟后端有关的区别

    hash模式不会给后端发请求

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

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

    九、扩展

    路由守卫

    全局守卫

    // src/router/index.js
    
    //前置钩子
    router.beforeEach((to, from, next) => {
      
      //	to: 目标路由
      //	from: 当前路由
      
      // next() 跳转  一定要调用
      next(false);//不让走
      next(true);//继续前行
      next('/login')//走哪
      next({path:'/detail/2',params:{},query:{}})//带点货
      
      // 守卫业务
      if(to.path=='/login' || to.path=='/reg' ){    
        next(true)
      }else{
          //是否登录的判断
          if(没有登录过){
              next('/login');
           }else{
              next(true);
           }
      }
    })
    
    //后置
    router.afterEach((to,from)=>{
      //全局后置守卫业务
    })
    
    //过程:
    1、请求一个路径:如:/Index
    2、经历前置守卫(路由配置前)
       决定了能去哪个路径
    3、根据去的路径,找对应component(路由配置)
    4、经过后置守卫(路由配置后)
    5、创建组件
    

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

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

    独享,没有后置

    组件内部守卫

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

    注意:

    路由独享守卫,守的是path

    组件内部守卫,守的是component

    路由元信息

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

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

    访问 meta 字段

    this.$route.meta
    to.meta
    

    路由懒加载

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

    https://blog.csdn.net/jiang7701037/article/details/106794996

    转场效果:transition

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

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

    ​ 官网: https://www.jq22.com/yanshi819

    ​ CDN: https://cdn.jsdelivr.net/npm/[email protected]

    状态(数据)管理(VueX)

    官网

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

    抛出一个问题

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

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

    传统处理方案的问题:

    1、兄弟组件传值:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6wu76JQF-1647875628975)(img\08vuex01.png)]

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

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

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iouI0b4x-1647875628977)(img\09vuex02.png)]

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

    解决方案(vueX)

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SwbeiI44-1647875628978)(img\10vuex03.png)]

    vueX的作用

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

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

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

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

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

    modules:模块化

    vueX的数据流转

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wwARrQl3-1647875628979)(img\image-20211102111905111.png)]

    vueX的基本使用步骤

    1)、安装

    npm安装vueX:  npm install vuex –save
    

    2)、创建 vueX.store 对象

    ./src/store/index.js
    
    import Vue  from 'vue'
    //引入vueX
    import Vuex from 'vuex'
    //把vueX安装到Vue里
    Vue.use(Vuex)
    
    export default new Vuex.Store({
        state:{
              id: '01'
        },
        getters:{},
        mutations:{},
        actions:{}
    })
    

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

    ./src/main.js
    
    import store from './store'
    
    new Vue({
      	el: '#app',
      	store,//把store对象植入到vue的根属性,在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

    https://vuex.vuejs.org/zh/guide/state.html

    1、mapState:

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

    //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;
            },
        }
    };
    

    2、mapGetters

    ​ 在computed里映射

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

    3、mapMutations和mapActions

    ​ 在methods里映射

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

    反向代理

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

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

    2、生产(发布)环境:

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

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

    跨域的解决方案:

    1)、jsonp

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

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

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

    反向代理

    一、为啥要叫反向代理

    正向代理隐藏真实客户端,反向代理隐藏真实服务端,让浏览器依然是同源。

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

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

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

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

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

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

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

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

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

    五、图示

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

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

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

    ​ 写上如下代码:

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

    注意:

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

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

    postman

    跟后端联调项目

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

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

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

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

    二、前后端的计算机连在同一个局域网(能够连上)

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

    ping 后端的ip地址

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

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

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

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

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

    四、跨域

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

    Vue-cli2.X:

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

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

    proxyTable: {

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

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

    ​ changeOrigin: true,

    ​ pathRewrite: {

    ​ ‘^/api’: ‘’ // 去掉"/api"

    ​ }

    ​ }

    }

    Vue-cli3/4

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

    ​ 写上如下代码:

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

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

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

    四、发送请求,获取数据

    ​ 测试功能

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

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

    移动端事件

    1、click事件

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

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

    2、touch类事件

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

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

    示例:

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

    3、touch的优先级高于click

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

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

    ​ 触发顺序: touchstart,touchend

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

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

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

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

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

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

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

    4、 阻止click事件的触发

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

    ​ 由于:当用户在点击屏幕的时候,系统会触发touch事件和click事件,touch事件优先级高,touch事件触发完毕后, 才会去触发click事件.

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

    示例:

    	document.getElementById("box").ontouchstart  = function(event){
    		this.innerHTML +="ontouchstart
    "
    ;//手指触摸到屏幕 event.preventDefault(); } document.getElementById("box").onclick = function(){ this.innerHTML +="onclick:点击了
    "
    ; }

    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 — 当元素被划过时触发。(可选择给定的方向)

    示例:

    	
    $("#box").on("tap", function () { $('#box').append('
  • tap:单击屏幕
  • '); }); $("#box").on("singleTap", function () { $('#box').append('
  • singleTap:单击屏幕
  • '); }); $("#box").on("doubleTap", function () { $('#box').append('
  • 双击屏幕
  • '); }); $("#box").on("longTap", function () { $('#box').append('
  • 长安屏幕750ms
  • '); }); $("#box").on("swipe", function () { $('#box').append('
  • 划过屏幕
  • '); }); $("#box").on("swipeLeft", function () { $('#box').append('
  • 划过屏幕(左)
  • '); }); $("#box").on("swipeRight", function () { $('#box').append('
  • 划过屏幕(右)
  • '); }); $("#box").on("swipeUp", function () { $('#box').append('
  • 划过屏幕(上)
  • '); }); $("#box").on("swipeDown", function () { $('#box').append('
  • 划过屏幕(下)
  • '); });

    2)、touch.js

    介绍:

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

    绑定事件

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

    解除事件绑定

    touch.off( element, types, callback )

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

    缩放:

    事件名 描述
    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 )

    参数 类型 描述
    delegateElement element或string 事件代理元素或选择器
    types string 手势事件的类型,可接受多个事件以空格分开;
    selector string 代理子元素选择器
    callback function 事件处理函数

    示例:

       <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 相关位置信息,不同的操作产生不同的位置信息
    distance swipe类两点之间的位移
    distanceX, x 手势事件x方向的位移值,向左移动时为负数
    distanceY, y 手势事件y方向的位移值,向上移动时为负数
    angle rotate事件触发时旋转的角度
    duration touchstart与touchend之间的时间戳
    factor swipe事件加速度因子
    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"
          }
        ]
      ]
    //全局使用:   所有应用内部组件直接使用 
    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);
    
    //全局使用: 
    

    按需引入

    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}
      //
    
    //全局使用
      import { Button } from 'vant';
      Vue.use(Button); 
      //
    

    组件样式顺序

    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 [email protected] --save-dev //安装sass-loader
    npm install style-loader --save-dev //安装style-loader 或者 vue-style-loader !

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

    3、可能出现的问题

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pqBMty4Z-1647875628997)(C:\Users\31759\AppData\Roaming\Typora\typora-user-images\1600185128727.png)]
    说明版本太高,安装指定版本即可:
    npm install [email protected] --save-dev

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

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

    • 参数:{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 [email protected] --save-dev //安装sass-loader
    npm install style-loader --save-dev //安装style-loader 或者 vue-style-loader !

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

    3、可能出现的问题

    [外链图片转存中…(img-pqBMty4Z-1647875628997)]
    说明版本太高,安装指定版本即可:
    npm install [email protected] --save-dev

    [外链图片转存中…(img-8Zwp0qcZ-1647875628998)]

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

    你可能感兴趣的:(vue.js)