Vue2.X(迈向Vue3的基石)

748 阅读25分钟

费劲时间,呕心沥血,打了好久的,求求点个赞吧!

Vue的特点和优势

vue两大特点响应式编程组件化

vue的优势:轻量级框架、简单易学、双向数据绑定组件化、数据和结构的分离、虚拟DOM、运行速度快、后期维护简单。

vue是单页面应用,使页面局部刷新,不用每次跳转页面都要请求所有数据和dom,这样大大加快了访问速度和提升用户体验。而且他的第三方ui库很多节省开发时间。

Vue 2.x

基本框架及原理:

创建配置Vue实例

  1. 想让Vue工作,就必须创建一个Vue实例,且要传入一个配置对象(opotios);
  2. root容器里的代码依然符合html规范,只不过混入了一些特殊的Vue语法;
  3. root容器里的代码被称为【Vue模板】;
  4. Vue实例和容器是一一对应的;
  5. 真实开发中只有一个Vue实例,并且会配合着组件一起使用;
  6. {{xxx}}中的xxx要写js表达式,且xxx可以自动读取到data中的所有属性;
  7. 一旦data中的数据发生改变,那么页面中用到该数据的地方也会自

☆实例就是VM,由Vue构造函数生成,就有全部的property,Vm身上所有的属性及原型上的属性都可以直接使用。

☆配置项中常用的this就是指VM实例,所以由Vue所管理的函数不要用箭头函数,防止this穿透(windows)

    new Vue({ //创建Vue实例
	el:'#root', //el用于指定当前Vue实例为哪个容器服务,值通常为css选择器字符串。
	data:{ //data中用于存储数据,数据供el所指定的容器去使用,
                   // 将data写成回调函数的原因  
                return{
                  name: "张三"
                }                   
	 }
})

el与data的两种写法

1.el有2种写法

    (1).new Vue时候配置el属性。
    (2).先创建Vue实例,随后再通过vm.$mount('#root')指定el的值(v.$mount('#root'))。

2.data有2种写法

    (1).对象式
    (2).函数式

在组件实例中,data必须使用函数式,否则会报错。

MVVM模型

MVVM模型

  1. M:模型(Model) :data中的数据

  2. V:视图(View) :模板代码

  3. VM:视图模型(ViewModel):Vue实例 观察发现

  4. data中所有的属性,最后都出现在了vm身上。

  5. vm身上所有的属性 及 Vue原型上所有属性,在Vue模板中都可以直接使用,用{{ }}获取的值实际上都是vue实例和原型上的。

数据劫持、数据代理、数据响应式、数据双向绑定

数据劫持:

当我们访问或设置对象的属性的时候,都会触发相对应的函数,然后在这个函数里返回或设置属性的值。既然如此,我们当然可以在触发函数的时候动一些手脚做点我们自己想做的事情,这也就是 “劫持”操作。 在Vue中其实就是通过Object.defineProperty来劫持对象属性的setter和getter操作,并 “种下”一个监听器,当数据发生变化的时候发出通知。

数据响应式:

script中的数据驱动html的内容发生改变,不需要去操作DOM(用指令与插值表达式进行数据绑定时默认就是响应式的,也就是在改数据的时候,页面内容会自己发生变化)

  • vuedata初始化为一个Observer并对对象中的每个值,重写了其中的getsetdata中的每个key,都有一个独立的依赖收集器。

  • get中,向依赖收集器添加了监听

  • 在mount时,实例了一个Watcher,将收集器的目标指向了当前Watcher

  • data值发生变更时,触发set,触发了依赖收集器中的所有监听的更新,来触发Watcher.update

数据的双向绑定:

主要体现在用户输入域:用户输入域影响数据的改变,数据改变又影响页面内容的变化

数据代理

Object.defineproperty方法

                let number = 18
		let person = {
			name:'张三',
			sex:'男',
		}
    Object.defineProperty(person,'age',{
                            // value指定age的值。固定的
			// enumerable:true, //控制属性是否可以枚举,默认值是false
			// writable:true, //控制属性是否可以被修改,默认值是false
			// configurable:true //控制属性是否可以被删除,默认值是false
			//当有人读取person的age属性时,get函数(getter)就会被调用,且返回值就是age的值
			get(){
				console.log('有人读取age属性了')
				return number
			},

			//当有人修改person的age属性时,set函数(setter)就会被调用,且会收到修改的具体值
			set(value){
				console.log('有人修改了age属性,且值是',value)
				number = value
			}
		})

Vue中的数据代理

  1. Vue中的数据代理:通过vm对象来代理data对象中属性的操作(读/写)
  2. Vue中数据代理的好处:更加方便的操作data中的数据
  3. 基本原理:
  • 通过Object.defineProperty()把data对象中所有属性添加到vm上。
  • 每一个添加到vm上的属性,都指定一个getter/setter。
  • 使用get return回data中的值,所以模板dom能够获取到data的值。
  • 使用set 将模板dom改掉的值赋值给data,实现了对data的修改。

data在vm上的属性 : vm_data = opotios.data

把vm的属性和data中的属性联系在了一起 _data中的不是数据代理,是数据劫持,即响应式

☆Vue监视数据的原理:

  1. vue会监视data中所有层次的数据。
  2. 如何监测对象中的数据?
let data = {
        name: "shangguigu",
        address: "beijing"
    }
    // 创建一个监视的实例对象,用于监视data中属性的变化
    const obs = new Observer(data)
    console.log(obs);

    // 准备一个vm实例对象
    let vm = {}
    vm._data = data = obs

    function Observer(obj) {
        //汇总对象中所有的属性形成一个数组
        const keys = Object.keys(obj);
        //遍历数组
        keys.forEach((k) => {
            Object.defineProperty(this, k, {
                get() {
                    return obj[k]   //把传入对象的k对应的值传出去
                },
                set(val) {
                    console.log(`${k}被改了,要去解析模板,生成虚拟dom...,`);
                    obj[k] = val    //收到new val,改传入对象的k对应的属性改掉

                }
            })
        })
    }


