vue2笔记

Vue笔记
视频: https://www.bilibili.com/video/BV1Zy4y1K7SH?p=1
vue是渐进式JavaScript框架 用到什么功能,只需要引入什么功能模块 ;
vue的特点:易用,灵活,高效;

  1. 组件化 , 一个vue文件包括了(html +css + js)
  2. 声明式编程(不直接操作DOM) ;
  3. 虚拟DOM + diff算法(虚拟dom的对比)

Vue2

vue引入:
引入开发版本的vue, 下载vue Devtools (vue开发者工具) ,

 // 阻止 vue 在启动时生成生产提示。
Vue.config.productionTip = false

创建vue实例:

创建vue实例, 创建一个配置对象; 通过new 关键字调用;

配置项

  • el => element : 即 vue实例管理的元素, 值通常为 id 选择器 的字符串;

  • data 对象或函数且返回一个对象, 用于存储数据, 供el管理的元素使用;

  • vue实例和被管理的容器是一一对应的;且真实开发中只有一个vue实例;

  • data中的数据改变,页面中用到该数据的地方自动更新; (响应式)

const vm = new Vue({
  el:"#app",
  data:{
    msg:"世界"
  }
})

vue模板语法

{{ }} 插值语法/mustache语法; {{ }}插值语法的内容可以自动读取data中的数据,只能放js表达式;
指令语法,v-开头, 也可以直接使用data中的数据;
vue2笔记_第1张图片


数据绑定:

单向数据绑定(属性绑定) v-bind: 简写为 :

单向数据绑定: <input type="text" :value="msg">

双向数据绑定 v-model , 通常绑定表单元素, v-model: value, 直接简写为 v-model

双向数据绑定:<input type="text" v-model="msg">

双向数据绑定的原理: v-bind 绑定value属性, v-on监听input事件 ;
vue2笔记_第2张图片

el与data的两种写法:

  el: '#app',
  // 通过实例vm挂载
 vm.$mount("#app")
data: 
对象形式
data: {
        msg:"你好 vue"
      }
函数形式, 返回一个对象
data() {
        return {
          msg:"你好 VUE"
    }
}

vue实例中使用数据, 记得加this;
模板中使用数据,不用加this;
模板中调用方法,加()/也可以不加,但是不传参,默认是event事件;
模板中调用计算属性,不用加(),计算属性是一个属性,get方法简写了;


MVVM框架:

vue2笔记_第3张图片
vue2笔记_第4张图片
vue2笔记_第5张图片


Vue中的数据代理:

Object.defineProperty的getter()与setter()方法

vue2笔记_第6张图片
vue2笔记_第7张图片


事件处理: v-on

v-on: 事件名 = “事件处理函数”
事件包括 ( 点击事件, 键盘事件, 滚动事件等等)

v-on:事件名  ==> 简写  @事件名

插值语法中调用事件处理函数加 ( ); 也可以不加()

<h4>姓名:{{fullName()}}</h4>

vue2笔记_第8张图片

事件处理函数要写法到 methods (方法) 配置项中去;
事件不传参可以省略();
==不传参,事件处理函数的形参第一个默认是event事件; 传参可以传vm上的数据;
事件传参, 想要保留event 事件,用$event占位;

<button v-on:click="showInfo1">按钮1</button>   //不传参
<button @click="showInfo2(100,$event)">按钮2</button>  //传参

methods: {
  showInfo1(e) { // 调用不加(), 回调函数的形参默认event事件;
    console.log(e.target); // e.target  拿到触发的对象(元素)
    console.log(e.target.innerText);  // e.target.innerText  拿到触发对象的内容
    console.log(this);  // this是Vue实例对象
  },
  showInfo2(num, e) {
    console.log(num, e);
  },
},

事件修饰符 :

  • .stop 阻止事件冒泡 ==> event.stopPropagation( )
  • prevent 阻止默认行为 ==> event.preventDefault();
  • .once 事件只触发一次
  • .native 触发组件或原生监听事件;
  • .self 只有在 event.target是当前操作的元素时才触发这个事件
  • .capture 事件的捕获阶段
  • .passive
  • 修饰符可以连续使用

键盘修饰符:
键盘事件:

  • keydown 键盘按下触发事件

  • keyup 按下去的键盘松开触发事件

  • 键盘keyCode码
    vue2笔记_第9张图片

键盘修饰符: <input type="text" @keydown.enter=key>

计算属性 computed

配置项: computed: { }

计算属性本质是一个函数,可以实时监听data中数据的变化,并return一个计算后的值,供组件渲染dom时使用;
调用的时候不需要加()。
计算属性依靠的是他的返回值 return, 必需要有;
计算属性会缓存计算的结果,只有计算属性的依赖项发生变化时,才会重新进行运算,所以计算属性性能更好。

计算属性: <span>{{fullName}}</span> -->
      1.定义:要用的属性不存在,要通过已有属性计算得来。
      2.原理:底层借助了Objcet.defineproperty方法提供的getter和setter。
      3.get函数什么时候执行?
            (1).初次读取时会执行一次。
            (2).当依赖的数据发生改变时会被再次调用。
      4.优势:与methods实现相比,内部有缓存机制(复用),效率更高,调试方便。
      5.备注:
          1.计算属性最终会出现在vm上,直接读取使用即可。
          2.如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生改变。

vue2笔记_第10张图片

计算属性的简写: 当只读取(get),不设置(set) 的时候,可以简写;

不简写: 
   computed: {
         fullName: {
            get() {
              return this.xing + "-" + this.ming;
            },
         },
}
  调用的时候:   <h4>姓名:{{fullName}}</h4>
简写: 
      computed: {
          fullName() { //对象中函数的简写
            return this.xing + "-" + this.ming;
          },
},
    调用的时候:   <h4>姓名:{{fullName}}</h4>

监视属性(侦听属性) watch :

  • 监视data中数据的变化;

  • 组件在初次加载完毕后不会调用 watch 侦听器,因为immediate默认false。

  • watch监视的是个整个对象的变化,只想监听对象中某个属性的变化, 加引号; " xx.xxx " : { } ;

  • 配置项: watch: { 监听对象: { } }

       1.当被监视的属性变化时, 回调函数自动调用, 进行相关操作
       2.监视的属性必须存在,才能进行监视!!
       3.监视的两种写法:
           (1)  new Vue时传入watch配置
           (2)  通过vm.$watch("监视对象",{配置项} ) 监视 ; ( 监视的属性,记得加引号)
    
watch监视的属性的配置项: 
/ immediate: false(默认),为true时,实例初始化时会调用一下handler函数; /
handler(newValue,oldValue){} 函数; 监视的属性发生修改时,触发handler()函数;
deep:true; 默认不开启(效率问题), 开启深度监视: 监视属性的多层级的数据的改变, 监听对象内的数据改变;a.b.c
deep深度监视:
     (1) Vue中的watch默认不监测对象内部值的改变(一层)。
     (2) 配置deep:true可以监测对象内部值改变(多层)。
     (3)  监视多级结构中属性所有数据的变化

监视属性的简写:

// 不简写
  watch: {
         hot:{
          handler(newValue,oldValue){
            console.log('hot改变了', newValue,oldValue);
             },
    	immediate:true,
     	deep:true,
          }
      },

当监视属性只有handler()函数时,可以简写

简写, / 当监视属性只存在handler()函数时/,可以简写
 watch: {
        hot(newValue,oldValue){
          console.log('hot改变了',newValue,oldValue);
      }
},
// 简写 第二种方法, 在vue实例上监视
 vm.$watch("hot", function (newValue, oldValue) {
        console.log("hot改变了", newValue, oldValue);
      });

vue2笔记_第11张图片

vue2笔记_第12张图片
vue2笔记_第13张图片
vue2笔记_第14张图片
vue2笔记_第15张图片


computed、watch、方法的区别:

1.computed能完成的功能,watch都可以完成。
2.watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作。
vue2笔记_第16张图片

两个重要的小原则:
1.所被Vue管理的函数,最好写成普通函数,这样this的指向才是vm 或 组件实例对象vc。
2.所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数等、Promise的回调函数),最好写成箭头函数,
这样this的指向才是vm 或 组件实例对象vc。


样式绑定:
固定的样式写死, 不固定的写成动态的;

  • 绑定class样式
  • 绑定style样式

绑定class样式 :

通过v-bind: 属性绑定, v-bind: 指令可以使用data中的数据 ; 
  • 绑定字符串
<div class="box1" :class="color"></div>
  • 绑定数组
<div class="box1" :class="colorArr"></div>
  • 绑定对象 , 当对象的 value值为真时, 才绑定上
<div class="box1" :class="colorObj"></div>
  • 绑定style样式

行内样式 style绑定; 注意: 短横线命名法与驼峰命名法的切换;
v-bin: style 通常写成对象的形式 ;

对象写法
<div class="box1" :style="{fontSize: font_size + 'px'}">字体大小</div>
<div class="box1" :style="styleObj">字体大小</div>
数组写法
<div class="box1" :style="styleArr">字体大小</div>
     data: {
         font_size: 40,
          styleObj:{
            fontSize:40 + "px"
          },
          // 数组里面写样式对象
          styleArr:[
            {fontSize:40 + "px"}
          ]
       },

vue2笔记_第17张图片
vue2笔记_第18张图片
vue2笔记_第19张图片
vue2笔记_第20张图片


条件渲染 v-if 与 v-show

vue2笔记_第21张图片

v-if与v-show指令后面要跟表达式, 只有当表达式结果为真才显示;
v-if 是真正的节点的创建与销毁;
v-if 与 v-else-if , v-else配合使用, v-else后面不用在跟表达式,中间不能断开; v-if可以配合 template标签使用,
不会渲染template标签; tempalte标签无法添加样式;
v-show只是行内样式: display属性的显示与隐藏;


列表渲染 v-for

vue2笔记_第22张图片

v-for 遍历, 要加 :key, key要属性绑定, 且要有唯一性;
v-for/ v-of v-for 遍历数组, (item,index) 每一项, 下标 ;
v-for 遍历对象 (value , key, index) 属性值, 属性名 , 下标;

key的作用:

虚拟dom对比, diff算法;
文本节点对比, 标签节点对比, 具有差异的节点,新的节点更新旧的节点;
:key 要有唯一性 ;
用index作为key, 打乱顺序容易错乱

1. 虚拟DOM中key的作用:
	key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM, 
随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下				
2.对比规则:
		(1).旧虚拟DOM中找到了与新虚拟DOM相同的key:
			①.若虚拟DOM中内容没变, 直接使用之前的真实DOM!
			②.若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真
		(2).旧虚拟DOM中未找到与新虚拟DOM相同的key
