「这是我参与2022首次更文挑战的第2天,活动详情查看:2022首次更文挑战」。
1. Vue 入门简介
随着前端技术的不断发展,前端开发能够处理的业务也越来越多,网页也变得越来越强大与动态化,这些进步始终离不开 JavaScript 代码,它连接着各式各样的 HTML 与 CSS 文件,但是缺乏正规的组织形式。所以衍生出了许多 JavaScript 框架: Vue、React、Angular!
为什么是 Vue:
- 易学易用:只需学会 HTML、CSS、JavaScript 三件套即可快速上手
- 高效:运行速度快,轻量级框架
- 响应式与组件化开发:实时监听数据;组件复用
⭐官方简介:Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。
MVVM
🌤在你真正明白 MVVM 后,就该转变思想来学习 Vue 了,不要在想着怎么操作 DOM(那是 JS 干的事),而是应该想着如何操作数据!!!
在理解 MVVM 之前,着重提一下 vue 的特性:
- 数据驱动视图:vue 会监听数据的变化,从而自动重新渲染页面结构的单项数据绑定。
- 双向数据绑定:在填写表单时,双向数据绑定可以辅助开发者在不操作 DOM 的前提下,自动把用户填写的内容同步到数据源中(即下文的
v-model的实现效果)。
MVVM 是 vue 实现数据驱动视图和双向数据绑定的核心原理。MVVM 指的是 Model、View、ViewModel,它把每个 HTML 页面都拆分成了这三个部分,如图所示:
MVVM 示意图
- Model: 当前页面渲染时所以来的数据源
- View: 当前页面所渲染的 DOM 结构
- ViewModel: 表示 vue 的示例,它是 MVVM 的核心,把当前页面的数据源(Model)和页面结构(View)连接在了一起
当数据源 data 发生变化时,会被 ViewModel 监听到,VM 会根据最新的数据源自动更新页面结构。
当表单元素值发生变化时,也会被 VM 监听到,VM 会把变化过后最新的值自动同步到 Model 数据源中。
现在看不懂没关系,等你慢慢往下学习,某一天就会恍然大悟。
2. 快速起步
2.1 引入 vue.js
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<!-- 生产环境版本,优化了尺寸和速度 -->
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
2.2 HelloWorld 初体验
<!-- 使用el控制如下div,将数据填充到div内部 -->
<div id='app'>{{ Hello }}</div>
<!-- 1.导入 vue 的文件库,在 window 全局就有了 Vue 这个构造函数 -->
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<!-- 2.创建 Vue 实例对象! -->
<script>
var vm = new Vue({
// el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
el: '#app',
// data 对象就是要渲染到页面上的数据
data: {
hello: 'Hello,World!'
}
})
</script>
运行结果:
如果网速较慢,会遇到插值闪烁的效果:
我们已经成功创建了第一个 Vue 应用!看起来这跟渲染一个字符串模板非常类似,但是 Vue 在背后做了大量工作。
3. Vue 常用指令
指令的 vue 为开发者提供的模板语法,用于辅助开发者渲染页面的基本结构。
vue 中的指令可以按照不同用途分为以下 6 大类:
- 内容渲染指令:
v-text、v-html、{{}} - 属性绑定指令:
v-bind - 事件绑定指令:
v-on - 条件渲染指令:
v-if、v-else-if、v-else、v-show - 列表渲染指令:
v-for - 双向绑定指令:
v-model
你可能会觉得指令很复杂,其实不然,指令是 vue 开发中最基础、最简单、最常用的知识点了。接下来一一详细介绍这些指令。
3.1 v-text
指令 v-text 只能将 data 中的数据以文本的形式渲染到视图上,这就是区别于 v-html 的点 (下文讲解)。
注:如果标签内有内容,仍会被
v-text渲染的文本所覆盖。
<div id="app">
<div v-text='msg'></div>
<div v-text='msg'>Hello Vue!</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
var vm = new Vue({
el: "#app",
data: {
msg: '半截的詩'
}
})
</script>
运行结果:
3.2 {{ }}
vue 提供的 {{ }} 语法,专门用于解决 v-text 覆盖默认文本内容的问题。同时,插值表达式支持 JS 表达式。
专业术语:插值表达式 (Mustache)
相对 v-text 指令而言,插值表达式在开发中更常用一些!
<div id="app">
<li>这是第 {{ index }} 个 box</li>
<li>这是第 {{ index + 1 }} 个 box</li>
<div>反转前:{{ msg }}</div>
<div>反转后:{{ msg.split('').reverse().join('') }}</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
var vm = new Vue({
el: "#app",
data: {
index: 1,
msg: 'Hello Vue!'
}
})
</script>
运行结果:
3.3 v-html
v-text 指令与插值表达式只能渲染纯文本内容。如果要把包含 HTML 标签的字符串渲染为页面的 HTML 元素,则需要使用 v-html 指令:
<div id="app">
<div v-html='msg'></div>
<div v-html='info'></div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
var vm = new Vue({
el: "#app",
data: {
msg: `<div style="color:red; font-weight:bold">I'm Chinese!</div>`,
info: 'v-html渲染'
}
})
</script>
运行结果:
3.4 v-bind
如果需要为元素的属性动态绑定属性值,则可以用 v-bind 属性绑定指令。
基础用法
简单演示下为 <a> 链接动态绑定 href 属性,若 GitHub 主页地址发生变化,修改 address 数据即可。
<div id="app">
<a v-bind:href='address'>GitHub主页</a>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
var vm = new Vue({
el: '#app',
data: {
address: 'http://github.com/Wu-yikun'
}
})
</script>
运行结果:
v-bind 缩写语法
<!-- 完整语法 -->
<a v-bind:href="url">...</a>
<!-- 缩写 -->
<a :href="url">...</a>
绑定对象
Class 与 Style 的绑定
操作元素的 class 列表与内联样式 style 是常见的两种需求。我们可以通过 v-bind 对 class 和 style 进行操作。
Vue.js 为此进行了增强,v-bind 绑定的表达式除了字符串以外,还可以是对象或数组。
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue</title>
<style>
.active {
color: tomato;
}
.boldSty {
font-weight: bold;
}
</style>
</head>
<body>
<div id="app">
<div :class="{ active: isActive, boldSty: 'true' }">我爱我的国,我爱我的家</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
var vm = new Vue({
el: '#app',
data: {
isActive: true
}
})
</script>
</body>
渲染结果:
<div id="app">
<div class="active boldSty"></div>
</div>
运行结果:
⭐绑定的数据对象不必内联在模板里:
<div id="app">
<div v-bind:class="classObject"></div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
var vm = new Vue({
el: '#app',
data: {
classObject: {
active: true,
'text-danger': false
}
}
})
</script>
style 内联样式的绑定方式也同理,无非就是将 true/false 变成具体的样式对应的属性值!
<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
data: {
activeColor: 'red',
fontSize: 30
}
绑定数组
Class 与 Style 的绑定
如上说了,v-bind 可以绑定对象或数组,这里演示绑定数组:
<div id="app">
<div :class="[activeClass, boldClass]">我爱我的国,我爱我的家</div>
<!-- 加引号''表示直接赋值 -->
<div :class="['active', 'boldSty']">我爱我的国,我爱我的家</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
var vm = new Vue({
el: '#app',
data: {
activeClass: 'active',
boldClass: 'boldSty'
}
})
</script>
style 同理!
3.5 v-on
vue 提供的事件绑定指令 v-on,用于为 DOM 元素绑定事件监听。
通过 v-on 绑定的事件处理函数,需要在 methods 节点中进行声明。
原生 DOM 对象有
onclick、oninput、onkeyup等,替换为对应的v-on:click、v-on:inpupt、v-on:keyup。
基础用法
<div id="app">
<button v-on:click='reduce'>-1</button>
{{ num }}
<button v-on:click='add'>+1</button>
<button @click="print('半截的詩')">打印我的名字</button>
{{name}}
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
var vm = new Vue({
el: '#app',
data: {
num: 1,
name: ''
},
methods: {
add() {
this.num++
},
reduce() {
this.num--
},
print(username) {
this.name = username
}
},
})
</script>
运行结果:
v-on 缩写语法
<!-- 完整语法 -->
<button v-on:click='add'>+1</button>
<!-- 缩写 -->
<button @click='add'>+1</button>
$event
<div id="app">
<p>number: {{ num }}</p>
<!-- 显式传入 -->
<button @click='sub($event)'>-1</button>
<!-- 隐式传入 -->
<button @click='add'>+1</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
var vm = new Vue({
el: "#app",
data: {
num: 1
},
methods: {
// 调用该函数时没传参,则该实参为 event 对象
add(event) {
console.log(event);
this.num++
// 根据奇偶改变 button 的颜色(button 为事件源)
if (this.num % 2 === 0) {
event.target.style.backgroundColor = 'tomato'
} else {
event.target.style.backgroundColor = 'lightBlue'
}
},
sub(e) {
console.log(e);
this.num--
}
}
})
</script>
运行结果:
事件修饰符
在事件处理程序中,event.preventDefault() 与 event.stopPropagation() 是非常常见的需求。
因此 Vue.js 为 v-on 提供了事件修饰符:
.stop:阻止冒泡,调用event.stopPropagation().prevent:阻止默认行为,调用event.preventDefault().capture:添加事件侦听器时使用事件捕获模式.self:只当事件在该元素本身(比如不是子元素)触发时,才会触发事件.once:事件只触发一次
修饰符在下一篇文章中详细介绍。
3.6 v-if
v-if 是条件渲染指令中的一种,至于它的用意显而易见。
<div id="app">
<span v-if='flag'>我出现了!</span>
<button @click='changeStatus'>{{ msg }}</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
var vm = new Vue({
el: '#app',
data: {
flag: false,
msg: '显示'
},
methods: {
changeStatus() {
if (!this.flag) {
this.flag = true
this.msg = '隐藏'
} else {
this.flag = false
this.msg = '显示'
}
}
},
})
</script>
运行结果:
注:v-if 还可以搭配 v-else 与 v-else-if 一起使用。
<div id="app">
<div v-if="type === 'A'">优秀</div>
<div v-else-if="type === 'B'">良好</div>
<div v-else-if="type === 'C'">及格</div>
<div v-else>不及格</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
var vm = new Vue({
el: "#app",
data: {
type: 'A'
}
})
</script>
运行结果:
3.7 v-show
v-show 则是条件渲染指令中的另一种,它与 v-if 的细微区别在于:
- 实现原理不同
v-if指令会动态创建或移除 DOM 元素v-show指令会动态为元素添加或移除style="display: none;"样式
- 性能消耗不同:
v-if有更高的切换开销,而v-show有更高的初始渲染开销(不过在如今计算机的加持下,这两者在性能上几乎没有差别)
<div id="app">
<span v-if='flag1'>v-if 出现!</span>
<button @click='changeStatus1'>v-if</button>
<br>
<span v-show='flag2'>v-show 出现!</span>
<button @click='changeStatus2'>v-show</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
var vm = new Vue({
el: '#app',
data: {
flag1: true,
flag2: true
},
methods: {
changeStatus1() {
if (!this.flag1) {
this.flag1 = true
} else {
this.flag1 = false
}
},
changeStatus2() {
if (!this.flag2) {
this.flag2 = true
} else {
this.flag2 = false
}
}
},
})
</script>
运行结果:
3.8 v-for
v-for 指令可用于遍历数组,数组内可以是普通元素或对象。
<div id="app">
<ul>
<li v-for='(item, index) in list' :key='item.id'>序号{{ index }} : {{ item.name }} --- {{ item.age }}</li>
</ul>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
var vm = new Vue({
el: '#app',
data: {
list: [
{ id: 1, name: '张三', age: 18 },
{ id: 2, name: '李四', age: 19 },
{ id: 3, name: '半截的詩', age: 21 }
]
}
})
</script>
运行结果:
⭐关于 v-for 指令有几个注意点:
- 官方建议一旦使用
v-for指令,那么就要绑定:key属性,且key的值不能重复 (否则报错:Duplicate keys detected),因为 key 的作用是为每一个节点做唯一标识 (item, index)的顺序不能错位- 不要用
index作为key
接下来展开说说~
⭐v-for 为什么必须用 key,且不能使用 index 作为 key?
因为 vue 组件高度复用,增加 key 可以标识组件的唯一性,key 的作用主要是为了高效地更新虚拟 DOM。
举个栗子:
const list = [
{ id: 1, name: 'test1' },
{ id: 2, name: 'test2' }
]
如果要在该列表的末尾插入一条数据,那么使用 index 作为 key 完全没有问题,因为此时前三条数据会直接复用之前的数据,然后再新渲染插入的最后一条数据:
const list = [
{ id: 1, name: 'test1' },
{ id: 2, name: 'test2' },
{ id: 3, name: '新数据' }
]
但是如果在列表的中间插入呢?
const list = [
{ id: 1, name: 'test1' },
{ id: 3, name: '新数据' },
{ id: 2, name: 'test2' }
]
此时更新渲染数据后,通过 index 定义的 key 去对比前后数据,发现:
之前的数据 之后的数据
key: 0 index: 0 name: test1 key: 0 index: 0 name: test1
key: 1 index: 1 name: test2 key: 1 index: 1 name: 新数据
key: 2 index: 2 name: test2
可以发现除了第一个数据以外,另外的两条数据都要重新渲染,起不到复用的效果。
这就会达到只插入一条数据,却要重新渲染两条数据的荒谬结果。
最好的办法就是每条数据都有唯有的 id 来标识该数据的唯一性,使用 id 作为 key 值,此时渲染的结果如下:
之前的数据 之后的数据
key: 0 index: 0 name: test1 key: 1 id: 1 index: 0 name: test1
key: 1 index: 1 name: test2 key: 3 id: 3 index: 1 name: 新数据
key: 2 id: 2 index: 2 name: test2
现在对比发现只有一条数据发生了改变,只要渲染新插入的数据即可,其余数据皆复用。
⭐其实底层原理就是虚拟 DOM 的 Diff 算法。
插入节点
没有 key 时,diff 算法的执行过程:
只是把 C 更新成 F,D 更新成 C,E 更新成 D,最后再插入 E,是不是很没有效率?
所有我们需要使用 key 来给每一个节点做唯一标识,diff 算法就可以正确识别该节点,找到正确的位置区域插入新节点。
无 key VS 有 key
对于 diff 算法:在插入节点时,有/无 key 就好比链式存储结构/顺序存储结构对插入节点执行的操作。
更多关于 diff 算法的描述:juejin.cn/post/684490…
⭐小结
所以经过以上解释,我们明确了几个点。
- 用组件唯一的
id作为它的key - 如果列表顺序会发生改变,别用
index作为key,否则会导致 Vue 复用错误的旧子节点,从而多做许多额外的工作 - 千万别用随机数作为
key,不然会导致旧节点被全部删除,新节点也重新创建
3.9 v-model
我们可以使用 v-model 指令在表单 <input>、<select>、<textarea> 元素上创建双向数据绑定。
基础用法
<div id="app">
<input type="text" v-model='message'>
<div> {{ message }} </div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
var vm = new Vue({
el: '#app',
data: {
message: '双向绑定数据'
}
})
</script>
运行结果:
修饰符
.lazy:改变输入框的值时,绑定的 lazyValue 不会改变,当失去焦点时v-model绑定的 lazyValue 才会发生改变.number:将值转化为数字.trim:去除首尾空格
修饰符在下一篇文章中详细介绍。
Vue 双向绑定的极简实现【Vue 响应式原理】
既然都讲到 v-model 了,我们来看看简单感受下底层是如何实现其双向绑定的效果的。
🎨原理:Object.defineProperty 中的 set 与 get 方法,,通过 getter 和 setter 访问器指定 读取或设置 对象属性值的时候,执行相应操作。
// defineProperty 语法介绍
var obj = {}
Object.defineProperty(obj, 'msg', {
set: function (newVal) {
console.log('set: ' + newVal);
},
get: function () {
console.log('旧的不去,新的不来..');
}
})
obj.msg = '1'
obj.msg
👑接下来就是极简双向绑定的实现:
<!-- 示例 -->
<input type="text" id="txt"><br/>
<span id="sp"></span>
<script>
var txt = document.getElementById('txt')
var sp = document.getElementById('sp')
obj = {}
// obj.msg => input、span
Object.defineProperty(obj, 'msg', {
// 当 obj.msg 变化时调用set方法
set: function (newVal) {
// 当 obj.msg 被赋值时, 同时设置给span与input
txt.value = newVal
sp.innerText = newVal
}
})
// 监听文本框的改变, 当文本框输入内容时改变 obj.msg
txt.addEventListener('keyup', function (event) {
obj.msg = event.target.value
})
</script>
看看效果:
🥣动态添加数据的注意点:
- Vue 中只有
data中的数据才是响应式的,动态添加进来的数据默认为非响应式 - 可以通过以下方法实现动态添加响应式数据
Vue.set(object, key, value):适用于添加单个属性Object.assign():适用于添加多个属性
var vm = new Vue({
el: '#app',
data: {
info: {
name: '半截的詩',
age: 21
}
}
})
/* Vue.set */
Vue.set(vm.info, 'gender', 'male')
/* Object.assign: 将参数中所有对象属性和值合并到第一个参数, 并返回合并后的对象 */
Object.assign({}, vm.info, { gender: 'female', height: '170+' })
关于 Vue 双向绑定原理推荐几篇不错的博客
❤️/ END / 如果本文对你有帮助,点个「赞」支持下吧。