通过setter实现监视,且要在new Vue时就传入要监测的数据。

  • 对象中后追加的属性,Vue默认不做响应式处理
  • 如需给后添加的属性做响应式,请使用如下API: Vue.set(target,propertyName/index,value) 或 vm.$set(target,propertyName/index,value)
  • 如需删除某个响应式对象属性 Vue.delete(target,propertyName/index) 或 vm.$delete(target,propertyName/index)

注:Vue 是从node_modules中引入的Vue, vm是指当前实例。

☆Vue的弊端:

  1. data新增属性,删除属性页面不会更新
  • Vue.set() 或 vm.$set(),vm.$delete 或 Vue.delete
  1. data中的数据如果缺少属性,后期新添加的属性不是响应式
  2. Vue界面中不显示underfind
  3. 直接根据索引修改数组,界面不会自动更新。 在Vue修改数组中的某个元素一定要用如下方法,都是经过VUE 加工后的 方法,并不是JS的原生方法:
  • 使用这些API:push()、pop()、shift()、unshift()、splice()、sort()、reverse() 通过包裹数组更新元素的方法实现,本质就是做了两件事: (1).调用原生对应的方法对数组进行更新。 (2).重新解析模板,进而更新页面。

特别注意:Vue.set() 和 vm.$set() 不能给vm 或 vm的根数据对象 添加属性!!!

methods事件处理

事件的基本使用(挺重要的):

  1. 使用v-on:xxx 或 @xxx 绑定事件,其中xxx是事件名;
  2. 事件的回调需要配置在methods对象中,最终会在vm上;
  3. methods中配置的函数,不要用箭头函数!否则this就不是vm了;
  4. methods中配置的函数,都是被Vue所管理的函数,this的指向是vm 或 组件实例对象;
  5. @click="demo" 和 @click="demo(event)"效果一致(前面不写event)" 效果一致(前面不写event也会默认传入),但后者可以传参;

☆如何获取事件和事件对象

  1. ref
  2. $event参数

事件: $event为事件,点击还是双击;

事件源(dom):e.target 是你当前点击的元素; e.currentTarget 是你绑定事件的元素; blog.csdn.net/weixin_3067… 注:js中事件是会冒泡的,所以this是可以变化的,但event.target不会变化,它永远是直接接受事件的目标DOM元素;

Vue中的事件修饰符:

  1. prevent:阻止默认事件(常用);
  2. stop:阻止事件冒泡(常用);
  3. once:事件只触发一次(常用);
  4. capture:使用事件的捕获模式;
  5. self:只有event.target是当前操作的元素时才触发事件;
  6. passive:事件的默认行为立即执行,无需等待事件回调执行完毕;
  7. native: 在组件名上用原生事件时需要,否则事件不触发;

键盘事件

  1. Vue中常用的按键别名:
回车 => enter
删除 => delete (捕获“删除”和“退格”键)
退出 => esc
空格 => space
换行 => ta须配合keydown去使用)
上 => up
下 => down
左 => left
右 => right
  1. Vue未提供别名的按键,可以使用按键原始的key值去绑定,但注意要转为kebab-case(短横线命名)
  2. 系统修饰键(用法特殊):ctrl、alt、shift、meta
  • 配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发。
  • 配合keydown使用:正常触发事件。
  1. 也可以使用keyCode去指定具体的按键(不推荐)
  2. Vue.config.keyCodes.自定义键名 = 键码,可以去定制按键别名

computed计算属性

computed: 根据已有属性,生成的新的属性

计算属性在开始时就会调用

计算属性:

  1. 定义:要用的属性不存在,要通过已有属性计算得来。
  2. 原理:底层借助了Objcet.defineproperty方法提供的getter和setter。
  3. get函数什么时候执行?
  • 初次读取时会执行一次。
  • 当依赖的数据发生改变时会被再次调用。
  1. 优势:与methods实现相比(methods方法实现会重新解析模板,多次调用方法),内部有缓存机制(复用),效率更高,调试方便。
  2. 备注:
  • 计算属性最终会出现在vm上,直接读取使用即可。
  • 如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生改变。
computed:{
	fullName:{
		//get有什么作用?当有人读取fullName时,get就会被调用,且返回值就作为fullName的值
		//get什么时候调用?1.初次读取fullName时。2.所依赖的数据发生变化时。
		get(){
			console.log('get被调用了')
			// console.log(this) //此处的this是vm
			return this.firstName + '-' + this.lastName
		},
		//set什么时候调用? 当fullName被修改时。
		set(value){
			console.log('set',value)
			const arr = value.split('-')
			this.firstName = arr[0]
			this.lastName = arr[1]
		}
	}
}

格式

				 fullName:{
					get(){
						console.log('get被调用了')
						return this.firstName + '-' + this.lastName
					},
					set(value){
						console.log('set',value)
						const arr = value.split('-')
						this.firstName = arr[0]
						this.lastName = arr[1]
					}
				} 
   简写:只读不改的情况下,计算属性后 的函数即为get函数。
				fullName(){
					console.log('get被调用了')
					return this.firstName + '-' + this.lastName
				}

watch监听属性

watch:

  1. 当被监视的属性变化时, 回调函数自动调用, 进行相关操作
  2. Vue中的watch默认不监测对象内部值的改变(一层)
  3. 监视的两种写法: (1).new Vue时传入watch配置 (2).通过vm.$watch监视