, 创建新的真实DOM,随后渲染到到页面。
						
3. 用index作为key可能会引发的问题:
		1. 若对数据进行:逆序添加、逆序删除等破坏顺序操作:
会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。
		2. 如果结构中还包含输入类的DOM:会产生错误DOM更新 ==> 界面有问题。
  
4. 开发中如何选择key?:
		1.最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
		2.如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。

vue2笔记_第23张图片
vue2笔记_第24张图片

Vue.set()方法

Vue.set(目标, key , val) ;V要大写
Vue.set(目标, "key'", "val") 等价与 vm.$set(目标,"key","val")
添加具有响应式的数据到vm实例上, 但不能直接给vm实例 或 根数据对象(data) 添加 ;
响应式的数据, 具有 set()和get()方法;
数据代理
把vm._data.person 代理成 vm.person ;
Vue.set()方法

 vm配置对象中, this 就是 vm 
 Vue.set(this.person,"address","曹县") 
 等价与
  Vue.set(this._data.person,"address","曹县") 

vm.$set()方法

  this.$set(this.person,"school","港湾学院") 
  等价与 
  this.$set(this._data.person,"school","港湾学院") 

在vue实例的代码中, vm实例的配置对象中, this 指向vm的,使用data中的数据,或者methods中的方法加this ;
在插值语法中, 可以直接使用 vm实例中的data中的数据,不用加this;


数组更新检测的方法
变更方法:
在vue中不能直接通过数组的下标直接赋值的方法修改数组(arr[0]=123), 因为视图不会更新;
Vue 将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新。
以下方法会改变原数组;

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()
  • Vue.set()

vue监测数据
Vue监视数据的原理:

  1. vue会监视data中所有层次的数据。
  2. 如何监测对象中的数据?
    通过setter实现监视,且要在new Vue时就传入要监测的数据。
    (1).对象中后追加的属性,Vue默认不做响应式处理
    (2).如需给后添加的属性做响应式,请使用如下API:
    Vue.set(target,propertyName/index,value) 或
    this.$set(target,propertyName/index,value)
  3. 如何监测数组中的数据?
    通过包裹数组更新元素的方法实现,本质就是做了两件事:
    (1).调用原生对应的方法对数组进行更新。
    (2).重新解析模板,进而更新页面。
    4.在Vue修改数组中的某个元素一定要用如下方法:
    1.使用这些API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
    2.Vue.set() 或 vm.$set() 特别注意:Vue.set() 和 vm.$set() 不能给vm 或 vm的根数据对象 添加属性!!!

v-model 双向数据绑定

v-model绑定的就是value值; v-model是v-bind和v-on的语法糖;
v-bind绑定value属性,v-on监听input事件;

v-model:value  简写==>  v-model
<input type="text" :value="msg" @input="msg = $event.target.value" />

收集表单数据:
若:,则v-model收集的是value值,用户输入的就是value值。
若:,则v-model收集的是value值,且要给标签配置value值。使用v-model后,可以不用再写name属性;
若:
1.没有配置input的value属性,那么收集的就是checked(勾选 or 未勾选,是布尔值true/false)
2.配置input的value属性:
(1)v-model的初始值是非数组,那么收集的就是checked(勾选 or 未勾选,是布尔值true/false)
(2)v-model的初始值是数组,那么收集的的就是value组成的数组
若: input的类型是 select 或 textarea , v-model 收集的也是value值;
v-model的三个修饰符:
.lazy:当前表单元素失去焦点时再收集数据;
.number:输入字符串转为有效的数字, 通常配合 number 类型的input框使用 ; (输入框通常输入的是字符串类型);
.trim:输入首尾空格过滤;

v-model原理:
vue2笔记_第25张图片

v-model拆解:
vue2笔记_第26张图片

v-model简化:
vue2笔记_第27张图片

.sync修饰符: 实现子组件与父组件的双向绑定,并且可以实现子组件同步修改父组件的值。

vue2笔记_第28张图片
vue2笔记_第29张图片


过滤器

本质: 函数 , 依靠返回值 , 对 | 前的数据进行处理;
过滤器本质是一个函数, 可以加() 或者 不加 ; 加()后, ()内的传的实参;
过滤器的处理函数的默认实参默认就是要处理的数据, 即 | 前的数据;
过滤器可以传参, 传的参数就是额外参数, 形参默认值(rest参数);
过滤器可以串联使用, 上一个过滤器的返回值作为下一个过滤器的参数;
过滤器依靠返回值对数据进行处理,通过管道符 | 对数据进行处理 ;
要处理的数据会作为一个实参,过滤器函数处理完, 把返回值 替换掉整个 {{ }} 中的数据;

{{ 数据名  |  过滤器名 }} 
  定义:对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理)。 		

语法:
1.注册过滤器:Vue.filter(name,callback) 或 new Vue{ filters:{} }
2.使用过滤器:{{ xxx | 过滤器名}} 或 v-bind:属性 = “xxx | 过滤器名”, 插值语法中使用,或者v-bind属性绑定定时用;

  备注:
  		1.过滤器也可以接收额外参数、多个过滤器也可以串联
  		2.并没有改变原本的数据, 是产生新的对应的数据;
全局的过滤器 Vue.filter("过滤器名", 处理函数) , 全局过滤器要写在new Vue() 之前; 

<h4>全局过滤器: {{msg | quanju_Filter}}</h4>
 Vue.filter("quanju_Filter", function(val){
 	 return val.slice(0,7)
})
局部过滤器: new Vue配置项中的  filters:{ }
 <h4>过滤器的时间是: {{time | filterTime}}</h4>
 <h4>过滤器传参: {{time | filterTime("YYYY&&MM&&DD")}}</h4>
 <h4>过滤器串联: {{time | filterTime("YYYY&&MM&&DD") | mySlice()}}</h4>
 
  filters:{
    // 过滤器传参, 形参默认值:  如果有传了参数就用&&, 如果没有参数就用##
    filterTime(val,str="YYYY##MM##DD"){
       return  dayjs(val). format(str)
    },
    // 过滤器串联使用
    mySlice(val){
      return val.slice(0,4)
    }
  }

内置指令

v-指令名, 可以直接使用data中的数据;

v-on:  简写 @   事件绑定
v-bind:  简写 :   属性绑定, 可以使用vue实例data中的数据, 单向的数据绑定;
v-model:value  简写 v-model   input元素双向数据绑定
v-if,  v-else-if , v-else (这个不用跟数据) ; 条件渲染   条件为真才会创建这个节点;
v-show, 条件渲染	条件为真,会把这个节点的css属性的dispaly:none和block切换;
v-for, 列表渲染; :key = "something"  ; 当v-for和 v-if 一起使用时,v-for 的优先级比    v-if 更高
v-text指令; 不解析标签, 会替代节点的原有的内容;
<h2 v-text="msg"></h2> 
 {{ }} 是v-text的另一种写法
v-html指令; 解析标签, 会替代节点原有的内容;
<h2 v-html="msg"></h2>
v-cloak指令,不用跟值; 用于解决网速慢时页面展示出{{xxx}}的问题,先隐藏起来,
通常配合属性选择器使用, [v-cloak]{display:none;}; 当vue解析到v-cloak指令时,移除这个指令;
<h4 v-cloak>{{msg}}</h4>
v-once指令,不用跟值;只渲染一次,
1.v-once所在节点在初次动态渲染后,就视为静态内容了。
2.以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能。
<h4 v-once>计数器:{{n}}</h4>
 v-pre指令, 不用跟值; 不解析节点的{{}}内容;
1.跳过其所在节点的编译过程。
2.可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译。
<h4 v-pre>{{msg}}</h4>

自定义指令 direactive

v-自定义指令名 = "数据"
对底层DOM元素的操作; 自定义指令的this指向window, 方便操作 DOM 元素 (不是自定义指令中的回调函数的this指向)。
自定义指令中的函数何时调用: 1. 指令与元素成功绑定时(一上来)。2.指令所在的模板被重新解析时。

局部自定义指令
directives:{指令名 : 配置对象 }
directives:{指令名 : 回调函数 }
对象的key可以加引号也可以不加,
对象中函数的增强写法, 省略 : function

全局自定义指令 Vue.directive(“指令名”,配置对象)
Vue.directive(“指令名”,回调函数)

配置对象中的钩子函数;
bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置;
inserted:被绑定元素插入父节点时调用 ;
update:指令所在模板结构被重新解析时;
unbind:只调用一次,指令与元素解绑时调用;
*/
bind:绑定时调用。
inserted:插入到父节点时调用。
update:组件更新时调用。
componentUpdated:组件更新完成时调用。
unbind:解绑时调用。

钩子函数的参数; 
el:指令所绑定的元素,可以用来直接操作 DOM; 
binding:一个对象; 主要使用binding.value:自定义指令所依赖的值; 例如: v-big='num'

binding 对象具有以下常见属性:
value: 指令绑定的值。
oldValue: 指令前一个绑定的值,只在update钩子函数中可用。
expression: 绑定值的原始表达式字符串。
arg: 指令的参数。
modifiers: 一个包含修饰符键值对的对象。
name: 指令的名称。
vnode: 虚拟节点对应的 VNode 实例。
instance: 当前组件实例。
el: 指令所绑定的元素,可以通过binding.el访问。
/通过访问 binding.value 属性,可以获取指令的绑定值,例如: v-big='num'/
在很多时候,你可能想在 bind 和 update 时触发相同行为,而不关心其它的钩子。比如这样写:
Vue.directive('big', function (el, binding) {
  el.innerText = binding.value*10
})
自定义指令总结:
一、定义语法:
	(1).局部指令:
    	directives:{指令名:配置对象}   或 directives{指令名:回调函数}

	(2).全局指令:
		Vue.directive(指令名,配置对象) 或   Vue.directive(指令名,回调函数)
二、配置对象中常用的3个回调:
	(1) bind:指令与元素成功绑定时调用。
	(2) inserted:指令所在元素被插入页面时调用。
	(3) update:指令所在模板结构被重新解析时
三、备注:
	1.指令定义时不加v-,但使用时要加v-2.指令名如果是多个单词,要使用kebab-case(短横线)命名方式,不要用camelCase(驼峰)命名。 例如: v-custom-directive
 	3.指令的回调函数名是短横线命名的, 可以用引号引起来,解决报错;
 	
  	directives:{
		'big-number'(element,binding){ element.innerText = binding.value * 10; }
    }

<input type="text" v-fbig="num">

Vue.directive("fbig",{
  bind(el,binding){
    el.value = binding.value*10
  },
  inserted(el,binding){
    el.focus()
  },
  update(el,binding){
    el.focus()
    el.value = binding.value*10
  }
})

生命周期

