Vue2学习
1 Vue基础
1.1 介绍
Vue是一套构建用户界面的渐进式(自底向上,从简单到复杂) JavaScript 框架,是一种易学易用,性能出色,适用场景丰富的 Web 前端框架。
1.2 理解MVVM
- M(模型):模型是指代表真实状态内容的领域模型(面向对象),或指代表内容的数据访问层(以数据为中心)=>对应 data() 中的数据
- V(视图):就像在MVC和MVP模式中一样,视图是用户在屏幕上看到的结构、布局和外观(UI)。=>对应 Vue 的模板
- VM(视图模型):视图模型是暴露公共属性和命令的视图的抽象。=>对应 Vue 实例对象
理解:
前端的框架其实是扮演 VM 的角色,实现在 M 和 V 之间建立链接,当我们规定好页面模板和数据之后,框架负责联系它们并构建出最终的页面效果。我们学习前端框架是在学习它建立 M 和 V 之间链接的语法。
1.3 安装
1.3.1 使用CDN导入
对于制作原型或学习,你可以这样使用最新版本:
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
对于生产环境,我们推荐链接到一个明确的版本号和构建文件,以避免新版本造成的不可预期的破坏:
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.14"></script>
如果你使用原生 ES Modules,这里也有一个兼容 ES Module 的构建文件:
<script type="module">
import Vue from 'https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.esm.browser.js'
</script>
1.3.2 使用NPM安装
在用 Vue 构建大型应用时推荐使用 NPM 安装。NPM 能很好地和诸如 webpack 或 Browserify 模块打包器配合使用。同时 Vue 也提供配套工具来开发单文件组件。
npm install vue # 最新稳定版
1.4 Hello World
Vue 的声明式编程:Vue.js 的核心是一个允许采用简洁的模板语法来声明式地将数据渲染进 DOM 的系统:
<div id="app">
{{ message }} <!-- 可以写数据、js表达式、js代码 -->
</div>
<script>
var vm = new Vue({ // ({中间写配置项})
el: '#app', // el指定当前Vue实例为哪一个容器来服务,两者之间一一对应,指定方式一般是css选择器
data: { // data中存储el中使用的数据
message: 'Hello Vue!'
},
// 写函数
methods:{
}
})
</script>
注意点:
所有 Vue 管理的函数都写成普通函数,不是 Vue 管理的函数都写成箭头函数,这样做的目的是让 this 始终指向 vm。
Vue的处理方式:
Vue 将 el
中指定的容器拿到,检查其中是否有 Vue 语法,如有则根据语法规则重新生成容器内容变成正常的 Html 片段,再替换原本的容器的内容。
将数据和虚拟 DOM 关联,虚拟 DOM 会剔除重复数据把新数据放进来,再由虚拟 DOM 操作真实 DOM ,由真实 DOM 将数据写入 Html 页面。
指定容器的方法
<script>
var vm = new Vue({ // ({中间写配置项})
el: '#app', // 方法一:使用 el 来指定
})
</script>
<script>
var vm = new Vue({ // ({中间写配置项})
})
vm.$mount('#app') // 方法二:使用 $mount 来指定
</script>
1.5 模板语法
1.5.1 插值语法
用于解析标签体的内容
{{ 数据 }}被称为 Mustache 标签是插值语法的格式,每当它对应数据对象的值发生变化,都会替换整个 Mustache 标签。
Mustache 标签可以获取到任何vm实例上的东西
<span>Message: {{ msg }}</span>
我们也可以使用 v-once
命令来指定 该插值表达式的值只允许插入一次。
<span v-once>Message: {{ msg }}</span>
<script>
var vm = new Vue({ // ({中间写配置项})
el: '#app',
data: {
message: 'Hello Vue!'
}
})
</script>
1.5.2 指令语法
用于解析标签(包括:标签属性、标签体的内容、绑定事件…)
动态参数
从 2.6.0 开始,可以用方括号括起来的 JavaScript 表达式作为一个指令的参数:
<!--
注意,参数表达式的写法存在一些约束,如之后的“对动态参数表达式的约束”章节所述。
-->
<a v-bind:[attributeName]="url"> ... </a>
这里的 attributeName
会被作为一个 JavaScript 表达式进行动态求值,求得的值将会作为最终的参数来使用。例如,如果你的 Vue 实例有一个 data
property attributeName
,其值为 "href"
,那么这个绑定将等价于 v-bind:href
。
同样地,你可以使用动态参数为一个动态的事件名绑定处理函数:
<a v-on:[eventName]="doSomething"> ... </a>
在这个示例中,当 eventName
的值为 "focus"
时,v-on:[eventName]
将等价于 v-on:focus
。
对动态参数的值的约束
动态参数预期会求出一个字符串,异常情况下值为 null
。这个特殊的 null
值可以被显性地用于移除绑定。任何其它非字符串类型的值都将会触发一个警告。
对动态参数表达式的约束
-
动态参数表达式有一些语法约束,因为某些字符,如空格和引号,放在 HTML attribute 名里是无效的。例如:
<!-- 这会触发一个编译警告 --> <a v-bind:['foo' + bar]="value"> ... </a>
-
在 DOM 中使用模板时 (直接在一个 HTML 文件里撰写模板),还需要避免使用大写字符来命名键名,因为浏览器会把 attribute 名全部强制转为小写:
<!-- 在 DOM 中使用模板时这段代码会被转换为 `v-bind:[someattr]`。 除非在实例中有一个名为“someattr”的 property,否则代码不会工作。 --> <a v-bind:[someAttr]="value"> ... </a>
v-if
<p v-if="seen">现在你看到我了</p>
这里,v-if
指令将根据表达式 seen
的值的真假来插入/移除 <p>
元素。也可以配合 v-else
指令一起使用。
v-bind
Vue 的 Mustache 标签不能在 Html 的标签中的属性内直接使用,我们必须再前面加上 v-bind
指令:
<!-- 完整语法 -->
<a v-bind:href="url">...</a>
<!-- 缩写 -->
<a :href="url">...</a>
<!-- 动态参数的缩写 (2.6.0+) -->
<a :[key]="url"> ... </a>
v-bind
是单项的数据绑定,当 Vue 管理的数据改变时页面会发生变化,但是页面变化时 Vue 管理的数据不会发生变化。(如果需要双向绑定使用 v-model
指令完成,v-model
指令只能用于表单类标签)
v-on
另一个例子是 v-on
指令,它用于监听 DOM 事件:
<!-- 完整语法 -->
<a v-on:click="doSomething">...</a>
<!-- 缩写 -->
<a @click="doSomething">...</a>
<!-- 动态参数的缩写 (2.6.0+) -->
<a @[event]="doSomething"> ... </a>
1.6 数据代理
1.6.1 使用defineProperty方法数据代理
Object 的 defineProperty 方法可以为变量增加属性,使用该方法增加到属性默认不会参加枚举且无法被修改删除。
<script>
let num =18;
let person = {
name:'张三',
sex:'男'
}
<!-- 给person添加age属性 -->
Object.defineProperty(person,'age',{
value:18,
<!-- 使属性可枚举 -->
enumerable:true,
<!-- 使属性可被修改 -->
writable:true,
<!-- 使属性可被删除 -->
configurable:true,
<!-- 可配置age的getter,该方法在每次age属性被读取时都被调用,返回值为age的值-->
get() {
return num
},
<!-- 可配置age的setter,该方法在每次age属性被修改时都被调用,会传入修改的值-->
set(value) {
num = value
}
})
</script>
1.6.2 Vue中的数据代理
const vm = new Vue({
el:'#root',
data:{
msg = 'data'
}
})
- 将data中的数据复制到_data中(_data = options.data = data)
- 通过Object.defineProperty()将_data的所有属性添加到vm
- 为每一个属性添加getter/setter方法操作data中对应的值
1.6.3 Vue 如何得知数据更新
Vue 首先使用观察者模式对 data 里的数据进行加工,递归调用 Object 的 defineProperty 方法,为每一个属性、对象内的属性创建对应的 getter、setter,当数据发生变化时会调用 setter,setter 会更新值然后重新解析模板。
对于数组:
Vue 将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新。这些被包裹过的方法包括:
push()
在末尾添加数据pop()
删除末尾的数据,返回值为删除的数据unshift()
在头部添加数据shift()
删除头部的数据,返回值为删除的数据splice()
截取数组\删除并插入数据,返回值:是一个新数组,里面是截取出来的数据sort()
对数组进行排序reverse()
反转整个数组
1.7 计算属性与监听器
1.7.1 计算属性
我们在使用模板语法的时候,总是要避免在其中放入太多的逻辑,让我们难以维护,这时我们应该采用计算属性来实现逻辑计算。
<div id="root">
<p>Original message: "{{ message }}"</p>
<p>Computed reversed message: "{{ reversedMessage }}"</p>
</div>
var vm = new Vue({
el: '#root',
data: {
message: 'Hello'
},
computed: {
reversedMessage: {
// 1.初次读取该属性时调用 2.所依赖的数据发生变化时调用
get() {
// Vue 为我们重新将 `this` 指向 vm 实例
return this.message.split('').reverse().join('')
},
// 当属性被修改
set(newValue) {
this.message = newValue
}
}
}
})
计算属性VS方法?
两种方法实现的最终结果完全相同。不同的是计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。这就意味着只要 message
还没有发生改变,多次访问 reversedMessage
计算属性会立即返回之前的计算结果,而不必再次执行函数。相比之下,每当触发重新渲染时,调用方法将总会再次执行函数。
1.7.2 侦听属性
侦听属性顾名思义就是在侦听某个属性的变化,当需要在数据变化时执行异步或开销较大的操作时,我们使用侦听器 wath
方式是最有用的。
当属性变化时, 回调函数自动调用, 在函数内部进行计算
<script>
var watchExampleVM = new Vue({
el: '#root',
data: {
flags:{
flag1: true,
flag2: true
}
},
watch: {
// watch默认不侦听多层级数据的内部的改变
'flags':{
// 侦听多级结构中所有属性的变化
deep:reue,
// 如果 `flag` 发生改变,这个函数就会运行
handler (newValue, oldValue) {
console.log('falg被修改了', newValue, oldValue)
}
}
}
})
</script>
注意:
当我们侦听对象时,默认侦听的时对象的地址,而不是对象的值是否改变,如果要侦听对象属性的值,我们应该加上deep:true
1.8 Class 与 Style 绑定
当我们需要动态的修改 Class 与 Style 时我们可以用 v-bind
处理它们,在将 v-bind
用于 class
和 style
时,Vue 做了专门的增强,表达式结果的类型除了字符串之外,还可以是对象或数组。
1.8.1 绑定 HTML Class
<div id="root">
<!-- 字符串写法,适用于:需要的样式名字不确定,需要动态指定 -->
<div class="basic" :class="clazz"></div>
<!-- 数组写法,适用于:需要的样式的个数不确定,名字也不确定 -->
<div class="basic" :class="clazzArr"></div>
<!-- 对象写法,适用于:需要的样式的个数确定,名字也确定,但要动态决定是否生效 -->
<div class="basic" :class="clazzObj"></div>
</div>
const vm =new Vue({
el: '#root',
data: {
clazz: 'class1',
clazzArr: ['clazz1','clazz2'],
clazzObj: {
// true 表示该样式生效,false 表示该样式不生效
clazz1:true,
clazz2:false
}
}
})
1.8.2 绑定 Style 内联样式
我们一般用 Class 而不用 Style 绑定
v-bind:style
的对象语法十分直观——看着非常像 CSS,但其实是一个 JavaScript 对象。
<div id="root">
<!-- 对象写法 -->
<div class="basic" :style="styleObj"></div>
<!-- 数组写法,更不常用 -->
<div class="basic" :style="styleArr"></div>
</div>
const vm =new Vue({
el:'#root',
data:{
styleObj: {
fontSize: '40px',
color: 'red'
}
styleArr: [
{
fontSize: '40px',
color: 'red'
},
{
backgroundColor: 'blue'
},
]
}
})
1.9 条件渲染
1.9.1 v-if 指令
<h1 v-if="true/false">Hello!</h1>
当 v-if
的值为true时该标签会被渲染,为false时不会被渲染。
带有 v-if
的元素不一定会被渲染并保留在 DOM 中。
v-else-if
v-else-if
,顾名思义,充当 v-if
的“else-if 块”,可以连续使用:
<div v-if="type === 'A'">
A
</div>
<div v-else-if="type === 'B'">
B
</div>
<div v-else-if="type === 'C'">
C
</div>
<div v-else>
Not A/B/C
</div>
v-else-if
必须紧跟在带 v-if
或者 v-else-if
的元素之后。
v-else
你可以使用 v-else
指令来表示 v-if
的“else 块”:
<div v-if="Math.random() > 0.5">
Now you see me
</div>
<div v-else>
Now you don't
</div>
v-else
元素必须紧跟在带 v-if
或者 v-else-if
的元素的后面,否则它将不会被识别。
1.9.2 v-show 指令
<h1 v-show="true/false">Hello!</h1>
当 v-show
的值为true时该标签显示,为false时不显示。
带有 v-show
的元素始终会被渲染并保留在 DOM 中。v-show
只是简单地设置元素的样式为 display:none。
1.9.3 v-if VS v-show
使用 template 标签时只能使用v-if
。
一般来说,v-if
有更高的切换开销,而 v-show
有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show
较好;如果在运行时条件很少改变,则使用 v-if
较好。但是就算频繁地切换时使用 v-if
问题也不大。
1.10 列表渲染
1.10.1 v-for 指令
遍历数组
<ul id="root">
<li v-for="(item,index) in items" :key="index">
<!-- index 是索引,item 是 items 数组里的每一项 -->
{{ item.message }}
</li>
</ul>
var vm = new Vue({
el: '#root',
data: {
items: [
{ message: 'msg1' },
{ message: 'msg2' }
]
}
})
遍历对象
<ul id="root">
<li v-for="(c,index) in object" :key="index">
<!-- index 是对象的属性,value 是属性对应的值 -->
{{ index }} -- {{ value }}
</li>
</ul>
var vm = new Vue({
el: '#root',
data: {
object: {
title: 'How to do lists in Vue',
author: 'Jane Doe',
publishedAt: '2016-04-10'
}
}
})
遍历字符串(用得很少)
<ul id="root">
<li v-for="(char,index) in str" :key="index">
<!-- index 是索引,char 是 str 的每一个字符 -->
{{ index }} -- {{ char }}
</li>
</ul>
var vm = new Vue({
el: '#root',
data: {
str: 'hello'
}
})
遍历指定数字(用得更少)
<ul id="root">
<li v-for="(num,index) in 5" :key="index">
<!-- index 是索引,num 是自然计数(从1开始) -->
{{ index }} -- {{ num }}
</li>
</ul>
1.10.2 key的作用
key的作用是作为唯一标识给 Vue 内部使用,在页面中不显示。
<button @click.once="add">add</button>
<ul>
<li v-for="(p,index) in persons" :key="index">
{{p.name}}--{{p.age}} <input type="text"></input>
</li>
</ul>
<script>
// 创建Vue实例
const vm = new Vue({
el: '#app',
data: {
persons: [
{id: '001', name: '张三', age: 18},
{id: '002', name: '李四', age: 19},
{id: '003', name: '王五', age: 20}
]
},
methods: {
add(){
const p = {id: '004', name: '赵六', age: 21};
this.persons.unshift(p)
}
}
});
</script>
使用 index 作为 key 的问题:
当我们在 v-for
遍历的对象中破化顺序的插入新的元素并且原标签包含有输入性标签时,输入标签的值会发生错乱,例如:
原列表:
赵六添加到第一后:
原因:
前面提到 Vue 在解析模板之后将数据放入虚拟 DOM 再由虚拟 DOM 操作真实 DOM 将页面显示出来,再新加入数据后,Vue 会对新旧两个虚拟 DOM 执行对比算法。由于我们破坏了原来的顺序,新旧两个虚拟 DOM 将如下所示:
原虚拟 DOM
新虚拟 DOM
执行对比算法时,会对key相同的元素进行对比。此时发现文本块不相同于是重新渲染到真实 DOM,而 input 块相同(我们对input的修改是在真实 DOM 中进行的,使用在虚拟 DOM 看来它们是相同的)于是直接复用,这样就造成了input框内容的错乱。
解决方法:
id的唯一性保证了复用性
<button @click.once="add">add</button>
<ul>
<li v-for="(p,index) in persons" :key="p.id">
{{p.name}}--{{p.age}} <input type="text"></input>
</li>
</ul>
<script>
// 创建Vue实例
const vm = new Vue({
el: '#app',
data: {
persons: [
{id: '001', name: '张三', age: 18},
{id: '002', name: '李四', age: 19},
{id: '003', name: '王五', age: 20}
]
},
methods: {
add(){
const p = {id: '004', name: '赵六', age: 21};
this.persons.unshift(p)
}
}
});
</script>
1.11 事件处理
1.11.1 监听事件
我们可以用 v-on
指令监听 DOM 事件,并在触发时运行指定方法
<div id="root">
<button v-on:click="showInfo">点我提示信息</button>
<!-- v-on简写 -->
<button @click="showInfo">点我提示信息</button>
</div>
var example1 = new Vue({
el: '#root',
data: {
},
methods:{
showInfo(event) {
alert('这是一条信息')
}
}
})
1.11.2 事件修饰符
.stop
阻止事件冒泡(常用).prevent
阻止默认事件(常用).once
事件只触发一次(常用).capture
使用事件的捕获模式.self
只有event.target是当前操作的元素才触发事件.passive
事件的默认行为立即执行,无需等待事件回调执行完毕,尤其能够提升移动端的性能
<!-- 阻止单击事件继续传播 -->
<a v-on:click.stop="doThis"></a>
<!-- 提交事件不再重载页面 -->
<form v-on:submit.prevent="onSubmit"></form>
<!-- 修饰符可以串联 -->
<a v-on:click.stop.prevent="doThat"></a>
<!-- 只有修饰符 -->
<form v-on:submit.prevent></form>
<!-- 添加事件监听器时使用事件捕获模式 -->
<!-- 即内部元素触发的事件先在此处理,然后才交由内部元素进行处理 -->
<div v-on:click.capture="doThis">...</div>
<!-- 只当在 event.target 是当前元素自身时触发处理函数 -->
<!-- 即事件不是从内部元素触发的 -->
<div v-on:click.self="doThat">...</div>
1.11.2 按键修饰符
监听按键
- @keydown:按下按键后立即执行对应方法
- @keyup:按下按键并松开后执行对应方法
别名
Vue 提供了绝大多数常用的按键码的别名:
- 捕获回车键
.enter
- 捕获tab换行键
.tab
(必须配合@keydown使用) - 捕获删除和退格键
.delete
- 捕获退出键
.esc
- 捕获空格键
.space
- 捕获方向键上
.up
- 捕获方向键下
.down
- 捕获方向键左
.left
- 捕获方向键右
.right
系统修饰键
系统修饰键因为有一些特殊的功能,所有我们一般配合@keydown来使用
- 捕获ctrl键
.ctrl
- 捕获alt键
.alt
- 捕获shift键
.shift
- 捕获Win徽标键/command 键(⌘)
.meta
鼠标按键修饰符
- 捕获鼠标左键
.left
- 捕获鼠标右键
.right
- 捕获鼠标中键
.middle
1.12 表单输入绑定
1.12.1 v-modle指令
你可以用 v-model
指令在表单 <input>
、<textarea>
及 <select>
等元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。
v-model
会忽略所有表单元素 value
、checked
、selected
等属性的初始值,而总是将 Vue 实例的数据作为数据来源。我们应在 Vue 实例的 data
选项中声明初始值。
v-model
在内部为不同的输入元素使用不同的 property 并抛出不同的事件:
- text 和 textarea 元素使用
value
property 和 input 事件 - checkbox 和 radio 使用
checked
property 和 change 事件 - select 字段将
value
作为 prop 并将 change 作为事件
1.12.2 修饰符
.lazy
在change事件之后更新而非input事件后更新.number
自动将用户的输入值转为数值类型.trim
自动过滤用户输入的首尾空白字符
1.13 Vue的指令
已经学过的指令:
v-show
、v-if
、v-else-if
、v-else
、v-for
、v-on
、v-bind
、v-model
1.13.1 其他内置指令
v-text
: 更新元素的 textContv-html
: 与v-text
类似,但是能解析 Html 语句。一定不要在用户提交的内容上使用,这样可能导致XSS攻击v-slot
: 可放置在函数参数位置的 JavaScript 表达式 。可选,即只需要在为插槽传入 prop 的时候使用v-pre
: 跳过这个元素和它的子元素的编译过程v-cloak
: 这个指令保持在元素上直到关联实例结束编译。和 CSS 规则[v-cloak] { display: none }
一起用时,这个指令可以隐藏未编译的 Mustache 标签直到实例准备完毕v-once
: 只渲染元素和组件一次,在初次动态渲染之后,就视为静态内容
1.13.2 自定义指令
<div id="root">
<p v-msg="msg"> </p>
</div>
函数式定义指令:
new Vue({
el: '#root',
data: {
msg: "msg"
},
directives: {
// 1.指令与属性绑定成功时调用 2.指令所在的模板被重新解析时调用
msg(element, binding){
// element是指令所在的 Html 元素
// binding 包含所绑定带的属性的值
console.log(element, binding.value)
}
}
})
对象式定义指令:
new Vue({
el: '#root',
data: {
msg: "msg"
},
directives: {
msg: {
// 指令与属性绑定成功时调用
bind(element, binding){
},
// 指令所在的元素被插入页面后调用
inserted(element, binding){
},
// 指令所在的模板被重新解析时调用
update(element, binding){
}
}
}
})
1.14 Vue2的生命周期
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-niYM8GuV-1681370437197)(http://yun-picgo.oss-cn-hangzhou.aliyuncs.com/img/Vue生命周期.png)]
1.14.1 各个生命周期介绍
初始化显示:
-
beforeCreate()
在数据监测、数据代理之前被调用,此时data不可使用,vue实例上没有数据和 getter、setter
-
created()
在数据监测、数据代理之后,el 被处理之前被调用,此时 el 不可使用
挂载状态:
-
beforeMount()
在 el 指定容器的
outerHTML
被挂载之前被调用,虚拟 DOM 尚未转化为真实 DOM ,因此所有对 DOM 的操作都无效 -
mounted()
在 el 指定容器的
outerHTML
被挂载完成后被调用
更新状态: this.xxx = value
-
beforeUpdate()
在监测到数据变化后,更新数据之前被调用,数据是最新的,但页面是旧的
-
updated()
在更新数据之后被调用,数据是最新的,但页面也是是新的
销毁 vue 实例: vm.$destory()
-
beforeDestory()
在销毁之前被调用,此时所有东西都还能用
-
destoryed()
在销毁之后被调用,此时 data、methods、组件间链接等都不能用了
1.14.2 常用生命周期钩子
mounted()
初始化工作:发送 ajax 请求, 启动定时器、绑定自定义事件、订阅消息等异步任务
beforeDestory()
做收尾工作:清除定时器、解绑自定义事件、取消订阅消息等
2 Vue2 组件
2.1 组件基础
通常一个应用会以一棵嵌套的组件树的形式来组织:
组件其实是实现应用中局部功能的代码和资源的集合,Vue 的组件是可复用的 Vue 实例
2.1.1 使用步骤
创建组件
-
非单文件组件创建
const 组件名 = Vue.extend({ template: ` <div> <!-- 中间写组件的 html 内容--> </div> `, data(){ return { } } })
-
单文件组件创建
<template> </template> <script> export defult { name: 'z', data(){ return { } } } </script> <style> </style>
注册组件
-
局部注册:组件只在 Vue 的 el 指定的容器内有效
new Vue({ el: '#root', components: { // 组件别名:组件, } })
-
全局注册
Vue.component('组件别名',组件)
引入组件
<组件名></组件名>
2.1.2 VueComponent()
Vue 在解析到组件的标签时会为我们创建组件的实例对象,组件的本质是一个名叫 VueComponent()的构造函数,由 Vue.extend生成
组件的配置中 this 的指向是组件的实例对象,new Vue({}) 的配置中 this 的指向是组件的实例对象
2.1.3 render() 函数
在 Vue CIL 中使用的默认为 vue.runtime.esm.js,它与 vue.js 的区别是只有核心库没有模板解析器所以在 main.js 中需要使用render()
来解析 App 组件
// 全写法
render (createElement){
return createElement(App)
}
// 使用 lambda 表达式缩写
render h : => h(App)
2.1.4 注意点
- 组件中不使用 el,最后由 Vue 实例指定
- data 必须写成函数的形式,避免组件复用时数据存在引用关系
- 嵌套组件时,在谁身上引入的,就在谁的 template 调用
2.2 数据传递
ref 与 props 都是用来传递数据的区别是
-
ref 用于获取在当前组件中写的 Html 渲染的 DOM 和引入的子组件放入当前组件的实例对象中,类似于原生 js 的 id 和 document.getElementById
- ref 可以拿到 Vue 的组件实例对象,getElementById 只能拿到 Vue 的组件实例对应的真实 DOM
<template> <h1 ref="name">ref</h1> </template> <srcipt> export default { data(){ return {} }, methods: { DOM() {this.$refs.name} } } </srcipt>
-
props 用于从父组件给子组件传递数据,props是只读的,但是 Vue 对于它的监测只是浅层的,即修改对象的子属性 Vue 监测不到
例如给 Student 传值
<template> <Student name="张三" sex="男" :age="19" </template>
Student 接收值
<srcipt> export default { data(){ return {} }, props:['name','sex','age'] } </srcipt>
-
props 的接收方式
-
简单接收
props:['name','sex','age']
-
限定类型接收
props:{ name:String, sex:String, age:Number }
-
指定必传参数
props:{ name:{ type:String, required:true // 必须要传 }, sex:{ type:String, required:true }, age:{ type:Number default:18 // 如果没传,给默认值 } }
-
2.3 组件的自定义事件
2.3.1 自定义事件绑定和解绑
-
在标签上使用绑定
-
方法一:通过
v-on
或简写@
来绑定<template> <div @MyEevent="eventStart"/> </template> <srcipt> export default { methods: { eventStart() {console.log.('自定义事件被调用')} } } </srcipt>
-
方法二:通过 ref 和 $on 来绑定
<template> <div ref="refId"/> </template> <srcipt> export default { methods: { eventStart() {console.log.('自定义事件被调用')} }, mounted(){this.$refs.refId.$on('MyEvent',this.eventStart)} } </srcipt>
-
-
调用自定义事件
<srcipt> export default { methods: { sent() {this.$emit('MyEevent')} } } </srcipt>
-
自定义事件解绑
// 解绑一个事件 this.$off('MyEvent') // 解绑一个事件 this.$off('事件1','事件2','事件3') // 解绑所有事件 this.$off()
-
自定义事件也可以使用事件的修饰符
-
将原生事件绑定在组件上可以使用
.native
修饰符
2.4 全局事件总线
2.4.1 全局事件总线的使用
全局事件总线不是 Vue 里的具体功能,而是总结出的经验。它可以实现任意组件间的数据通信
new Vue({
el: '#app',
render: h => h(App),
beforCreate() {
// 安装全局事件总线,这样 $bus 就是当前应用的vm
Vue.prototype.$bus = this
}
}
2.4.2 全局事件总线的原理 #
2.5 消息订阅与发布 #
2.6 插槽
2.6.1 默认插槽的简单使用
我们可以在引用组件标签的时候,在标签内部写东西,然后使用 slot
标签告诉 Vue 我们写的东西应该放在组件内的什么地方。
在引用组件时
<组件名>
<span>需要往组件中加的东西</span>
</组件名>
组件内部
<template>
<span>本来的内容</span>
<!-- 使用 solt 来指定要插入到哪 -->
<solt>这里是一些默认值,当组件的使用者没有传入值时会出现</solt>
<span>本来的内容</span>
</template>
关于样式
插槽是在我们引入组件的地方解析的,当我们在引入组件的地方为插槽写了样式,则样式和插槽会一起传递给组件。
如果没有写样式则只传递插槽本身,这时我们也可以将样式写在组件内。
2.6.2 具名插槽
当我们需要给一个组件传入多个插槽时,我们需要给插槽起名字,否则 Vue 会在每一个 solt
标签处都插入数据
在引用组件时
<组件名>
<span solt="solt1" >需要往组件中加的东西</span>
<span solt="solt2" >需要往组件中加的东西</span>
</组件名>
组件内部
<template>
<span>本来的内容</span>
<!-- 使用 solt 来指定要插入到哪 -->
<solt name="solt1" >这里是一些默认值</solt>
<solt name="solt2" >这里是一些默认值</solt>
<span>本来的内容</span>
</template>
2.6.3 作用域插槽
数据在插槽内,但是数据渲染的方式需要由组件的使用者来定义,这个时候会发生组件的使用者拿不到数据的问题,我们可以使用作用域插槽来解决这个问题
在组件内部
<template>
<span>本来的内容</span>
<!-- 将 data 传给了使用者 -->
<solt :data1="data1" :data2="data2">这里是一些默认值</solt>
<span>本来的内容</span>
</template>
在引用组件时
<组件名>
<template scope="data">
<span>{{data.data1}}---{{data.data2}}</span>
</template>
</组件名>
2.6 动态组件 & 异步组件
2.7 处理边界情况
兄弟间数据传递 #
-
通过父亲中转(状态提升)
-
父亲给子组件1传一个函数类型的 props
<template> <component1 :receive="receive"/> <component2 :datas="datas"/> </template> <srcipt> import component1 from './components/component1' import component2 from './components/component2' export default { data(){ return { datas:[] } }, components:{component1,component2}, methods: { receive(dataObj) {this.datas.unshift(dataObj)} } } </srcipt>
-
子组件1调用函数将数据传给父亲
<template> </template> <srcipt> export default { data(){ return { dataObj:{} } }, props:['receive'] methods: { sent(){ this.receive(this.dataObj) } } } </srcipt>
-
子组件2用 props 接收父亲传来的数据
<srcipt> export default { data(){ return {} }, props:['datas'] } </srcipt>
-
-
通过自定义事件
-
父亲给子组件1绑定一个自定义事件(事件的回调一定留在接收者中)
<template> <component1 @event="receive"/> <component2 :datas="datas"/> </template> <srcipt> import component1 from './components/component1' import component2 from './components/component2' export default { data(){ return { datas:[] } }, components:{component1,component2}, methods: { receive(dataObj) {this.data.sunshift(dataObj)} } } </srcipt>
-
子组件1使用 $emit 触发事件将数据传给父亲(发送者使用$emit)
<template> </template> <srcipt> export default { data(){ return { dataObj:{} } }, methods: { sent(){ this.$emit('event',this.dataObj) } } } </srcipt>
-
子组件2用 props 接收父亲传来的数据
<srcipt> export default { data(){ return {} }, props:['datas'] } </srcipt>
-
-
通过全局事件总线
-
通过消息订阅与发布
3 过渡 & 动画
3.1 如何使用
3.1.1 Vue 的过渡
过渡的类名
v-enter
:定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。v-enter-active
:定义进入过渡后的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。v-enter-to
:定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时v-enter
被移除),在过渡/动画完成之后移除。v-leave
:定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。v-leave-active
:定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。v-leave-to
:定义离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时v-leave
被删除),在过渡/动画完成之后移除。
3.1.2 进入和离开的过渡和动画
单元素过度
Vue 提供了 transition
的封装组件,在下列情形中,可以给任何元素和组件添加进入/离开过渡 我们要做的是,给需要过度的元素包裹上 transition
标签并指定 name 属性,Vue 会帮我们自动使用写好的 CSS 动画
<div id="app">
<transition name="hello">
<p>hello</p>
</transition>
</div>
.hello-enter-active, .hello-leave-active {
transition: 0.5s linear;
}
.hello-enter, .hello-leave-to {
transform: translateX(-100%)
}
.hello-leave, .hello-enter-to {
transform: translateX(0)
}
- 如果我们需要在页面加载就应用过渡,我们可以为
transition
标签添加appear
属性
单元素动画
CSS 动画用法与 CSS 过渡相同,区别是在动画中 v-enter
类名在节点插入 DOM 后不会立即删除,而是在 animationend
事件触发时删除。
.hello-enter-active {
animation: come-leave 0.5s;
}
.hello-leave-active {
animation: come-leave 0.5s;
}
@keyframes come-leave {
from {
transform: translateX(0)
}
to {
transform: translateX(0)
}
}
多元素过渡
在过渡中有多个元素时,我们使用 transition-group
标签,并且给每一个元素加上唯一的 key
属性,其他和单元素过渡一致
3.2 列表过渡 #
v-move
3.3 状态过渡 #
3.4 集成第三方动画库
使用第三方库时我们使用下面的过渡类名
enter-class
enter-active-class
enter-to-class
leave-class
leave-active-class
leave-to-class
以第三方 CSS 动画库 Animate.css 为例
-
安装 Animate.css
npm install animate.css
-
引入
import 'animate.css'
-
使用
<div id="app"> <transition appear name="animate__animated animate__bounce" enter-active-class="animate__bounce" > <p>hello</p> </transition> </div>
4 对 Vue 可复用性和组合的增强
4.1 mixins 混入
不破坏代码,只给你没有的
局部混入
全局混入
4.2 插件
5 相关工具
5.1 AJAX请求
5.1.1 axios的使用
-
安装
npm i axiox
-
引入
import axios from 'axios'
-
使用
axiosDemo(){ axios.gat('url').then( res => {/**请求成功时*/}, error => {/**请求s时*/} ) }
5.2 跨域的处理方式
-
后端 cors 解决跨域
-
前端 + 后端 使用 jsonp 解决 get 请求跨域 (基本不用,但面试喜欢问)
-
前端使用代理服务器解决
使用 Vue-CLI 的代理服务器,配置 vue.config.js 文件
module.exports = { pages: { index: { entry: 'src/main.js' } }, lineOnSave: false, // 开启代理服务器 devServer: { proxy: { '/api1': { target: 'http://localhost:5000', pathRewrite: {'^/api1',''}, ws: true, // 用于支持 web socket changeOringin: true // 代理服务器发送数据的请求头中host端口是否和数据服务器一致 }, '/api2': { target: 'http://localhost:5001', pathRewrite: {'^/api2',''}, ws: true, changeOringin: true } } } }