方式一:new Vue时传入watch配置

 watch:{
         real:{ //如果想监听real 中的某个key,需要加引号: "real.key"
           immediate:true,   // 是否一加载组件就执行一遍
           deep: true,       // 是否进行深度监视,Vue中的watch默认不监测对象内部值的改变(一层)。
           handler(new,old){  //参数为real的新值,旧值
             console.log(new,old)  // 执行想要执行的操作
           }
         }

简写:不需要立即执行和深度监听的情况下

watch:{
    real(newValue,oldValue){  
			   console.log('isHot被修改了',newValue,oldValue,this)
                           } 
}

方式二:vm.$watch监视

vm.$watch('isHot',{
			immediate:true, //初始化时让handler调用一下
			deep:true,//深度监视
			handler(newValue,oldValue){
				console.log('isHot被修改了',newValue,oldValue)
			}
		}) 

简写

vm.$watch('isHot',(newValue,oldValue)=>{
			console.log('isHot被修改了',newValue,oldValue,this)
		}) 

备注:

computed和watch之间的区别:

  1. computed能完成的功能,watch都可以完成。
  2. watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作。 两个重要的小原则:
  3. 所被Vue管理的函数,最好写成普通函数,这样this的指向才是vm 或 组件实例对象。
  4. 所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数等、Promise的回调函数),最好写成箭头函数,这样this的指向才是vm 或 组

模板语法(HTML)

分类(插值与指令)

  1. 插值语法: 功能:用于解析标签体内容。

写法:{{xxx}},xxx是js表达式,且可以直接读取到data中的所有属性。

  1. 指令语法: 功能:用于解析标签(包括:标签属性、标签体内容、绑定事件.....)。

举例:v-bind:href="xxx" 或 简写为 :href="xxx",xxx同样要写js表达式,且可以直接读取到data中的所有属性。

插值语法:{{}}

  1. {{xxx}}中的xxx要写js表达式,且xxx可以自动读取到data中的所有属性;
  2. 一旦data中的数据发生改变,那么页面中用到该数据的地方也会自动更新; ☆注意区分:js表达式 和 js代码(语句)
  3. 表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方:
(1). a
(2). a+b
(3). demo(1)
(4). x === y ? 'a' : 'b'

2.js代码(语句)

(1). if(){}
(2). for(){}

绑定样式

  1. 动态class样式
  • :class="xxx" xxx可以是字符串、对象、数组。
  • 字符串写法适用于:类名(多个类名选一个)
  • 对象写法适用于:要绑定多个样式,个数不确定,名字也不确定。
  • 数组写法适用于:要绑定多个样式,个数确定,名字也确定,但不确定用不用。
  1. 动态style样式
  • :style="{fontSize: xxx}"其中xxx是动态值。
  • :style="[a,b]"其中a、b是样式对象。

条件渲染

  1. v-if=‘表达式’
  2. v-show=‘表达式’

列表渲染

v-for指令:

  1. 用于展示列表数据
  2. 语法:v-for="(item, index) in xxx" :key="yyy"
  3. 可遍历:数组、对象、字符串(用的很少)、指定次数

☆面试题:react、vue中的key有什么作用?(key的内部原理)

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

收集表单数据(v-model)

type不同的input:

若:<input type="text"/>,则v-model收集的是value值,用户输入的就是value值。

若:<input type="radio"/>,则v-model收集的是value值,且要给标签配置value值。

若:<input type="checkbox"/>

  1. 没有配置input的value属性,那么收集的就是checked(勾选 or 未勾选,是布尔值)
  2. 配置input的value属性:
  • v-model的初始值是非数组,那么收集的就是checked(勾选 or 未勾选,是布尔值)
  • v-model的初始值是数组,那么收集的的就是value组成的数组 备注:v-model的三个修饰符:
  • lazy:失去焦点再收集数据
  • number:输入字符串转为有效的数字
  • trim:输入首尾空格过滤
<form @submit.prevent="demo">
				账号:<input type="text" v-model.trim="userInfo.account"> <br/><br/>
				密码:<input type="password" v-model="userInfo.password"> <br/><br/>
				年龄:<input type="number" v-model.number="userInfo.age"> <br/><br/>
				性别:
				男<input type="radio" name="sex" v-model="userInfo.sex" value="male">
				女<input type="radio" name="sex" v-model="userInfo.sex" value="female"> <br/><br/>
				爱好:
				学习<input type="checkbox" v-model="userInfo.hobby" value="study">
				打游戏<input type="checkbox" v-model="userInfo.hobby" value="game">
				吃饭<input type="checkbox" v-model="userInfo.hobby" value="eat">
				<br/><br/>
				所属校区
				<select v-model="userInfo.city">
					<option value="">请选择校区</option>
					<option value="beijing">北京</option>
					<option value="shanghai">上海</option>
					<option value="shenzhen">深圳</option>
					<option value="wuhan">武汉</option>
				</select>
				<br/><br/>
				其他信息:
				<textarea v-model.lazy="userInfo.other"></textarea> <br/><br/>
				<input type="checkbox" v-model="userInfo.agree">阅读并接受<a href="http://www.atguigu.com">《用户协议》</a>
				<button>提交</button>
			</form>

内置指令

v-text指令:
  1. 作用:向其所在的节点中渲染文本内容。
  2. 与插值语法的区别:v-text会替换掉节点中的内容,{{xx}}则不会。
v-html指令:
  1. 作用:向指定节点中渲染包含html结构的内容。
  2. 与插值语法的区别:

(1).v-html会替换掉节点中所有的内容,{{xx}}则不会。 (2).v-html可以识别html结构。

  1. 严重注意:v-html有安全性问题!!!!

(1).在网站上动态渲染任意HTML是非常危险的,容易导致XSS攻击。 (2).一定要在可信的内容上使用v-html,永不要用在用户提交的内容上!

v-cloak指令(没有值):
  1. 本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性。
  2. 使用css配合v-cloak可以解决网速慢时页面展示出{{xxx}}的问题。
v-once指令:
  1. v-once所在节点在初次动态渲染后,就视为静态内容了。
  2. 以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能。
v-pre指令:
  1. 跳过其所在节点的编译过程(包括子节点)。
  2. 可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译。

Vue.filter()过滤器(vue3放弃)

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

语法:

  1. 注册过滤器:Vue.filter(name,callback) 或 new Vue{filters:{}}
  2. 使用过滤器:{{ xxx | 过滤器名}} 或 v-bind:属性 = "xxx | 过滤器名" 备注:
  3. 过滤器也可以接收额外参数、多个过滤器也可以串联
  4. 并没有改变原本的数据, 是产生新的对应的数据 全局过滤器
 vue.filters(‘过滤器的名字’,function(需要处理的数据){
                 console.log()         //处理数据
 }

局部过滤器:类似于computed配置项的样子

	filters:{
				过滤器名字(value,str='YYYY年MM月DD日 HH:mm:ss'){  //参数
					// console.log('@',value)
					return dayjs(value).format(str)
				}
			}

directives()自定义指令

一、定义语法:

局部指令: new Vue({directives:{指令名:配置对象或回调函数} })

全局指令:Vue.directive(指令名,配置对象) 或 Vue.directive(指令名,回调函数)

二、配置对象中常用的3个回调:

  1. bind:指令与元素成功绑定时调用。
  2. inserted:指令所在元素被插入页面时调用。
  3. update:指令所在模板结构被重新解析时调用。

三、备注:

  1. 指令定义时不加v-,但使用时要加v-;
  2. 指令名如果是多个单词,要使用kebab-case命名方式,不要用camelCase命名。 全局指令
参数element(节点),binding(是一个对象,包括指令名,绑定的value等)
Vue.directive('fbind',{
			//指令与元素成功绑定时(一上来)
			bind(element,binding){
				element.value = binding.value
			},
			//指令所在元素被插入页面时, 默认回调函数
			inserted(element,binding){
				element.focus()
			},
			//指令所在的模板被重新解析时
			update(element,binding){
				element.value = binding.value
			}
		}) 

生命周期

8大生命周期 + 3个其他周期(nexTick, activated,deactivated)

常用的生命周期钩子:

  1. mounted: 发送ajax请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】。
  2. beforeDestroy: 清除定时器、解绑自定义事件、取消订阅消息等【收关于销毁Vue实例
  • 销毁后借助Vue开发者工具看不到任何信息。
  • 销毁后自定义事件会失效,但原生DOM事件依然有效。
  • 一般不会在beforeDestroy操作数据,因为即便操作数据,也不会再触发更新流程了。

beforemount: 已经创建了DOM,但没经过Vue解析编译,全是template中写的原始DOM,页面没显示的原因是没来的及放,间太快。在此 生命周期对DOM进行操作白费,因为还需要Vue进行编译 在created 和 beforemount之间进行的操作(模板的选择 root ? teamplate) $el为模板,包含全部的标签及内容,是真实DOM,且为上一版本的真实DOM

beforeupdate: 数据是新的,页面是旧的,页面数据未保持同步。 beforeupdate 和 updated 之间: 生成新的虚拟DOM 和 旧的虚拟 DOM进行对比,最终完成页面的更新,即:完成了Model => View 的更新 updated: 数据页面同步

☆模块化和组件化

模块化:将实现不同功能的js分别封装

组件化:界面的不同功能块进行封装(HTML + CSS + js)

非单文件组件

template属性: 配置组件的HTML结构

☆data为什么要写成函数形式(指针指向问题):

多个位置使用该组件时,对其data中的数据进行修改,如果写的是对象形式,则影响全部使用该组件的位置, 写成函数形式:函数有单独的作用域,不用担心一处地方的修改影响到其他使用该组件的地方

组件

组件使用的步骤: 创建组件 => 引入组件 => 注册组件 => template中使用 组件的本质: 构造函数

VueComponent(vc)的实例对象和vue的实例对象:vue的操作: 将vuecomponent的原型对象 指向了 vue的原型对象

引入的为残缺Vue : render的作用

@vue-cli中使用Vue

ref(获取DOM元素)

在标签上打ref可以在实例上拿到

组件上写ref,this.$ref拿的是组件的实例对象(所以可以实现父组件获取子组件的数据,也可修改)

ref是被用来给元素或子组件注册引用信息。引用信息将注册在父组件的 $refs 对象身上。如果在普通的DOM元素身上使用,引用指向就是DOM元素;如果用在子组件身上,引用就是指向组件实例

当v-for用于元素或组件身上时,引用循序就是将包含DOM节点或组件实例的数组。

关于ref注册时间的重要说明:因为ref本身就是作为渲染结果被创建的,在初始渲染的时候不能被范文-他们还不存在! $ref也不是响应式的,因此你不应该试图它用在模板中做数据绑定。

props通信(父传子)

传(可传数据也可传方法): 在父组件中使用的子组件上用v-bind绑定的方式传

接收:

  1. 简单声明接受:props:['name','age','sex']
  2. 接收的同时对数据进行类型限制
props:{
	                name:String,
			age:Number,
			sex:String
		}
  1. 接收的同时对数据:进行类型限制+默认值的指定+必要性的限制
		props:{
			name:{
				type:String, //name的类型是字符串
				required:true, //name是必要的
			},
			age:{
				type:Number,
				default:99 //默认值
			},
			sex:{
				type:String,
				required:true
			}
		}

不修改props传进来的数据,因为数据不藏在对象中时易报错

mixin混入

定义: 建立一个公共的options对象,可在别的组件中导入使用

使用:

  1. 定义一个mixin.js的文件, 配置想要的options对象(可配置多个,用具名导出)
  2. 在入口文件,添加到Vue构造函数上 Vue.mixin(options名)
  3. 在想要使用的组件中进行引入
  4. 在组件的options中配置mixin属性
mixins:[hunhe,hunhe2]
  1. mixins中的mounted生命周期仍旧执行,且可以在应用mixins的组件中使用mixins的一切。

plugin插件

定义:生成install函数,得到Vue构造函数,在install函数中对Vue构造函数和原型进行操作(向原型或构造函数上挂载方法或组件),减少了入口文件的代码

使用:

  1. 定义plugin.js文件,在文件中生成 install函数,函数的参数为Vue构造函数,以及传入的参数
  2. 配置install函数,通常配置全局方法或属性 ``
  3. 在入口文件,添加到Vue构造函数上,Vue.use(plugins,想传入的参数)
export default {
	install(Vue,x,y,z){
		console.log(x,y,z)
		//全局过滤器
		Vue.filter('mySlice',function(value){
			return value.slice(0,4)
		})

		//定义全局指令
		Vue.directive('fbind',{
			//指令与元素成功绑定时(一上来)
			bind(element,binding){
				element.value = binding.value
			},
			//指令所在元素被插入页面时
			inserted(element,binding){
				element.focus()
			},
			//指令所在的模板被重新解析时
			update(element,binding){
				element.value = binding.value
			}
		})

		//定义混入
		Vue.mixin({
			data() {
				return {
					x:100,
					y:200
				}
			},
		})

		//给Vue原型上添加一个方法(vm和vc就都能用了)
		Vue.prototype.hello = ()=>{alert('你好啊')}
	}
}

☆挂载

use的使用: vue.use( xxx ) 是挂载 用 vue 的函数编写的 方法/命令/模板/插件 等函数,供框架使用
被引入的 js 文件 必暴露 install 方法, 需要使用Vue构造函数的东西

vue.prototype.$xxx: 为了挂载任何想在全局频繁调用的 js 函数/对象

localStorage 和 sessionStorage(cookie另外再说)

localStorage使用(sessionStorage同样):

存储信息: localStorage.setitem('key',value(最好进行JSON处理)) 获取信息: localStorage。getItem('key');

image.png

组件自定义事件(emit,on)

$on:监听被绑定组件上的自定义事件。 事件可以由vm.$emit触发。回调函数会接收所有传入事件触发函数的额外参数。

$emit:在子组件中写,触发父组件绑定的自定义事件。 子组件可以使用$emit调用父组件给子组件绑定的方法并给父组件传递数据

使用:适用于子组件给父组件传参

子组件:**方法名不能为驼峰**
this.$emit('demo(绑定的事件名不是方法名)', value(-想要传递的参数))
父组件: 在使用子组件的标签上
@demo(自定义的事件)='HandleDemo(-想要触发的父组件的方法名)'

$on是用来在监听(注册)自定义事件的($on就不限于父子之间了,但必须和$emit统一实例, 即$emit触发和$on监听自定义事件必须为一个组件)

使用:

this.$refs.child.$on('atguigu'(自定义的事件),this.getStudentName(methods中定义的方法))
this.$refs.child.$on('atguigu',() =>(value(传过来的值)){})
  • atguigu:为监听的自定义事件
  • this.getStudentName:为父组件要调用的函数,也可为回调函数,但是回调函数中的this不指向当前实例(父组件),而是子组件(被绑定事件的组件实例,被触发者),写成箭头函数可以让this指向当前实例。
  • 函数的参数为传的值 this.$refs.child.$once('atguigu',this.getStudentName) //绑定自定义事件(一次性)

备注:使用自定义函数需要及时销毁,在被绑定的组件中调用($off),或者调用生命周期函数destroy,在调用这个生命周期函数后,自定义事件将会解绑,但不影响原生事件。

全局事件总线

定义: 便于全部组件之间的通信,利用$emit 和 $on,利用的是将VM自己挂载到自己构造函数的原型上,这样就可以全局调相同实例的方法了

使用:

//创建
new Vue({
	el:'#app',
	render: h => h(App),
	beforeCreate() {
		Vue.prototype.$bus = this //安装全局事件总线
	},
})
在组件A中发送
  this.$bus.$emit("demo", this.name)
在组件B中接收
  this.$bus.$on("demo", B组件的方法或者是回调,参数为传过来的值)

备注: 其实也可以直接新建一个js文件,new 一个新的Vue实例,将这个Vue实例作为工具导出使用,这样的话$emit和$on也算是挂载到一个实例上了。

消息的订阅发布(用的不多)

定义:实现全局通信 ,需要pubsub-js安装包,其实也不算是Vue的东西,只是封装好的一个包

使用:

发送消息:
import pubsub from 'pubsub-js';

pubsub.publish('hello'(-发送的消息名,用于被接收),666(-发送的数据))

接收消息:
import pubsub from 'pubsub-js';

			this.pubId(销毁时需要) = pubsub.subscribe('hello'(-想接收的消息名),
                        (msgName(-想接受的消息名),data(传过来的数据))=>{
				console.log(this)   //这个this指向不对,为undefined
			
			})
销毁:
    publish.unsubscribe(this.pubId)

☆代理服务器

解决跨域的方式:

  1. cors(后端)
  2. jsonp(前后端):利用script标签,但只能解决get请求
  3. 代理服务器(前端)
开启代理服务器(方式一): 只开一个
devServer: {
    proxy: 'http://localhost:5000'
  }, 
开启代理服务器(方式二): 开启多个
	devServer: {
    proxy: {
      '/atguigu': {
        target: 'http://localhost:5000',
	pathRewrite:{'^/atguigu':''},
        // ws: true, //用于支持websocket
        // changeOrigin: true //用于控制请求头中的host值
      },
      '/demo': {
        target: 'http://localhost:5001',
	pathRewrite:{'^/demo':''},
        // ws: true, //用于支持websocket
        // changeOrigin: true //用于控制请求头中的host值
      }
    }
  }

插槽

定义:从父组件中向子组件中放东西,不放东西时出现默认值,现在用v-solt + template方式,solt方式已经被废弃(vue2.60)

默认插槽:

父组件:
<Category>
	<img src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" alt="">
</Category>
子组件:Category中
<div class="category">
     <h3>{{title}}分类</h3>
     <!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) -->
     <slot>我是一些默认值,当使用者没有传递具体结构时,我会出现</slot>