生命周期:一些特殊的回调函数;
1.又名:生命周期回调函数、生命周期函数、生命周期钩子。
2.是什么:Vue在关键时刻调用的一些特殊名称的函数。
3.生命周期函数的名字不可更改。
4.生命周期函数中的this指向是vm 或 组件实例对象。

Vue的生命周期
vue2笔记_第30张图片
vue2笔记_第31张图片


vm.$destroy() 方法 ;完全销毁一个实例。清理它与其它实例的连接,解绑它的全部指令及自定义事件监听器; 触发beforeDestroy 和 destroyed 的钩子。
初始化页面(第一次进入页面)会触发前四个钩子;

常用的生命周期钩子:
   1.created : 请求数据; (调用methdos中的函数, methods中定义函数,请求数据);
 	2.mounted: 操作DOM;发送ajax请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】。
	3.beforeDestroy: 清除定时器、解绑自定义事件、取消订阅消息等【收尾工作】。
关于销毁Vue实例
	1.销毁后借助Vue开发者工具看不到任何信息。
	2.销毁后自定义事件会失效,但原生DOM事件依然有效。 
	3.一般不会在beforeDestroy操作数据,因为即便操作数据,也不会再触发更新流程了。
 (解绑的是自定义事件, 原生DOM事件不影响, 该钩子函数可以改变数据,但不会在视图更新了)

函数作用域之间有作用域, 无法访问到其他函数内的变量;
解决方法: 追加vm的属性; this.XXX = “变量” ; 这里的this是vm实例对象;


组件 component

vue2笔记_第32张图片

组件 : 为了复用;
非单文件组件 : 包含多个组件。
单文件组件: 只包含一个组件(.vue文件的组件)。
全局组件 Vue.component(“标签名”, 组件构造器)
局部组件 components: { } 配置项 Vue.extend()
组件构造器,配置项内容和vm一样, 但是不要写el配置项,data要写成一个函数,返回一个对象;
template模板内要有一个根元素包裹, 换行可以使用模板字符串 ;

Vue中使用组件的三大步骤:
		一、创建组件(定义组件)
		二、注册组件 (components配置项中注册)
		三、使用组件(写组件标签)

一、如何创建一个组件? 	使用Vue.extend(options)创建,其中options和new Vue(options)时传入的那个options几乎一样,但也有点区别; 	区别如下:
		1.el不要写,为什么? ——— 最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器。
		2.data必须写成函数,返回一个对象; 为什么? ———— 避免组件被复用时,数据存在引用关系, 组件各自维护独立的数据拷贝。 	备注:使用template可以配置组件结构, 要有根元素包裹, 换行可以使用模板字符串; 

二、如何注册组件?
		1.局部注册:靠new Vue的时候传入components配置项, components:{"组件标签名": 组件构造器名};  //对象的key与value ; 
		2.全局注册:靠Vue.component('组件标签名',组件构造器名)

三、编写组件标签: 		<组件标签名></组件标签名>  //单词首字母大写
组件名注意点: 
几个注意点:
	1.关于组件名:
		一个单词组成:
			第一种写法(首字母小写):school
			第二种写法(首字母大写):School
		多个单词组成:
			第一种写法(kebab-case命名):全小写; my-school , 对象的key值加引号; 
			第二种写法(CamelCase命名):单词首字母大写;  MySchool (需要Vue脚手架支持)
		备注:
			(1).组件名尽可能回避HTML中已有的元素名称,例如:h2都不行。
			(2).组件构造器配置项中, 可以使用name配置项,指定组件在开发者工具中呈现的名字。

	2.关于组件标签使用:
		第一种写法:<school></school>
		第二种写法:闭合标签 <school/>
 (需要Vue脚手架支持, 不用使用脚手架时,<school/>会导致后续组件不能渲染。)

	3.组件构造器的简写方式:
		const school = Vue.extend(options) 可简写为:const school = options ; 省略了 Vue.extend() ,直接{ }

配置项中, name 属性 , 可以指定组件在开发者工具中呈现的名字;
vm是根组件; vm管理app组件, app组件管理所有的组件;
main.js文件是入口文件,挂载App组件;
创建App标签,render函数的 createElement(“标签名”,“内容”)函数

组件的嵌套: 父子组件关系;
子组件定义之后, 子组件在父组件components:{ }配置项中注册;
在父组件的template模板中使用子组件标签;
子组件的定义要在父组件之前; 否则父子间读取不到;
定义 => 引入 => 注册 => 使用

VueComponent 组件实例对象

组件的本质: 一个构造函数 ==》VueComponent的构造函数,调用Vue.extend()方法生成的。
每次调用Vue.extend,返回的都是一个全新的VueComponent!!!!

关于VueComponent:
1.school组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的。
2.我们只需要写<school/><school></school>,Vue解析时会帮我们创建school组件的实例对象,

即Vue帮我们执行的:new VueComponent(options)

3.特别注意:每次调用Vue.extend,返回的都是一个全新的VueComponent!!!!
4.关于this指向:
	(1).组件配置中:vc
    	data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【VueComponent实例对象】。
	(2).new Vue(options)配置中:vm
		data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【Vue实例对象】。
5.VueComponent的实例对象,以后简称vc(也可称之为:组件实例对象)。
6.Vue的实例对象,以后简称vm。

组件是可复用的 Vue 实例,所以它们与 new Vue 接收相同的选项,例如 data、computed、watch、methods以及生命周期钩子等。
el除外,因为最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器。
一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝; 防止数据的污染。

Vue与Vuecomponent的关系

vue2笔记_第33张图片

  1. 一个重要的内置关系:VueComponent.prototype.proto === Vue.prototype
  2. 为什么要有这个关系:让组件实例对象(vc)可以访问到 Vue原型上的属性、方法。 (组件可以访问vue上的数据,方法。。。)
  3. vm对象和组件对象的关系: vm的原型对象 === 组件对象的原型对象的原型对象。

**单文件组件: 以.vue结尾的文件; **

包含html,js,css;一个文件就是一个组件;
export default {组件的定义} 暴露出去;Vue.extend()省略; 但仍调用Vue.extend()方法,生成一个全新的组件实例对象;
组件的name属性, vue开发者工具查看到的组件名,没有配置name,则是组件发文件名;
单文件组件文件的命名, 推荐大驼峰命名;同组件标签命名一致;
template标签内要有一个根组件div包含;
mian.js 入口文件,import App from ‘./App.vue’,创建vue实例;
index.html 首页;
App.vue文件, 汇总组件, 管理组件,引入汇总组件,在配置项componets中注册,并在模板中使用, 由vm管理 App.vue文件;一人之上,万人之下;
impiort 组件名 from “XXX路径” ;
.vue .js 文件名后缀名可以省略;

组件分类: 页面组件和复用组件。

  • 页面组件存放在 src/views文件夹=>配合路由, 展示页面;
  • 复用组件存放在src/components文件夹=>封装复用, 展示数据;

省略
.vue文件/.js文件 引入 可以省略文件名的后缀 ;
引入index.js文件可以省略, 直接引用到文件夹路径 ;


脚手架 Vue-cli

文档: https://cli.vuejs.org/zh/guide/

第一次全局安装@vue/cli。 npm install -g @vue/cli
切换到你要创建项目的目录,然后使用命令创建项目 。 vue create 项目文件名
进入所在的项目文件夹, 然后启动项目 npm run serve/dev

切换淘宝镜像

npm config set registry https://registry.npm.taobao.org

Vue 脚手架隐藏了所有 webpack 相关的配置,若想查看具体的 webpakc 配置,
vue inspect > output.js

脚手架文件结构
	├── node_modules : 所依赖的包
	├── public  :打包后发布的
	│   ├── favicon.ico: 页签图标
	│   └── index.html: 主页面
	├── src	   : 写代码的地方
	│   ├── assets: 存放静态资源
	│   │   └── logo.png
	│   │── component: 存放组件
	│   │   └── HelloWorld.vue
	│   │── App.vue: 汇总所有组件
	│   │── main.js: 入口文件
	├── .gitignore: git版本管制忽略的配置
	├── babel.config.js: babel的配置文件
	├── package.json: 应用包配置文件 
	├── README.md: 应用描述文件
	├── package-lock.json:包版本控制文件
vue.config.js配置文件  (项目的根目录下)

使用vue.config.js可以对脚手架进行个性化定制,详情见:https://cli.vuejs.org/zh/config

配置项 :   lintOnSave: false; // 关闭语法检查

render函数 渲染函数
关于不同版本的Vue:

	1.vue.js与vue.runtime.xxx.js的区别:
		(1).vue.js是完整版的Vue,包含:核心功能+模板解析器。
		(2).vue.runtime.xxx.js是运行版的Vue,只包含:核心功能;没有模板解析器。

	2.因为vue.runtime.xxx.js没有模板解析器,

所以不能使用template配置项,需要使用
render函数接收到的createElement函数去指定具体内容。

  render: h => h(App) 函数 ; render 函数跟template配置项 一样都是创建 html 模板的; 
  必须要有返回值, vue自动调用; 参数是createElement函数, 这个函数第一个参数是创建的标签名, 第二个参数是标签的内容; 
  render:function(createElement){return createElement("标签名","内容")} ;
  h代替了createElement函数;
  render:function(h) {
    return h(App)  //App组件, App是一个变量,已将引入了, App组件内有标签内容; 
  },
  箭头函数的简写 :
  render: h => h(App) 
小计简写:
箭头函数; ()=>{}
对象的简写; 属性的简写,方法的简写;对象中方法通常是匿名函数;

ref属性

作用: 打标记 ref = "xxx"  
this.$refs.xxx 应用在html标签上获取的是真实DOM元素; 应用在组件标签上是该组件实例对象(vc)
1. 被用来给元素或子组件注册引用信息(id的替代者)
2. 应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc)
3. 使用方式:
    1. 打标识:<h1 ref="xxx">.....</h1><School ref="xxx"></School>
    2. 获取:this.$refs.xxx

props 自定义属性:

功能:让组件接收外部传过来的数据 ;
组件标签传值, props配置项接收, 然后使用传过来的值;
同过v-bind 属性绑定,可以使用组件内的数据; 可以传数据,也可以传递函数;

 <Student :id="name"></Student>
 一个等号, 赋值操作, 等号右边赋值给左边; 
 把右边name的值赋值给左边的id, id属性绑定可以使用组件data中的数据了;
 在子组件中props配置项中接收id
 通常左右两边都是一样的;  <Student :name="name" />