</div>

具名插槽:可以精准的插入子组件指定的位置(v-solt 的缩写形式 #)

父组件:新版写法 +  旧版写法
<Category>
     新版
    <template v-solt:center>  
        <img  src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" alt=""> 
    </template>
    
     旧版
    <a slot="footer" href="http://www.atguigu.com">更多美食</a>
</Category>
子组件:
<solt name= 'center'> </solt>
<solt name= 'footer'> </solt>

作用域插槽: 想在父组件使用子组件中的数据(在父组件中解析完再向子组件中插入)

父组件:
方法1:
<Category>
     <template scope="games">  // 名字随便取:不一定要叫games, 取到子组件传过来的数据,并且外部包裹上一个对象。 **可直接用解构获取到真正的数据{games}**
           <h4 v-for="(g,index) in games" :key="index">{{g}}</h4>
     </template>
</Category>
方法2:
<Category>
     <template solt-scope="{games}">  // 新的
           <h4 v-for="(g,index) in games" :key="index">{{g}}</h4>
     </template>
</Category>
方法3: 最新的api, v-solt要在template中使用
<Category>
     <template v-solt:defalut="games">  // 可以简写为 v-solt="games"
           <h4 v-for="(g,index) in games" :key="index">{{g}}</h4>
     </template>
</Category>
子组件:
<solt  :games=games></solt>

vuex

定义:Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

基本配置项

import Vue from 'vue';
import vuex from 'vuex';

Vue.use(vuex)   // vuex需要在store挂载之前引入use

const store = new vuex.Store({
     // 存储数据,类似于options中的data
    state:{
     
    },
    
    //mutations——存放方法函数,用于操作数据(state)
    mutations:{
       // 参数1:state为上面存储数据的对象
       // 参数2:value为vue实例调用对象传的参数或action传的参数
       注意:
       // 方法名 _ 或 大写 为了和action中区分
       // 只有mutations才能修改state中的数据,但是在mutations配置项中的函数不能进行异步操作(ajax、计时器)
       _fun(state, value){   
       
       }
    },
    
    //actions——存放方法函数,用于执行mutations不能执行的异步操作,并调用mutations中的方法达
    到修改state数据的目的。
    actions:{
        //参数1:context为上下文,**具有store实例相同的属性,但不是store实例 **  
        //参数2:value为vue实例调用时传进来的参数
        fun(context,value){
            // 可直接结构获取想要的属性,通常使用commit调用mutations中的方法
            context:{
                state,   等同于store.$state,若在模块中则为局部状态
                rootState,   等同于store.$state,只存在模块中
                commit,   等同于store.$commit
                dispatch,   等同于store.$dispatch
                getters   等同于store.$getters
                    }
        }
    },
    
    //modules —— 用于注册子仓库,和component一样
    modules:{
    
    },
    
    //getters——仓库中的计算属性
    getters:{
      // 参数为state,便于计算,需要用return的方式返回
        bigSum(state){
		return state.sum*10
	}
    },
    
    //子仓库的专属配置,给仓库取名字
    namespaced: '名字'
})
    
 

在vue中的使用

挂载store

在入口文件引入配置好的store

将store添加到VM的身上

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

调用store的方法

state:因为state中的数据是响应式,所以在Vue中使用该数据时,需要放在computed计算属性中

this.$store.state.数据名

辅助函数: mapState(也是写到computed属性中)

   借助mapState生成计算属性,从state中读取数据。(对象写法)
...mapState({he:'sum',xuexiao:'school',xueke:'subject'}),
   借助mapState生成计算属性,从state中读取数据。(数组写法)
...mapState(['sum','school','subject']),

调用getters方法

getters:大致同state

this.$store.getters.方法名

辅助函数: mapGetters

   对象写法(可以取别名): ...mapGetters({bigSum: 'bigSum'})
   数组写法: ...mapGetters(['bigSum'])      

调用mutations的方法

mutations: this.$store.commit('方法名', '参数')

辅助函数: mapMutations(借助生成相关对应的方法,方法会调用commit去调用Vuex中的方法

对象写法: ...mapMutations({ addStudent: "_add"})
数组写法:...mapMutations([ '_add ,'_del'])

调用actions中的方法

actions:this.$store.dispatch('方法名', '参数')

辅助函数:mapActions

对象写法:...mapActions({ addStudent: 'add'})
数组写法:...mapActions(['add'])

Vuex模块化

模块化: 建立子仓库,实现功能应用的区分

配置项:在子仓库中getters、mutations、actions中的参数不同于总仓库

getters:第一个是模块内的 state,第二个是 模块内的 getters,第三个是根节点状态 rootState,

getters:{
    bigSum(state,getters,rootState){
       return state.num * 10
    }
}

mutations:state为模块内的state, value为传的参数

actions:context可以解构{state,commit,rootState} 配置同上面

actions:{
    add(context,value){
        context.state.num + context.rootState.num
    }
}

在带命名空间的模块内访问全局内容

如果你希望使用全局 state 和 getter,rootState 和 rootGetter 会作为第三和第四参数传入 getter,也会通过 context 对象的属性传入 action。

若需要在全局命名空间内分发 action 或提交 mutation,将 { root: true } 作为第三参数传给 dispatch 或 commit 即可。具体看下面代码:

modules: {
  foo: {
    namespaced: true,

    getters: {
      // 在这个模块的 getter 中,`getters` 被局部化了
      // 你可以使用 getter 的第四个参数来调用 `rootGetters`
      someGetter (state, getters, rootState, rootGetters) {
        getters.someOtherGetter // -> 'foo/someOtherGetter 模块内的 getter'
        rootGetters.someOtherGetter // -> 'someOtherGetter 全局的getter'
      },
      someOtherGetter: state => { ... }
    },

    actions: {
      // 在这个模块中, dispatch 和 commit 也被局部化了
      // 他们可以接受 `root` 属性以访问根 dispatch 或 commit
      someAction ({ dispatch, commit, getters, rootGetters }) {
        getters.someGetter // -> 'foo/someGetter'
        rootGetters.someGetter // -> 'someGetter'

        dispatch('someOtherAction') // -> 'foo/someOtherAction' 模块内的 action
        dispatch('someOtherAction', null, { root: true }) // ->'someOtherAction' 全局的 action 

        commit('someMutation') // -> 'foo/someMutation' 模块内的 action
        commit('someMutation', null, { root: true }) // -> 'someMutation' 全局 mutation
      },
      someOtherAction (ctx, payload) { ... }
    }
  }
}

注册:

import countOptions from './count'
import personOptions from './person'
//应用Vuex插件
Vue.use(Vuex)

//创建并暴露store
export default new Vuex.Store({
	modules:{
		countAbout:countOptions,
		personAbout:personOptions
	}
})

在Vue组件中的使用

state普通的使用方法:

this.$store.state.组件名.数据
// 辅助函数也一样,state参数 ===  this.$store.state
   ...mapState({ name: state => 组件名.name })
   ...mapState(组件名,{ name: name }) // 不取别名数组也可以

getters/mutations/actions:默认是注册在全局命名空间的,所以我们默认是可以和使用根状态一样去使用他们,但是这样不可避免会出现命名冲突的问题,所以使模块有更高的封装性与复用性,我们可以通过添加 namespaced: true 使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。

getters/mutations/actions的普通使用方法:需要去拼接

this.$stroe.getters['countAbout/num']
this.$store.commit('countAbout/add', this.num);

辅助函数的使用:

简陋版:mutations和actions的用法一样

 对象写法:...mapGetters({ num:'子仓库/num'})

粗糙版:vue提供了一个参数,只需要写一遍组件名就好了

对象写法:...mapAction('子仓库名',{方法名: ‘方法名’ })
数组写法:...mapAction('子仓库名',[ '方法名' ])

准确版:利用createNamespacedHelpers就可以像只有一个仓库一样使用了

import { createNamespacedHelpers } from 'vuex'

const { mapState, mapActions } = createNamespacedHelpers('moduleB') // moduleName

将模块内的 action 注册为全局(可以忽略)

这个感觉和维护模块的封装性有点冲突,但是既然作者提出来了,那就学吧,当我们想要我们模块内的某个 action 提升为全局 action 的时候,在他声明的时候,添加 root: true,并将 action 的定义放到 hanler 函数中,具体如下:

const actions = {
    // 模块内 action
    [ASET_AGE]({ commit }, payload) {
        setTimeout(() => {
            commit('SET_B_NAME', payload.name);
        }, 2000)
    },
    // 提升到全局的 action 
    globalAction: {
        root: true,
        handler({ commit }, payload) {
            debugger
            setTimeout(() => {
                commit('SET_B_NAME', payload.name);
            }, 2000)
        }
    }
}

vue-router

定义: key - value

后端路由: value: function

前端路由: value: compoment

配置项(类似于vuex的过程)

import vue from 'vue';
import Router from 'vue-router';
//导入相关组件
import About from '../views/About';
import Home from '../views/Home';

// 路由懒加载
const Child = () => import('./Child.vue')

vue.use(Router)

const router = new Router({
    routes:[
        // 路由默认路径,即打开页面如果没有规定路径
        {
         path: '/',
         redirect: 'home'
        },
        
        {
         name: 'about'        // 给组件起名字,插件中看到的就是这个名字
         path: '/about',     // 设置路径
         component: About   // 组件名
        },
        
        {
         path: '/home',
         component: Home,
                    
         // 嵌套路由
         children:[
          {
          name: 'child',
          path:'child',     // 不要加‘/’,会自动添加。 如果加上了,路径没问题,但是匹配组件匹配不到
          component: Child,
          }
         ]
        }
    ]
})

挂载

import router from './router'
new Vue({
        el:'#app',
        render: h => h(App),
        router:router
})

使用

定义:

指定展示位置: <router-view /> 在哪,组件就是在哪显示 
点击切换路径:将`<a></a>`标签替换为 `<router-link></router-link>`
  • $router(路由器):每个组件的$router是一样的
  • $route(存储路由信息):每个组件都有,信息不一样 注意: 在切换的过程中是不断创造销毁的过程

router-link: to的匹配本来就是对象的写法,但是一般不传参数,也不用name跳转时我们就可以直接简写为 想跳转的地址

//普通跳转
<router-link to='/about(配置的path的地址)'></router-link>

//嵌套路由的跳转: 父path + 子path
<router-link to='/home/child'></router-link>

// 路由层数过多,直接用配置的name跳转
<router-link to='{name: 'child'}'></router-link>

传参(query,params,props)

query传参:

  方法1: router-link传参:v-bind + 模板字符串(只在简单方式使用)
  <router-link :to='`/home/? num=${num}`'> </router-link>  // 数据简单的情况
  <router-link :to="{
                    path: '/home',
                    query:{
                      id: item.id,
                      num: item.num
                    }"                     </router-link>     // 数据比较多的情况以对象的方式传
  
  方法2: js跳转传参:  直接以参数的形式传
  例: this.$router.push({
                    path: '/home',
                    query:{
                      id: item.id,
                      num: item.num
                    }) 
                    
 
 获取值: this.$route.query.传的参数(注意:每个路由的route是不同的哦)

params传参(比较麻烦)

路由配置项为前提{
 name:'home'   // 需要起名字
 path: '/home/:id'  // 占位符
}
方法1:router-link传参:v-bind + 模板字符串(只在简单方式使用)+ name配置路由 + 地址后占位
<router-link :to='`/home/${id}`'></router-link>  //简单的写法
<router-link :to="{
                   name:'home'    //  需要的是name,而不是path
                   params:{       //  需要的是params 而不是query
                     id:item.id,
                     num: item.id      
                    }
                   }"  </router-link>   //复杂的写法
方法2:js跳转传参: 直接以参数的形式传
例: this.$router.push({
                     name:'home'    //  需要的是name,而不是path
                     params:{       //  需要的是params 而不是query
                     id:item.id,
                     num: item.id      
                    })


获取值:this.$route.params.传的参数

利用props的形式接收参数

方法1:在配置路由时,写props配置项,添加到配置中,
       特点:想接谁就接收谁
{
path: 'home'
component: Home,
props($route){          // $route为默认参数
    return {
        id:$route.query.id,
        a:1,
    }
}
}



方法二:将配置项中的props属性值设为true
       特点: 只有params的传参有效
{
path: 'home',
name: 'home'
component: Home,
props:true
}  


方法三:值为对象,该对象中的所有key-value都会以props的形式传给home
       特点:局限性太大了,看看就行了
{
path: 'home',
name: 'home'
component: Home,
props:{a:1, b:2}
}  

编程式路由导航(push/replace/go)

push: 普通跳转

this.$router.push()

replace: 同不带历史记录的跳转,只不过这是路由,会返回到上上次的路由地址

this.$router.replace()

go: 传入数值以跳到想要到达的路由界面

this.$router.go(0)    // 重新刷新路由 

缓存路由组件(keep-alive)

定义:通过keep-alive组件让离开或重新进入该路由组件时,页面不销毁而保留状态

使用:include属性: 后面的值为组件名而不是路由名

<keep-alive include="News"> //缓存一个   
                            //缓存多个  include="['news', 'home']
<router-view />
</keep-alive>

生命周期(activated 和 deactivated)

activated: 每次进入组件时触发(包括第一次

deactivated:每次离开组件时触发

注:准备销毁和销毁的生命周期不在执行、

☆路由守卫

全局路由守卫

  • router为自己创建的路由器
  • to: 去的组件,属性为你在上面即routes中的配置,例如to.meta, to.path;
  • from: 来的组件,属性即也是你配置的;
  • next: 允许跳转的方法;
  • meta(路由元信息):向meta中放置自定义数据在路由守卫中通过meta进行-判断校验 全局前置路由守卫————初始化的时候被调用、每次路由切换之前被调用
router.beforeEach((to,from,next)=>{
	console.log('前置路由守卫',to,from)
	if(to.meta.isAuth){ //判断是否需要鉴权
		if(localStorage.getItem('school')==='atguigu'){
			next()
		}else{
			alert('学校名不对,无权限查看!')
		}
	}else{
		next()
	}
})

全局后置路由守卫————初始化的时候被调用、每次路由切换之后被调用: 没有next方法,为已经跳完了

router.afterEach((to,from)=>{
	
})

独享路由守卫

定义:写在去的路由守卫的配置项中,关键词:beforeEnter,独享路由守卫只有前置没有后置

{
name:'xinwen',
path:'news',
component:News,
meta:{isAuth:true,title:'新闻'},
beforeEnter: (to, from, next) => {
	
}
},

组件内的路由守卫(使用方式和生命周期一样在script中配置)

beforeRouteEnter:通过路由规则,进入该组件时被调用

beforeRouteEnter (to, from, next) {
			
		},

beforeRouteLeave:通过路由规则,离开该组件时被调用

beforeRouteLeave (to, from, next) {

}

history模式和hash模式

定义:hash 模式和 history 模式都属于浏览器自身的特性,vue-router利用浏览器的特性(调用浏览器上的这连个接口)实现前端路由

更改模式:

new vueRuter({
mode: 'hash',
routes: []
})

区别:

  1. hash兼容性好,history较差;
  2. history地址比较干净,hash模式下有#号;
  3. history404(刷新时)问题需要服务器解决,因为history模式下会在http请求中把ip的全部信息都带向后端,易导致前端的 URL 必须和实际向后端发起请求的 URL 不一致;而hash值不包括在http请求中带去服务器,前端自己玩。
  4. hash不触发重载, history原本会触发,但是用pushState和replaceState可以解决。

注: #后为hash值

image.png