props ==>property 属性 ;
v-bind 属性绑定; v-bind:xxx = “表达式” ;
props: [ ] 或 { } ;
props 只可读,不能修改,数据单向流;
type 限制传入的数据的类型;
default 不传值, 会显示默认值 ;
required 和 default 冲突 ;
如果传入的数据是对象或数组类型;
default必须是一个函数; 返回 默认对象或数组 return { } 或 [ ] ;

 default() {
  return { message: 'hello' }
 	// or
  return  ["hello"]
 }

1. 功能:让组件接收外部传过来的数据

2. 传递数据: 在组件标签内写属性 <Demo name="xxx"/>

3. 接收数据: props 配置项接受数据

    1. 第一种方式:数组形式(只接收):props:['name']  // 加引号接受, 接收到是传过来的数据

    2. 第二种方式:对象形式(限制类型):props:{name:String}

    3. 第三种方式:对象形式(限制类型、限制必要性、指定默认值):

      
        props:{
        	name:{
        	type:String, //类型
        	required:true, //必传项
        	default:'张三' //默认值
        	}
        }
 

    > 备注:props是只读的,Vue底层会监测你对props的修改,
    如果进行了修改,就会发出警告,
    若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。 
  data() {
    return {
      msg: this.message, // props中的message; 
      myAge:this.age     // props中的age ; 
    };
  },
组件中的this是组件实例对象; vc

vue2笔记_第34张图片
vue2笔记_第35张图片
vue2笔记_第36张图片
vue2笔记_第37张图片
在这里插入图片描述
vue2笔记_第38张图片


通过props实现子父通信 :

通过自定义属性传递方法
通信: 数据/函数等在各个组件实例对象(vc)上的传递;
父组件先在methods中定义一个函数 ;
把这个函数通过v-bind 在 子组件标签上传递给子组件;
子组件在props配置项中接收;
然后子组件调用父组件传递过来的函数,把要传递的数据作为该函数的实参传递过去(也可以不传数据,仅调用父组件传递过来的函数);
父组件接收传递过来的参数,然后使用;

父组件  //App组件
  methods: {
// 父组件先在methods中定义一个函数 ;
    addTodo(xxx){ //形参
// 父组件接受子组件传递过来的数据作为该函数的参数, 然后使用
      this.todos.unshift(xxx)
    }
  },
  <MyHearder :addTodo="addTodo"/>  // 在子组件标签上通过v-bind把这个函数传给子组件;  //不加()
  子组件   // Myhearder组件
  props: ["addTodo"],  //接受传递过来的函数  // [ ] 中加引号; 
  this.addTodo(xxx);  // 子组件调用这个函数, 把数据作为参数传递给父组件

vue2笔记_第39张图片

v-bind: ==> : 属性绑定, 绑定之后可以使用组件/vm上的数据,方法…
v-bind 可以传数据, 也可以传函数, 等等;
函数的形参和实参是一一对应的,函数的调用是实参,函数定义时的参数是形参,函数内部使用的是形参,实参是要确定传过去的值;
实参和形参可以名称不一致, 但一定要一一对应;
模板中,可以使用方法(), 显示方法的最终处理结果return.


mixin 混入

功能:可以把多个组件共用的配置项提取成一个混入对象(单独的js文件,暴露出去), 然后引入使用; (复用相同的配置项)
局部混入 引入混入文件, 组件中 mixins:[ ];
全局混入 main.js中 引入混入文件, Vue.mixin( ),vm和所有的vc都可以使用;
组件和混入的配置一样时, 以组件的为主;

 mixin(混入)

1. 功能:可以把多个组件共用的配置提取成一个混入对象, 然后复用

2. 使用方式:

 第一步: 定义混合, 抽离成单独的js文件,暴露出去:


    {
        data(){....},
        methods:{....}
        ....
    }


    第二步: 引入混入文件, 使用混入:    
    	全局混入:Vue.mixin(xxx)
    ​	局部混入:mixins:['xxx']

插件(plugins) 与 Vue.use() 方法

Vue.use()的原理: 调⽤插件的 install()⽅法 ;
Vue 插件是一个包含 install 方法的对象
通过 install 方法给 Vue 或 Vue 实例添加方法, 定义全局指令等
引入插件, 使用插件

1. 功能:用于增强Vue

2. 本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据。

3. 定义插件:抽离成一个单独的js文件, plugins.js 暴露出去 ; 


    对象.install = function (Vue, options) {
        // 1. 添加全局过滤器
        Vue.filter(....)
    
        // 2. 添加全局指令
        Vue.directive(....)
    

        // 3. 配置全局混入(合)
        Vue.mixin(....)
    
        // 4. 添加实例方法
        Vue.prototype.$myMethod = function () {...}
        Vue.prototype.$myProperty = xxxx
    }
    

4. main.js文件 引入插件 import {xxx}  from  "路径"  

5. main.js文件 使用插件:Vue.use( )

scope

App.vue文件不加scoped, 可以给组件添加统一的样式; 
scoped作用
lang
/deep/
添加 scoped  
<style scoped>
作用:让样式只在当前组件生效,使组件的样式不相互污染。
原理: 自动给标签添加一个唯一标识(hash值), data-v-xxx的属性,配合属性选择器给元素添加样式; 
<div data-v-3375b0b8 data-v-7ba5bd90 class="school">
.school[data-v-3375b0b8] {XXX:XXX;}
<style lang="">  
编译语言,less/scss/css; 不加lang=""默认css ;
深度穿透;深度作用选择器; 
>>>   /deep/  ::v-deep   :deep()

vue2笔记_第40张图片
vue2笔记_第41张图片
vue2笔记_第42张图片
vue2笔记_第43张图片


封装组件:

组件化编码流程:
	(1).拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突。
	(2).实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:
			1).一个组件在用:放在组件自身即可。
			2). 一些组件在用:放在他们共同的父组件上(状态提升)。
	(3).实现交互:从绑定事件开始。
props适用于:
	(1).父组件 ==> 子组件 通信
	(2).子组件 ==> 父组件 通信(要求父先给子一个函数,通过调用函数传参的方法,进行通信)
使用v-model时要切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改的!
props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做。

webStorage 浏览器端的本地存储

存的都是JSON格式的字符串对象;
JSON.stringify()
JSON.parse()

存储内容大小一般支持5MB左右(不同浏览器可能还不一样) 
浏览器端通过 Window.sessionStorage 和 Window.localStorage 属性来实现本地存储机制。 
相关API:
  xxxxxStorage.setItem('key', 'value');  
  	该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值。 
  xxxxxStorage.getItem('key'); 
  	该方法接受一个键名作为参数,返回键名对应的值。
  xxxxxStorage.removeItem('key');  
  	该方法接受一个键名作为参数,并把该键名从存储中删除。
  xxxxxStorage.clear();
  	该方法会清空存储中的所有数据。  
  备注: 
  		SessionStorage存储的内容会随着浏览器窗口关闭而消失。 
    	LocalStorage存储的内容,需要手动清除才会消失。 
     	xxxxxStorage.getItem(xxx)如果xxx对应的value获取不到,那么getItem的返回值是nullJSON.parse(null)的结果依然是null

vue2笔记_第44张图片

/ 深拷贝 /
JSON.stringify()方法  // stringify 字符串化; 
    将对象转成JSON对象,JSON对象的key和value都是字符串; 
    {"a":"hello","b":"你好"};
    对象的key加不加引号都可以的;
JSON.parse()方法
    把JSON字符串解析成对象;
toString()方法
    转成字符串的方法

$emit 自定义事件

在子组件标签上绑定自定义事件;
给子组件的实例对象vc绑定一个自定义的事件, 通过触发这个自定义事件, 传数据来进行通信;
事件处理函数(是一个回调函数) ,写在父组件的methods中;
@自定义事件 = “事件处理函数”
事件处理函数写在父组件中;
自定义事件写在父组件中的子组件标签上;
自定义事件在子组件中触发 this.$emit;
自定义事件被触发, 对应的事件处理函数就会执行;
自定义事件可以和事件处理函数名成可以一致;
事件处理函数在父组件中定义, 来处理子组件传递过来的值 ;
在子组件中通过this.$emit() 来触发自定义事件, 并传递值过去;
this.$emit("自定义事件", 数据); // 自定义事件名要加引号;
自定义事件也可以使用事件修饰符;
解绑自定义事件, 解绑指定的 this.$off("自定义函数名");
解绑多个放一个数组中 this.$off(["自定义1","自定义2",.....]); 解绑所有 this.$off() ;
组件也可以绑定原生事件, 通过native修饰符 ; < Demo @click.native = “事件处理函数” / >

1. 一种组件间通信的方式,适用于:子组件 ===> 父组件
2. 使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件, 事件的回调函数(事件处理函数)A的methods中。
3. 绑定自定义事件:
(1)第一种方式,在父组件中:<Demo @atguigu="test"/><Demo v-on:atguigu="test"/>
(2)第二种方式,在父组件中:通过ref绑定自定义事件; 
this.$refs.xxx 获得的是该组件的实例对象vc, 然后给他绑定自定义事件,通过$on("自定义事件",回调函数) 
// 这个回调函数要么写成箭头函数, 要不提前在methods中写好,然后调用这个函数 this.函数名; 
因为写成普通函数, this会指向子组件的实例对象上;
<Demo ref="demo"/>
......
mounted(){
   this.$refs.demo.$on('atguigu',this.test)
}
(3)父组件绑定: 若想让自定义事件只能触发一次,可以使用once修饰符,或$once方法绑定事件。
<Demo @atguigu.once="test"/>this.$refs.demo.$once('atguigu',this.test)
(4)子组件触发: 触发自定义事件:this.$emit('atguigu',数据)	
(5)父组件: 解绑自定义事件this.$off('atguigu') ; 
(6)要在beforeDestroy函数中解绑;
(7)组件上也可以绑定原生DOM事件(click,等等),需要使用native修饰符。
(8)注意:通过this.$refs.xxx.$on('atguigu',回调)绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题!

vue2笔记_第45张图片

绑定自定义事件 $on("事件名",事件处理函数) ;
解绑自定义事件$off();
绑定一次性的自定义事件 $once("事件名",处理函数);
子组件触发父组件绑定的自定义事件, $emit("自定义事件名",参数)
vue2笔记_第46张图片

vue2笔记_第47张图片
vue2笔记_第48张图片
vue2笔记_第49张图片
vue2笔记_第50张图片

emits节点:
用于声明由组件触发的自定义事件。
vue3中配置象, emits: [ ‘xxx’ ], 字符串数组;
可以理解为父组件把自定义事件传递给子组件, 子组件通过emits来接收;
vue2中emits节点可以省略, 直接通过$emit来触发自定义事件;


全局事件总线 (GlobalEventBus)

一种组件间通信的方式,适用于任意组件间通信。

this.$bus.$on()
this.$bus.$emit()
this.$bus.$off()
全局事件总线说到底就是个对象,我们通常就是用vm对象作为全局事件总线使用,把vm对象添加到Vue原型对象, 就形成全局事件总线(vm);
Vue.prototype.$bus = this 

想要成为事件总线的条件:

1、所有的组件对象必须都能看得到这个总线对象,因此我们把这个对象放在了Vue原型; 
2、这个事件总线对象必须能调用$on,$emit,$off方法; 
3、总线对象必须是Vue的实例化对象或者是组件对象;

安装全局事件总线:

位置: 在入口文件mian.js中的创建vm实例的beforCreated钩子中 ;
new Vue({
	......
	beforeCreate() {
		Vue.prototype.$bus = this, / this就是vm实例,挂载到vue原型上, 每个组件通过this.$bus都也以使用 /
  //安装全局事件总线,$bus就是当前应用的vm ; 给Vue原型上添加了一个$bus公共项, 都去通过这个公共项进行通信;
	},
    ......
}) 
使用事件总线:
1.接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。
methods(){
  demo(){......} // 事件的处理函数
}
......
mounted() {
  this.$bus.$on('xxxx',this.demo) // this.$bus.$on('xxx',()=>{}) 处理函数为箭头函数
}
2.传递数据的组件 : 
通过触发 this.$bus.$emit('xxxx',数据)
3.最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件。
beforeDestroy() {
  this.$bus.$off('xxxx')
}
要数据的组件 ==> 绑定事件   this.$bus.$on("自定义事件名",处理函数); 箭头函数或者提前定义好 this调用; 
传数据的组件 ==> 触发事件   this.$bus.$emit("自定义事件名",参数) ; 
要数据的组件 ==> 解绑事件   this.$bus.$off("自定义事件名")

vue2笔记_第51张图片

使用的时候引入Bus; import Bus from ‘@/utils/EventBus.js’
vue2笔记_第52张图片


消息订阅与发布 (pubsub)

一种组件间通信的方式,适用于任意组件间通信。 依靠 pubsub-js包,pubsub对象; 
subscribe订阅消息; publish 发布消息; unsubscribe 取消订阅; 
   使用步骤:
安装pubsub:npm i pubsub-js -S
引入: import pubsub from 'pubsub-js'
接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。

订阅消息的组件定义事件,来接收数据
methods(){
    // 这个处理函数,两个参数,第一个是自定义事件的名称, 第二个才是传递过来的数据;  
  demo(name,data){......}
}
......
mounted() {
    	//订阅消息 
  this.pid = pubsub.subscribe('事件名',this.demo) //或箭头函数   
  // this.pid 给vc上追加属性, 要取消订阅this.pid; 类似于定时器的取消;
}
提供数据的组件发布数据;
提供数据:pubsub.publish('事件名',数据)  // 发布消息
在订阅消息的组件取消订阅; 
最好在beforeDestroy钩子中,用PubSub.unsubscribe(this.pid) 去取消订阅。

provide&inject 依赖注入

provide/inject 提供/注入
适用于多层 祖先/父组件 向子/孙组件通信;
vue2笔记_第53张图片

provide写法(提供):
 1. 使用函数的形式,返回一个对象,可以访问到 this,可以使用data中的数据;
 provide(){
     return{
         xxx:'xxx', // 基本数据类型=>非响应式的;
         xxx:{xxx..} // 引用数据类型=>响应式的;
     } 
 }
2. 对象形式,不能使用this,无法传递data中的数据;
provide:{
    xxx:'xxx", 
}
基本数据类型响应式方法:
import {computed} from 'vue'
  provide() {
    return {
     // c:this.count , count是基本数据类型,不是响应式的;
     c: computed(()=>this.count)
    / vue3中 通过computed()方法, 通过匿名函数,return出去 /
  },
  * / 后代组件使用该值时, .value 使用, 因为该数据变成响应式的了; / 
  {{c.value}}
inject(注入)写法:
1. 数组写法
inject:['xx','xxx']
2. 对象写法
inject:{
    //注入别名, localName是在本组件的属性名;  provideName是父组件提供的属性;
    localName:{from: 'provideName'}  简写: localName: 'provideName',
    //默认值,防止父组件没有提供值过来;
     meg:{ default:'默认值xxx' },
}

Vue.nextTick

  • 在下一次 DOM 更新循环结束之后执行延迟回调。
  • 在修改数据之后立即使用这个方法,获取更新后的DOM。
  • 想要立即拿到更新后的DOM,要在nextTick的回调函数中去获取更新后的Dom的值。
  • 语法: this.$nextTick(箭头函数)
  • 作用:在DOM更新完成后,在这个方法中做某件事;
  • 原因:data改变是响应式的,但是更新dom是异步的;
  • 是一个微任务;
    vue 异步更新队列: vue是依靠数据驱动视图更新的,该更新的过程是异步的。即:当侦听到你的数据发生变化时,Vue将开启一个异步更新队列,等这个队列中所有数据变化完成之后,再统一进行更新视图(DOM)。
    vue在修改数据后,视图(DOM)不会立刻更新,要等到所有数据变化完成之后,再统一进行视图更新。
    所以,在修改数据更新立马读取DOM是获取不到新数据的,
    可以在修改数据之后, 使用这个nextTick方法, 在指定的函数里获取更新后的DOM。
  • 等待当前 DOM 更新队列中的所有同步和异步更新完成后执行回调函数。
  • 用于确保在 DOM 更新之后执行操作,例如获取更新后的 DOM 元素。

nextTick 是 Vue 提供的一个方法,用于在 DOM 更新之后执行回调函数。
它的作用是等待当前 DOM更新队列中的所有同步和异步更新完成后,再执行传入的回调函数。
在 Vue 中,当我们修改了组件的数据或者调用了一些可能会触发 DOM更新的方法时,这些更新不是立即生效的,而是会被加入到一个队列中,在下一次事件循环中进行处理。
也就是说,如果我们需要在某个 DOM 更新完成后执行一些操作(例如获取更新后的 DOM 元素),直接在更新代码之后立即执行是不准确的。
这时候就可以使用 nextTick方法来确保在 DOM 更新完成后再执行相应的操作。
需要注意的是,nextTick() 方法是异步执行的,也就是说回调函数会在下一个事件循环才被调用,因此应该将相应的操作放在回调函数中以确保在正确的时机执行。


插槽 slot

封装一个组件会多次复用,但是不同场景下又有少部分结构数据会发生变化,(当然可以用不同的子组件)
那么就得通过父组件告诉子组件结构的变化的内容是什么,此时就要用到这个插槽slot;

子组件当中<slot></sloat> 其实就是占位用的,让父组件标签内给它填充内容,可以带标签; 
父组件标签内写内容, 子组件对应的位置显示内容;  
作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式; 
插槽通常与template标签配合使用,  emplate不会被解析;  
插槽就是预留的一个位置, 把组件标签内的内容解析完之后放到指定位置;
  • 匿名(默认)插槽
  • 具名插槽
  • 作用域插槽

匿名插槽

v-slot:default <==> #default
没有名字的插槽

  1. 父组件标签内有内容, 子组件对应的内没有默认值, 就会显示该内容;
  2. 父组件标签内没有内容, 子组件默认值 会显示默认值;
  3. 父组件标签内有内容, 子组件默认值, 插槽的默认值会被覆盖;

父组件中:

<Category>
   <div>html结构</div>
</Category>

子组件中:

<template>
    <div>               
       <slot>插槽默认内容...</slot>  // 会显示 
html结构
, (后备内容)
</div> </template>

具名插槽

有名字的插槽, 父组件标签内容放到子组件<slot name="xxx">的name相对应的位置; 
语法: 	<p  v-slot:xxx> hello vue </p>
 		<slot  name="xxx"> </slot>
插槽的简写: v-slot:xxx  <==> #xxx  <==> slot="xxx" 只能添加在 <template>标签上;
插槽的默认值: v-slot:default  <==> #default ;
	 <slot></slot> <==> <slot  name="default"></slot> 
	template标签的v-slot:xxx == #xxx ; 
	v-slot:xxx 只能配合template标签使用;
父组件中:
        <Category>
        	 <template v-slot:header> 
          	    <div>header位置</div>
          	 </template>    
            		
            <template #center>
              <div>center位置</div>
            </template>

            <template slot="footer">
               <div>footer位置</div>
            </template>
        </Category>
        
子组件中:
        <template>
            <div>
               <!-- 定义插槽 -->
               <slot name="header"></slot>     // 
header位置
<slot name="center">默认</slot> //
center位置
<slot name="footer">默认</slot> //
footer位置
</div> </template>

作用域插槽
vue2笔记_第54张图片

作用域插槽:类似于子传父通信, 把子组件的数据通过属性,包装成一个对象(通常命名scope),传递给父组件使用;
默认不能有其它插槽,如果要有其它插槽,必须设置为具名插槽;

理解:数据在子组件,但根据数据生成的结构需要父组件来决定。
子组件slot标签, 通过添加属性的方式传值;
所有添加的属性,会收集到一个对象中(通常命名scope), 传递给父组件;
父组件标签中的tempalate通过 #插槽名="scope"接收, 匿名插槽名为default;

子组件:
    <template>
  <div>
    <slot msg='四大名著' :shuName='books'></slot> // 默认插槽
    <slot name="game" id='手游' :games='games'></slot> // 具名插槽
  </div>
</template>
<script>
export default {
  name: 'slotComponent'  ,
  props: {
       
  },
  data () {
    return {
       books:['西游记','水浒传','三国演义','红楼梦'],
       games:['原神','王者荣耀','穿越火线']
    }
  }
}
</script>
父组件:
   <MySlot>
      <template v-slot:default="scope"> // 匿名插槽 #default, 传递过来的对象通常命名为scope;
        <div>{{scope}}</div>  //  { "msg": "四大名著", "shuName": [ "西游记", "水浒传", "三国演义", "红楼梦" ] } 
        <div>{{scope.msg}}</div> // 四大名著
      </template>
      <template #game="{games:youxi}"> // v-slot简写, scope对象的解构并重命名
          <div>{{youxi}}</div> // [ "原神", "王者荣耀", "穿越火线" ]
      </template>
  </MySlot>

slot属性弃用,具名插槽通过指令参数v-slot:插槽名的形式传入,可以简化为#插槽名;
slot-scope属性弃用,作用域插槽通过v-slot:xxx="slotProps"的slotProps来获取子组件传出的属性对象;
v-slot属性只能在template上使用


Proxy 代理服务器

同源策略: 协议,主机名(域名,IP), 端口号;三者相同; 跨域: 违反同源策略, 三者有一个不相同就会跨域,
不是同源的脚本不能操作其他源下面的对象;

解决跨域的方法:

  • cros+后端配置;
  • JSONP, 利用了script标签的src属性不受同源策略影响的特性, 但是只能get请求;
  • proxy, 配置代理服务器, (ngix服务器配置, vue-cli配置) ;
    vue2笔记_第55张图片
对脚手架进行定制, 在vue.config.js中配置; 
方法一: 在vue.config.js中添加如下配置:
devServer:{
  proxy:"http://localhost:5000"
}
说明:
    1.优点:配置简单,请求资源时直接发给前端(8080)即可。
    2.缺点:不能配置多个代理,不能灵活的控制请求是否走代理。
    3.工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器 (优先匹配前端本地的资源)

方法二: 编写vue.config.js配置具体代理规则:
module.exports = {
	devServer: {
      proxy: {
      '/api1': {// 匹配所有以 '/api1'开头的请求路径,  请求前缀,在端口号后面;
        target: 'http://localhost:5000',// 代理服务器的请求地址
        changeOrigin: true,
        pathRewrite: {'^/api1': ''}   // 键值对形式,正则表达式; 代理服务器对请求路径进行重定向以匹配到正确的请求地址
      },
      '/api2': {// 匹配所有以 '/api2'开头的请求路径;
        target: 'http://localhost:5001',// 代理目标的基础路径
        changeOrigin: true,	// 伪装
        pathRewrite: {'^/api2': ''}  // 重新匹配请求路径
      }
    }
  }
}

   changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
   changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:8080
   changeOrigin默认值为true;

说明:
    1.优点:可以配置多个代理,且可以灵活的控制请求是否走代理。
    2.缺点:配置略微繁琐,请求资源时必须加前缀。

Vuex

集中式状态(数据)管理工具,管理共享的数据,这个数据是响应式的,多组件共享;
vuex中的数据储存在内存中,刷新消失,单向数据流;
在Vue中实现集中式状态(数据)管理的一个Vue插件;
对vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。
核心在于创建的store对象, 全局都能访问到store对象; $store对象管理数据, 不能直接修改$store中的state数据,vue开发者工具检测不到;

vue2笔记_第56张图片

state 存储数据 ==> data ;
actions 处理异步任务: 调用commit方法 ; 可以获取context上下文对象,mini版的$store;
mutations 处理state中的数据, 处理同步任务:修改state中的数据必须通过mutations, 可以获取state对象;
getters ==>计算属性,依靠返回值:对state中的数据进行处理,然后使用,不会修改state中的数据, $store.getters.计算属性名; 可以获取state对象;
moduls 模块化的vuex:可以让每一个模块拥有自己的state、mutations、actions、getters,使得结构非常清晰,方便管理。

搭建vuex环境
npm  i  Vuex -S ;
创建文件:src/store/index.js  导出store对象;  // 引入文件时, 引入index.js文件, 可以省略/index.js     //  import store from "./store"
//引入Vue核心库
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//应用Vuex插件
Vue.use(Vuex)   // vue-cli 解析时,会先解析import 引入的东西; 

//准备actions对象——响应组件中用户的动作
const actions = {}
//准备mutations对象——修改state中的数据
const mutations = {}
//准备state对象——保存具体的数据
const state = {}
//准备getters对象, 列斯与计算属性, 对state中的数据进行加工然后使用;
const getters = {}


//创建并暴露store
export default new Vuex.Store({    // 通过new Vux.Store对象创建的store
	actions,
	mutations,
	state,
 	getters
})
在main.js入口文件中创建vm时传入store配置项  // 全局都可以访问store ,有了$store对象;
//引入store
import store from './store'
......

//创建vm
new Vue({
	el:'#app',
	render: h => h(App),
	store
})

基本使用
组件读取vux中的数据 : $store.state.数据名;
组件修改vuex中的数据: $store.dispatch(‘actions中的方法名’,数据) 或 $store.commit(‘mutations中的方法名’,数据);

备注:若没有网络请求或其他业务逻辑,组件中也可以越过actions,即不写dispatch,直接编写commit;
mutation中方法(函数)和commit的方法, 方法名通常大写;
context上下文对象, 迷你版的$store,actions中用到 ;
actions 中的方法,接收到两个参数:
第一个是context对象,这是一个min版的$store对象, 第二个参数是,传递过来的数据;
mutations 中的的方法,修改state中的数据, 接收到两个参数:
第一个是 state 对象,可以拿到state中的数据 ;第二个参数是 传递过来的数据;
getters 中的方法中的参数可以接收到state对象:对state中的数据经过加工后再使用, this.$store.getters.getter中的数据
state, 保存的数据; 读取this.$store.state.数据名

// 处理异步任务,业务逻辑,按顺序处理
const actions = {
    
	jia(context,value){
		
		context.commit('JIA',value)  // commit的方法名,通常大写
	},
}

// 处理同步任务,可以直接修改state中的数据 , mutations中的方法名,通常大写
const mutations = {
   
	JIA(state,value){
		
		state.sum += value
	}
}

//vux中的数据
const state = {
   sum:0
}
// 对state中的数据进一步加工
const getters = {
	bigSum(state){
		return state.sum * 10
	}
}

vuex的四个辅助函数函数;

// 引入vuex的辅助函数
import { mapActions, mapGetters, mapState, mapMutations } from "vuex";
...mapState和...mapGetters在computed中映射并解构出来值,然后使用;
...mapMutations和...mapActions在methods中映射并解构出来方法,然后调用方法;
  • …展开运算符
  • 数组写法,对象写法,普通调用方法;
  • 配合模块化,开启命名空间后,需要加模块化的名字;
  • 配合模块化,普通调用的方法;

mapState方法:用于帮助我们映射state中的数据为计算属性; 在computed配置项中;

 <p>学校名:{{ xuexiaoName }}</p>
computed: { // 依靠return返回值;
    //借助mapState生成计算属性:schoolName、subject(对象写法)
     ...mapState({xuexiaoName:'schoolName',subject:'subject'}), // 对象解构的重命名, 把schoolName重命名为xuexiaoName.
         
    //借助mapState生成计算属性:sum、school、subject(数组写法)
    ...mapState(['sum','schoolName','subject']),
    
    //等价与   
      xuexiaoName(){
      	return this.$store.state.schoolName 
    }, 
    ....                          
},

mapGetters方法:用于帮助我们映射getters中的数据为计算属性; 在computed配置项中;

<h4>计算属性:{{ bigSum }}</h4>

computed: { // 计算属性依靠返回值;
    //借助mapGetters生成计算属性:bigSum(对象写法)
    ...mapGetters({bigSum:'bigSum'}),

    //借助mapGetters生成计算属性:bigSum(数组写法)
    ...mapGetters(['bigSum'])
    
    //等价与
    bigSum() {
    	return this.$store.getters.bigSum
    },
    ...
},

mapActions方法:用于映射生成与actions对话的方法,即:包含$store.dispatch(xxx)的函数 ; 在methods配置项中;

1. 在methods中解构出来action中方法,在模板中直接调用这个方法并传参; 
 <button @click="incrementOdd(n)">++</button>
 <button @click="jiaOdd(n)">++</button>
methods:{ //methods配置项;
    //靠mapActions生成:incrementOdd、incrementWait(对象形式)
    ...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
  	//对象的解构并命名; methods配置项中的方法名=>incrementOdd;  action中的方法名=>jiaOdd
   
    //靠mapActions生成:incrementOdd、incrementWait(数组形式)
    ...mapActions(['jiaOdd','jiaWait'])
                         
}
2. 绑定事件时不传参, 在事件的处理函数中,调用映射过来的函数时传参;
<button @click="evenJia">++</button>
methods:{
    // 解构出来action中的方法, 调用传参
    ...mapActions(['jiaOdd'])
    evenJia(){
        this.jiaOdd(this.n)
    },
}

3.//等价与
methods:{
     incrementOdd() {
      // 通过dispatch调用action中的方法
      this.$store.dispatch("jiaOdd", this.n);
    },  
}

mapMutations方法:通过导入的mapMutations函数,将需要的mutations方法,映射为当前组件的methods的方法,然后调用这个方法,即:包含$store.commit(xxx)的函数, 在methods配置项中;

1.	<button @click="increment(n)">++</button>
<button @click="increment2">++</button>
methods:{ // methods配置项中
    //靠mapActions生成:increment、decrement(对象形式)
    ...mapMutations({increment:'JIA',decrement:'JIAN'}), //increment: method配置项中的方法名 ; JIA: mutation中的方法名  
    
    //靠mapMutations生成:JIA、JIAN(对象形式)
    ...mapMutations(['JIA','JIAN']),
    // 调用JIA方法并传参
    increment2(){
        this.JIA(n)    
    }

}

 - <button @click="evenJia">++</button>
methods:{
    // 解构出来, 调用传参
   ...mapActions(['increase'])
    evenJia(){
        this.increase(this.n)
    }
}
 - methods:{
    //等价与
    increment() {
      this.$store.commit("JIA", this.n);
    },
}

获取state中的数据: 1. this.$store.state.数据名 2. …mapState
获取getters中的数据: 1. this.$store.getters.计算属性名 2. …mapGetters
触发actions中的方法: 1. this.$store.dispatch("方法名", 参数) ; 2. …mapActions
触发mutations中的方法: 1. this.$store.commit("方法名",参数) 2. …mapMutations
mapActions与mapMutations使用时,若需要传递参数需要:在模板中绑定事件时传递好参数,否则参数是事件对象evnet。

模块化 + 命名空间

模块化vuex,可以让每一个模块拥有自己的state、mutation、action、getters,使得结构非常清晰,方便管理。
作用:让代码更好维护,让多种数据分类更加明确,每个组件各维护对应的数据;
namespaced:true; 开启命名空间
…mapXXX(“模块化名” , [ “xxx”] )
…mapXXX( “模块化名”, { “xxx”: “xxx” } )

创建关于count组件的模块
// counAbout
export default{
  namespaced:true,//开启命名空间
  state:{...},
  mutations: { ... },
  actions: { ... },
  getters: {
...}
}
创建关于person组件的模块
// personAbout 
export default{
  namespaced:true,//开启命名空间
  state:{...},
  mutations: { ... },
  actions: { ... },
  getters: {
...}
}
src/store/index.js文件
// 创建store对象
import Vue from "vue";
import Vuex from "vuex"
// 使用vuex
Vue.use(Vuex)
// 引入模块化的vuex
import countAbout from "./module/count.js";
import personAbout from "./module/person";
const store = new Vuex.Store({
  // 模块化
  modules: {
    countAbout,
    personAbout
  }
})
开启命名空间后,组件中读取state数据:
this.$store.state.moduleName.value
//方式一:自己直接读取; 从对应的模块中读取state中的数据

this.$store.state.personAbout.xxx;
//方式二:借助mapState读取:
computed: {
    ...mapState('countAbout',['sum','school','subject']), // 在computed中从对应的模块中读取state中的数据
}
开启命名空间后,组件中读取getters数据:
this.$store.getters['moduleName/getterName']
//方式一:自己直接读取 ; 从对应的模块读取getters中的数据
this.$store.getters['personAbout/xxx']
//方式二:借助mapGetters读取:
computed: {
    ...mapGetters('countAbout',['bigSum']) // 在computed中从对应的模块中读取getters中的数据
}
开启命名空间后,组件中调用dispatch触发actions中的方法: 
 this.$store.dispatch('moduleName/actionName',payload)
//方式一:自己直接dispatch; 明确从对应的模块中读取actions中方法;
this.$store.dispatch('personAbout/addPersonWang',person)
//方式二:借助mapActions:
 methods: {
    ...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'}) // 在methods中从对应的模块中读取actions中方法;
    ...mapActions('moduleName', ['actionName'])
}
开启命名空间后,组件中调用commit中的方法 : 
this.$store.commit('moduleName/mutationName')
//方式一:自己直接commit; 明确从对应的模块中读取mutations中方法;
this.$store.commit('personAbout/ADD_PERSON',person)
//方式二:借助mapMutations:
 methods: {

	...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}), //在methods中从对应的模块中读取mutations中方法;
   ...mapMutations('moduleName', ['mutationName'])
 }

vuex的缺点: 刷新浏览器,vuex中的state会重新变为初始状态; 因为vuex存储的数据在内存中,刷新消失;
解决方案:
1.使用插件vuex-persistedstate 让vuex的数据持久化;
2. 将vuex的数据进行本地存储,将vuex中的数据直接保存到浏览器缓存中(sessionStorage、localStorage、cookie);


SPA单页面应用

在 Web 页面初始化时加载相应的 html结构、JavaScript交互 和CSS样式。一旦页面加载完成,根据url地址栏的不同,来实现对应的路由组件的切换,整个页⾯是没有进⾏刷新的,只是组件与组件之间的卸载与装载的过程。

  • 原理:根据url地址栏的不同,来实现对应的路由组件的切换,整个页⾯是没有进⾏刷新的,只是组件与组件之间的卸载与挂载的过程。
  • 整个应用只有一个完整的页面, 所用的功能在一个html上实现。
  • 点击页面中的导航链接不会刷新页面,只会做页面的局部更新。
  • 数据需要通过 ajax 请求获取。
    vue2笔记_第57张图片

VueRouter 路由

  • 路径和组件的映射关系;

  • hash地址与组件之间的对应关系;

  • hash地址是#后面的内容(包括#);

一个路由(route)就是一组映射关系(key - value),路径和组件的展示关系;多个路由需要路由器(router)进行管理。
只有一个路由器router,每个组件都有的路由route; 全局有$router对象;且只有一个。

vue-router是基于路由和组件的;

路由用于设定访问路径,将路径和组件映射起来;

在vue-router的单页面应用中,页面的路径的改变就是组件的切换;

  • 后端路由:key是路径, value是函数, 用于处理客户端提交的请求。服务器接收到一个请求时, 根据请求路径找到匹配的函数,来处理请求, 返回响应数据。
  • 前端路由:key是路径,value是组件。当浏览器的路径改变时, 对应的组件就会显示。

路由组件通常存放在pages或views文件夹,一般组件通常存放在components文件夹。
通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载。
每个组件都有自己的$route属性,里面存储着当前组件的路由信息。
整个应用只有一个router,可以通过组件的this.$router属性获取到。
this.$router是路由的导航对象
this.$route是路由的参数对象


vueRouter的使用:

vue2笔记_第58张图片
vue2笔记_第59张图片


路由模块封装的基本使用:

安装vue-router,命令:npm i vue-router
编写router/index.js配置项: 创建router对象;

// 创建并暴露router对象;
//引入VueRouter
import VueRouter from 'vue-router' 

//使用VueRouter
 Vue.use(VueRouter)
//引入路由的组件
import About from '../components/About'
import Home from '../components/Home'

//创建router实例对象,去管理一组一组的路由规则
const router = new VueRouter({
	mode: 'hash', // 'history' 路由的工作模式;
 	routes:[ //路由规则是 routes ,一个数组; 
		{
			path:'/about', // path路径,带/
			component:About	 //引入的组件
		},
		{
			path:'/home',
			component:Home
		}
	]
})

//暴露router对象
export default router
在main.js引入,并挂载到vm上; 全局就有了$router
import Vue from 'vue'
import App from './App.vue'
import router from './router' // 引入创建的router对象
Vue.config.productionTip = false
new Vue({
  render: h => h(App),
  router, // 挂载router
}).$mount('#app')

实现切换(active-class可配置高亮样式)

active-class属性可以控制路径切换的时候对应的router-link渲染的dom添加的类名;
router-link自带激活时的高亮类名;

<router-link active-class="active" to="/about">About</router-link>
active-class 激活时的样式active样式, 点击后高亮; 

router-link的自带两个高亮类名:
vue2笔记_第60张图片

路由的自定义匹配类名:
vue2笔记_第61张图片

router-link:

进行跳转, 自带激活时的高亮类名;
该标签是一个vue-router中已经内置的组件,它会被渲染成一个<a>标签; tag属性渲染成其他标签;	
replace属性:
作用: 1.replace属性可以控制router-link的跳转不被记录;
      2.浏览器的历史记录有两种写入方式:分别为push和replace,push是追加历史记录,replace是替换当前记录。路由跳转时候默认为push;
      3.开启replace模式: < router-link  replace >  ==> < router-link  :replace="true" >

router-view

路由组件的展示位置;
<router-view></router-view> :指定组件展示的位置; 没有name, 默认default;
<router-view  name="componentsName">:该标签会根据当前的命名视图name,动态渲染出不同的组件;
切换路径时,切换的是<router-view>挂载的组件;

多级路由(路由的嵌套)配置:

在上级路由配置项中添加 children:[ { } ], 在children中配置自己的路由规则;
子级路由的path不要加 / ;
子级路由也要在父级路由组件中配置路由出口,
默认子路由, 子路由的path: " " ;

routes:[
 {	 // 一级路由
	path:'/home',
	component:Home,
 	redirect: '/home/mews', / 重定向到News路由组件 /
	children:[ //通过children配置子级路由
	   {	// 二级路由
		path:'news', // 子级路由不要加 /, => 渲染后/home/news ;
		component:News
      },
      / 默认子路由 /
      {
		path:'', // 子路由的path为空, 默认展示该子路由; => /home时就展示Title子路由组件;
		component:Title
      },
  }
]
跳转(要写完整路径): 从父子到子级具体的路径;   路由的路径,/ 开始 ; 
<router-link to="/home/news">News</router-link>
router-link 解析成a标签; to == href ;  tag解析成其他标签, tag="div',通过配置 tag 属性生成别的标签; 

路由的路径

单独的路由规则
 // 默认路由
    {
      path:"/",     / 根路径; /
      path:"*",    / 任意路径, 用作404页面,前面路径都不匹配就用这个,配置在路由的最后面; /
      redirect:"/home" , / 匹配到path后, 强制跳转到指定的path; /
      alias: "/about", / 别名 /
    },

命名路由

作用:可以简化路由的跳转。 给路由组件的配置项添加name属性 ;

routes:[
 {	 // 一级路由
	path:'/home',
 	name:'shouye',
	component:Home,
	children:[ //通过children配置子级路由
	   {	// 二级路由
		path:'news', //此处一定不要写/news; 子级路由不要加 / 
  		name: "xinwen", // 命名路由
		component:News
      },
  }
]
<!--简化前,需要写完整的路径 -->
<router-link to="/home/news">新闻</router-link>
<!--简化后,直接通过名字跳转 -->
<router-link :to="{name:'xinwen'}">新闻</router-link>   // name命名路由相当与path的路径; 
等价与
this.$router.push({name:"xinwen"})

命名视图

在components内配置;
如果 router-view 没有设置名字,那么默认为 default ;

<router-view></router-view>
<router-view name="a"></router-view>
<router-view name="b"></router-view>

// 一个视图使用一个组件渲染,因此对于同个路由,多个视图就需要多个组件。确保正确使用 components 配置;
const router = new VueRouter({
  routes: [
    {
      path: '/',
      components: {  //是components
        default: Foo, // 默认
        a: Bar,
        b: Baz
      }
    }
  ]
})

路由传参

路由传参:嵌套路由时父路由向子路由传递参数;
路由传参:父路由把数据传递给子路由;

query传参   ? key1=value1&key2=value2
类似于get请求
http://localhost:8080/#/home/?id=1&title=消息1
params传参    /xxx/xxx/xxx
类似于post请求, 将参数放在请求体中传递;
params传参需要配置path, 在path中用  : 占位 ; (动态路由)
url地址栏不显示参数名, 只会显示参数值;
params 传参是将参数直接嵌入到路由路径中,而不是作为查询字符串追加到 URL 上。
{
    path: '/home/:id/:title', // : 动态路由参数:占位;
    name:'home' // 命名路由
}
<router-link :to="{ name: 'home', params: { id: 1, title:'消息1' } }">home组件</router-link>
http://localhost:8080/#/home/1/消息1

vue2笔记_第62张图片

query传参

声明式

<router-link :to=`/home?id=${'hello'}&title=${'你好'}`>字符串写法</router-link>  //模板字符串写法 
<router-link 
:to="{ 
path:'/home', 
query:{ 
id:'hello',
title:'你好'
}
}"
>对象写法</router-link> // path写法
<router-link 
:to="{ 
name:'name', 
query:{ 
id:'hello',
title:'你好'
}
}"
>对象写法</router-link> // name命名路由写法
url地址栏解析为: /home?id=hello&title=你好
编程式
this.$router.push( `/home?id=${1}&title=${'hello'}`) // 模板字符串写法
this.$router.push({ path:"/home", query:{ id:1, title:"hello"} }) // path写法
this.$router.push({ name:"home", query:{ id:1, title:"hello"} }) // name命名路由写法
url地址栏解析为: /home?id=1&title=你好
读取参数, 借助组件的$route;
this.$route.query.xxx

params传参

通过路由属性中的name来确定匹配的路由,通过params来传递参数。
params传参, 需要占位符, 跳转路由,只能使用name命名路由, 不能使用path;
配置路由,声明接收params参数, : 占位符占位; (动态路由)
如果通过path跳转,params 会被忽略, paramas传参需要通过name命名路由来跳转;

接收参数的组件,配置路由
params传参,先要配置路由; path: " :占位符 "
const router = new VueRouter({
  routes: [
    {
       name:'news',
	    path:'news/:id/:title', //使用占位符声明接收params参数
	    component:News
      }
  ]
})
声明式
<router-link 
:to="{
name:'news, 
params:{
id:666,
title:'你好'
} 
}"
>跳转</router-link> // 通过name命名路由跳转
// 特别注意:路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置!
<router-link 
:to=`/news/&{666}/${'你好'}}}`
>跳转</router-link> // 通过模板字符串跳转
url地址栏解析为: /news/666/你好
编程式
this.$router.push({name:'news',params:{id:123,title:'hello'} })
this.$router.push(`/news/${123}/${'hello'}`)
url地址栏解析为: /news/123/hello

params读取参数, 借助$route;
this.$route.params.xxx

动态路由参数可选符:

路由有时候需要参数,有时候不需要参数,也希望匹配, 使用 ?
{
    path:'home/:id?',  / :参数? /
    component:()=>import('@/views/home'),
    name:'Home'
}

路由的props配置项

作用:让路由组件更方便的收到参数;
接收动态参数, 配置 path:’ /xxx/:xxx’, 例如: path: ‘/home/:id’;
谁接收参数,谁去配置props配置项;
在路由配置中配置props; 在组件中, 配置props,接收参数; prosp:[‘xxx’,‘xxx’]
路由关系中: prosp:{ } 对象形式;
props:true, 只能接收params参数, 所以要声明接收的是params参数, path要是用:占位符;
props:function($route){ return { } } , $route为参数, 依靠返回值;

接收参数的组件配置props:
    props:[ "id","a"]
//接收参数的组件==>路由配置关系
//第一种写法:props值为对象,该对象中所有的key-value的组合最终都会通过props传给组件
    props:{a:900}

//第二种写法:props值为布尔值,布尔值为true,则把路由收到的所有params参数通过props传给组件
    props:true; //只能使params传参;
    	
//第三种写法:props值为函数,该函数返回的对象中每一组key-value都会通过props传给路由组件
    props($route){ // props为函数时,可以接收到$route参数, 也可以解构赋值,连续解构赋值;
		return {
			id:$route.query.id,
			title:$route.params.id // 需要提前声明接收的是params参数;
		}
	}

声明式导航,编程式导航
实现路由的跳转

声明式导航

to, tag, replace

编程式导航
this.$router.push() //传参相当于router-link的to属性;
this.$router.replce()  //传参相当于router-link的to属性,跳转不被记录;
this.$router.back()  //后退,不用传参
this.$router.forward()  //前进,不用传参
this.$router.go()	 //传参数,正数前进,负数后退,0刷新;
编程式导航跳转
1.this.$router.push('路径')
2.this.$router.push({
    path:'路径'
})
3.this.$router.push({
   name:'路由名'
})
编程式导航跳转+传参-query
this.$router.push('/路径?参数名1=参数值1&参数名2=属性值2')
this.$router.push({
    path:'路径',或 name:'路由名',
    query:{
        参数名1:参数值1,
        参数名2:参数值2,
    }
})
编程式导航跳转+传参-params
首先需要配置动态路由 {path:'路径/:参数值', component:()=>import('@/文件路径'), name:'路由名'}
this.$router.push('/路径/参数值1/参数值2')
this.$router.push({
    name:'路由名', / 编程式导航跳转+params传参只能通过name跳转 /
    params:{
        参数名1:参数值1,
        参数名2:参数值2    
    }
})
<button @click="pushShow(n)">push</button>
  methods: { //methods中
    pushShow(n){ // 使用数据,调用传参的形式传递过来;
      this.$router.push({   //调用push方法, 传参相当于router-link的to属性;
        name:'xw',
        params:{id:n.id,news:n.new}
      })
    }
  },
  
  <button @click="replaceShow(m)">replace</button>
   methods: {
    replaceShow(m){
      this.$router.replace({  //调用replace方法
         name: 'msg',
            query: {
              id: m.id,
              title: m.title,
            }
      })
    }
  },

keep-alive 内置组件

缓存组件,不被销毁;
但被缓存的组件, 再次进入或离开不再执行组件的生命周期;
路由的跳转就是组件的销毁与加载;
作用:让不展示的组件保持挂载,不被销毁。
组件名 => 组件的name配置项;
缓存一个组件 include = “组件名”;
缓存多个组件 include = “组件名1, 组件名2,…” ; //逗号隔开
缓存多个组件 :include = “[ ‘xxx’, ‘xxx’]” // 数组名数组
不缓存某个组件,其他组件都缓存; exclude= “组件名”
不加include,默认包裹的组件及其子组件都被缓存;

 <keep-alive include="News">
      <router-view></router-view>
</keep-alive>

keep-alive的生命周期钩子

作用:路由组件所独有的两个钩子,用于捕获路由组件的激活和失活状态。
原因: 被缓存的组件,再次进入或离开不再执行生命周期函数;

  activated() { }, 路由组件被激活时触发。
  deactivated() { }, 路由组件销毁时触发。

vue2笔记_第63张图片
vue2笔记_第64张图片
vue2笔记_第65张图片

compnent 动态组件-内置组件

组件的占位, 渲染成指定的组件;
通过is属性指定要渲染的组件;
is属性的值是components配置项中的组件注册名;

<keep-alive>
    <component v-bind:is="componentName"></component>
 </keep-alive>
1.搭配<keep-alive>来实现动态组件的缓存;
2.可以通过v-bind:is动态指定显示那个组件;
3.'componentName'是components配置项中的注册组件名;

meta路由元信息

路由的配置项: 路由元信息,是一个对象;
在路由规则中配置这个路由组件独有的信息; meta:{title:“首页” } ;
是一个对象,获取: this.$route.meta.xxx

路由懒加载

当路由被访问时,才加载对应的组件,提高了首屏加载效率;
一打开就有的页面不需要路由懒加载;
在router文件的index.js文件中配置;
写成箭头函数的形式,按需加载;
import Film from './views/Film'
 {
    path: '/film',
    component: Film,
}
/ 替换成路由懒加载 /
*1.
const Film = () => import('../views/Film.vue')
 {
    path: '/film',
    component: Film,
}
*2.
 {
    path: '/film',
    component: () => import('../views/Film.vue'), // 箭头函数形式,按需加载
}

路由守卫

控制路由的访问权限;
路由钩子函数, 进入之前调用,离开之前调用;进行拦截或者其他操作;
作用:对路由进行权限控制;
当路由跳转前或跳转后、进入、离开某一个路由组件前、后,需要做某些操作,就可以使用路由钩子来监听路由的变化;
分类:全局守卫、独享守卫、组件内守卫;
参数: 是一个箭头函数, 这个箭头函数的参数, to from next;

  • to 将要访问的路由对象;
  • from 离开的路由对象;
  • next 调用next()方法放行;
 - next() 直接放行,正常跳转;
 - next('/路径')next({path: '/路径'}), 强制跳转到指定页面;
 - next(false), 不跳转,强制停留在当前页;
 - 不声明next形参,默认允许访问每一个路由;
 - 声明了next形参,必须调用next()函数,否则不允许访问任何一个路由;

全局路由守卫 : 在路由配置文件 index.js中配置;

router.beforeEach( ) 全局前置路由守卫; 页面初始化、路由切换之前调用;
router.afterEach( ) 全局后置路由守卫( 没有next方法); 页面初始化、路由切换之后调用;
router.beforeResolve( ) 全局解析守卫;

独享路由守卫: 在路由规则中配置; to参数的对象就是要配置的路由组件;

beforeEnter( ) ; 切换对应的组件之前调用;

组件内路由守卫: 配置在路由组件文件中的钩子;

beforeRouteEnter( ) ; 通过路由规则,进入该组件时被调用;
beforeRouteLeave( ) ; 通过路由规则,离开该组件时被调用;
beforeRouteUpdate( ); 在当前路由改变,同时该组件被复用时调用;

全局路由守卫:

//全局前置守卫:初始化时执行、每次路由切换前执行

router.beforeEach((to,from,next)=>{
if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
		if(localStorage.getItem('school') === 'atguigu'){ //权限控制的具体规则
			next() //放行
		}else{
			alert('暂无权限查看')
		}
}else{
	next() //放行
	}
})

//全局后置守卫:初始化时执行、每次路由切换后执行,没有next()
router.afterEach((to,from)=>{
	document.title = 'to.meta.title' || "vue"	
})

访问权限:
vue2笔记_第66张图片
vue2笔记_第67张图片

独享路由守卫 :

// 切换对应的组件之前调用;
routes: [
    {
      path: '/foo',
      component: Foo,
      //进入到Foo组件之前调用!
      beforeEnter: (to, from, next) => {
       	next()//放行
      }
    }
  ]

组件内路由守卫:

路由组件文件中的函数钩子,类似于生命周期函数;
//进入守卫:通过路由规则,进入该组件时被调用
beforeRouteEnter (to, from, next) {
    /不能获取组件实例 `this`, 因为当守卫执行前,组件实例还没被创建; /
},

//离开守卫:通过路由规则,离开该组件时被调用
beforeRouteLeave (to, from, next) {
    / 离开该组件的对应路由时调用,可以访问组件实例 `this`; /

},
 
beforeRouteUpdate(to, from, next) {
    / 在当前路由改变,但是该组件被复用时调用, 可以访问组件实例 `this`; /
    // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
 }

路由模式

创建router时; mode: ‘hash’ 或 ‘history’

hash模式(前端路由):默认hash模式;
hash 改变会触发 hashchange 事件;
hash模式原理: 调用了window.onhashchange方法监听 hash值的切换;
hash模式背后的原理是onhashchange事件,可以在window对象上监听这个事件,由于 hash值变化不会导致浏览器向服务器发出请求,而且 hash 改变会触发 hashchange 事件(hashchange只能改变 # 后面的url片段);
更关键的一点是,因为hash发生变化的url都会被浏览器记录下来;

mode: 'hash' 
地址中永远带着#号,不美观 。
若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。
兼容性较好。

history模式的原理: 本质使用H5的histroy.pushState方法来更改url,不会引起刷新。

mode: 'history'
地址干净,美观 ,不带#号。
兼容性和hash模式相比略差。
上线服务器后,刷新页面服务端404的问题,应用部署上线时需要后端人员配置,将所有访问都指向index.html;

对于一个url来说,什么是hash值?—— #及其后面的内容就是hash值。
hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器。
hash模式下,仅hash符号之前的内容会被包含在请求中。
history模式下,前端的url必须和实际向后端发起请求的url 一致。
hash值
vue2笔记_第68张图片

你可能感兴趣的:(Vue,vue.js,前端)