1、介绍
Vue 是一套用于构建用户界面的 渐进式框架
渐进式框架的意思就是,我们可以在项目中一点点的引入和使用 Vue
而不一定要使用 Vue 来开发整个项目
Vue 的本质就是一个 JavaScript 库
1.1 CDN 引入
我们在使用 Vue 时,需要将其添加到项目中
对于初学者,可以使用 CDN 引入
CDN 是一种通过互联网相互连接的电脑网络系统
利用最靠近每位用户的服务器,更快、更可靠地将音乐、图片、视频、应用程序及其他文件
发送给用户,来提供高性能、可扩展性及低成本的网络内容传递给用户
引入方式如下:
<script src="https://unpkg.com/vue@next"></script>
1.2 命令式编程和声明式编程
命令式编程关注的是 how to do
声明式编程关注的是 what to do,由框架完成 how 的过程
Vue 就属于声明式编程
Vue.js 的核心是一个允许采用简洁的模板语法来声明式地将数据渲染进 DOM 的系统
2、模板
<div id="app"></div>
<template id="my-app">
<h2>{{message}}</h2>
</template>
<script src="../vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
message: 'Hello World !'
}
},
methods: {
// 定义方法
}
}
Vue.createApp(App).mount('#app');
</script>
下面来介绍一下模板中的内容
2.1 template
一个字符串模板,用作 component 实例的标记 ?
模板将会 替换 所挂载元素的 innerHTML
挂载元素的原有内容都将被忽略,除非模板中存在通过插槽分发的内容
分离写法和 Vue2 相同
传入的时候加 # 是因为,如果值以 # 开始,那么它将被用作 querySelector
并且使用 匹配元素 的 innerHTML 作为模板字符串
2.2 data
类型:Function
该函数返回组件实例的 data 对象
注:data 中返回的对象会被 Vue 的响应式系统劫持
之后对该对象的修改或者访问都会在劫持中被处理
2.3 methods
类型:{ [ key:string ]:Function }
通常我们会在 methods 里面定义很多方法
这些方法可以被绑定到 template 模板中
我们可以直接使用 this 关键字访问 data 中返回的对象的属性
因为方法中的 this 自动绑定为组件实例
注意:methods 中不能使用箭头函数,理由是箭头函数绑定了父级作用域的上下文
所以 this 将不会按照期望指向组件实例
2.4 Mustache
也称双大括号语法,数据绑定最常见的形式
Mustache 中不仅可以是 data 中的属性,也可以是一个 JavaScript 中的表达式
3、常见指令
3.1 v-once
只渲染元素和组件一次
随后的重新渲染,元素/组件及其所有的子节点将被视为静态内容并跳过
这可以用于优化更新性能
用法如下:
<!-- 单个元素 -->
<span v-once>This will never change: {{msg}}</span>
<!-- 有子元素 -->
<div v-once>
<h1>comment</h1>
<p>{{msg}}</p>
</div>
<!-- 组件 -->
<my-component v-once :comment="msg"></my-component>
<!-- `v-for` 指令 -->
<ul>
<li v-for="i in list" v-once>{{i}}</li>
</ul>
3.2 v-text
更新元素的文本内容
<span v-text="message"></span>
<!-- 等价于 -->
<span>{{message}}</span>
3.3 v-html
更新元素的 innerHTML
<div id="app"></div>
<template id="my-app">
<div>{{message}}</div>
<div v-html="message"></div>
</template>
<script src="../vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
message: '<span style="color:pink;">Hello World !</span>'
}
}
}
Vue.createApp(App).mount('#app');
</script>
页面效果如图:
3.4 v-pre
跳过这个元素和它的子元素的编译过程
可以用来显示原始的 Mustache 标签
跳过大量没有指令的节点会加快编译
<template id="my-app">
<h2 v-pre>{{message}}</h2>
</template>
页面效果如图:
3.5 v-cloak
可以隐藏未编译的 Mustache 标签直到组件实例准备完毕
<style>
[v-cloak] {
display: none;
}
</style>
<template id="my-app">
<h2 v-cloak>{{message}}</h2>
</template>
只有在编译结束之后 h2 里面的内容才会显示出来
3.6 v-bind
动态地绑定一个或多个 attribute,或一个组件 prop 到表达式
缩写为 :
用法如下:
<!-- 绑定 attribute -->
<img v-bind:src="imageSrc" />
<!-- 动态 attribute 名 -->
<button v-bind:[key]="value"></button>
<!-- 缩写 -->
<img :src="imageSrc" />
<!-- 动态 attribute 名缩写 -->
<button :[key]="value"></button>
<!-- 内联字符串拼接 -->
<img :src="'/path/to/images/' + fileName" />
<!-- class 绑定 -->
<div :class="{ red: isRed }"></div>
<div :class="[classA, classB]"></div>
<div :class="[classA, { classB: isB, classC: isC }]">
<!-- style 绑定 -->
<div :style="{ fontSize: size + 'px' }"></div>
<div :style="[styleObjectA, styleObjectB]"></div>
<!-- 绑定一个全是 attribute 的对象 -->
<div v-bind="{ id: someProp, 'other-attr': otherProp }"></div>
<!-- prop 绑定。"prop" 必须在 my-component 声明 -->
<my-component :prop="someThing"></my-component>
<!-- 通过 $props 将父组件的 props 一起传给子组件 -->
<child-component v-bind="$props"></child-component>
<!-- XLink -->
<svg><a :xlink:special="foo"></a></svg>
</div>
3.6.1 绑定 HTML Class
对象语法
我们可以传给 v-bind:class 一个对象,以动态地切换 class
<div :class="{ active: isActive }"></div>
所以上面 active 这个 class 是否存在 取决于 isActive 的值是否为 true
此外,v-bind:class 也可以与普通的 class 属性共存
如下:
<div
class="static"
:class="{ active: isActive, 'text-danger': hasError }"
></div>
data() {
return {
isActive: true,
hasError: false
}
}
渲染结果为:
<div class="static active"></div>
其中 static 就是静态绑定的 class,后面两个 class 则是动态绑定的
注:我们可以看到 text-danger 外面加了引号,短横线拼接的字符串就需要加引号
数组语法
我们可以把一个数组传给 v-bind:class,以应用一个 class 列表
<div :class="[activeClass, errorClass]"></div>
data() {
return {
activeClass: 'active',
errorClass: 'text-danger'
}
}
渲染结果为:
<div class="active text-danger"></div>
3.6.2 绑定内联样式
对象语法
直接上代码
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
也可以像下面这样写:
<div :style="{ color: activeColor, 'font-size': fontSize + 'px' }"></div>
css 的属性名可以用驼峰式和短横线分割来命名,值得注意的是后者要加引号
data() {
return {
activeColor: 'red',
fontSize: 30
}
}
我们也可以把上面两个样式合并到一个对象中,使模板更加清晰
<div :style="styleObject"></div>
data() {
return {
styleObject: {
color: 'red',
fontSize: '13px'
}
}
}
数组语法
v-bind:style 的数组语法可以将多个样式对象应用到同一个元素上:
<div :style="[baseStyles, overridingStyles]"></div>
3.7 v-on
用于监听 DOM 事件
缩写为 @
基本用法:
<a v-on:click="doSomething"> ... </a>
如果希望一个元素绑定多个事件,可以传入一个对象
修饰符:
- .stop - 调用 event.stopPropagation()
- .prevent - 调用 event.preventDefault()
- .capture - 添加事件侦听器时使用 capture 模式
- .self - 只有当事件是从侦听器绑定的元素本身触发时才触发回调
- .{ keyAlias } - 仅当事件是从特定键触发时才触发回调
- .once - 只触发一次回调
- .left - 只当点击鼠标左键时触发
- .right - 只当点击鼠标右键时触发
- .middle - 只当点击鼠标中键时触发
- .passive - { passive: true } 模式添加侦听器
v-on 的参数传递问题 ?
4、条件渲染
4.1 基本用法
<h1 v-if="awesome">Vue is awesome!</h1>
<!-- v-else-if="" -->
<h1 v-else>Oh no 😢</h1>
条件为 true 时才会渲染
4.2 在 template 元素上使用
因为 v-if 是一个指令,所以必须将它添加到一个元素上
但是如果想切换多个元素呢
<template v-if="ok">
<h1>Title</h1>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</template>
4.3 v-show
用于条件性展示元素的另一种方式
<h1 v-show="ok">Hello!</h1>
4.4 v-show 和 v-if 的区别
首先,在用法上的区别
v-show 是不支持和 template 一起使用
v-show 不可以和 v-else 一起使用
其次,本质的区别是
v-show 元素无论是否需要显示到浏览器上,它的 DOM 实际都是有渲染的
只是通过 CSS 的 display 属性来进行切换
v-if 条件为 false 时,其对应的元素不会被渲染到 DOM 中
在实际开发中,应该如何进行选择呢?
5、列表渲染
5.1 v-for 的基本使用
<div id="app"></div>
<template id="my-app">
<h2>电影列表</h2>
<!-- 遍历数组 -->
<ul>
<li v-for="(movie,index) in movies">{{index+1}}.{{movie}}</li>
</ul>
<!-- 遍历对象 -->
<h2>个人信息</h2>
<ul>
<!-- 第一个参数为值,第二个参数为键名,第三个参数为索引 -->
<li v-for="(value,key,index) in info">{{index+1}}.{{key}} : {{value}}</li>
</ul>
<h2>数字</h2>
<!-- 遍历数字 -->
<ul>
<li v-for="item in 5">{{item}}</li>
</ul>
</template>
<script src="../vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
// 数组
movies: ["夏洛特烦恼","星际穿越","肖申克的救赎","当幸福来敲门"],
// 对象
info: {
name: "big_orange",
age: 23,
sex: "男"
}
}
}
}
Vue.createApp(App).mount('#app');
</script>
页面效果如图:
5.2 在 <template> 中使用 v-for
<div id="app"></div>
<template id="my-app">
<ul>
<template v-for="(value,key) in info">
<li>{{key}}</li>
<li>{{value}}</li>
<li></li>
</template>
</ul>
</template>
<script src="../vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
// 对象
info: {
name: "big_orange",
age: 23,
sex: "男"
}
}
}
}
Vue.createApp(App).mount('#app');
</script>
页面效果如图:
5.3 数组更新检测
变更方法
Vue 将被侦听的数组的变更方法进行了包裹,所以它们也将会 触发视图更新
这些被包裹过的方法包括:
- push()
- pop()
- shift()
- unshift()
- splice()
- sort()
- reverse()
替换数组
某些方法不会变更原始数组,而总是返回一个新数组
比如 filter() concat() slice()
?
5.4 v-for 中 key 的作用
高效的更新虚拟 DOM
感觉还是没完全搞懂 ?
5.4.1 认识 VNode
<div class="title" style="font-size: 30px; color: pink;">Hello World</div>
对应的 VNode 为:
const vnode = {
type: "div",
props: {
class: "title",
style: {
"font-size": "30px",
color: "red",
},
},
children: "Hello World",
}
5.4.2 插入案例
现在有一个列表,里面存放着 A B C D E G
我们现在需要在中间插入一个 F
那么 Vue 内部对列表是如何进行更新的呢
有 key,使用 patchKeyedChildren 方法
没有 key,使用 patchUnkeyedChildren 方法
5.4.3 没有 key 的 diff 算法
过程大致是,取新旧 VNode 中列表长度最短的值进行遍历
对于上图来说就是取旧 VNode 列表的长度
然后挨个比较,如果值相同就下一个,如果不同就用新值覆盖旧值,节点还是继续复用
最后新的多就增加节点,旧的多就删除节点
5.4.4 有 key 的 diff 算法
这个感觉还是要看源码会清晰一些
先从头部开始比较,如果两个节点的 key 不一样的话,就跳出循环
对于上图来说就是 a 和 a 比较,b 和 b 比较,c 和 f 比较之后跳出循环
然后从尾部开始比较,如果两个节点的 key 不一样的话,就跳出循环
新节点比较多的情况下,就用空节点与其比较,然后挂载新节点
旧节点比较多的情况下,移除旧节点
如果新旧节点一样多,但中间不一样,且是无序的
它就会尽量去利用旧的节点,去找跟新的节点 key 一样的,达到复用的一个效果
旧的里面有的,新的没有,就做删除操作
旧的里面没有的,新的里面有,就做新增操作
其余的就移动位置
6、计算属性
在模板中可以通过插值语法显示 data 中的数据
但是在某些情况下,我们需要对数据进行一些转化后再显示
或者需要将多个数据结合起来进行显示
我们在模板中使用表达式,可以很方便的实现上面的需求
但是 在模板中放入太多的逻辑会让模板过重,难以维护
我们有两种方法可以将逻辑抽离出去
- methods
- computed
6.1 案例实现
为了对比这两者的区别,我们来看三个案例
6.1.1 模板语法实现
<template id="my-app">
<h2>{{firstName + lastName}}</h2>
<h2>{{score >= 60 ? "及格":"不及格"}}</h2>
<h2>{{message.split("").reverse().join("")}}</h2>
</template>
data() {
return {
message: 'Hello World !',
firstName: 'coder',
lastName: 'why',
score: 89
}
}
页面效果如图:
可以很明显的看到缺点:
- 模板中存在大量复杂的逻辑,不便于维护
- 当有多次一样的逻辑时,存在重复的代码
- 多次使用时,逻辑需要重复执行,没有缓存
6.1.2 methods 实现
<template id="my-app">
<h2>{{getFullName()}}</h2>
<h2>{{getResult()}}</h2>
<h2>{{getReverseMessage()}}</h2>
</template>
methods: {
getFullName() {
return this.firstName + " " + this.lastName;
},
getResult() {
return this.score >= 60 ? "及格":"不及格";
},
getReverseMessage() {
return this.message.split("").reverse().join("");
}
}
这种方法也是有缺点的:
- 我们想在模板上呈现的是一个结果,但是其实是方法的调用
- 多次使用时,需要重复调用方法,没有缓存
6.1.3 computed 实现
<template id="my-app">
<h2>{{fullName}}</h2>
<h2>{{result}}</h2>
<h2>{{reverseMessage}}</h2>
</template>
computed: {
fullName() {
return this.firstName + " " + this.lastName;
},
result() {
return this.score >= 60 ? "及格":"不及格";
},
reverseMessage() {
return this.message.split("").reverse().join("");
}
}
计算属性看起来像是一个函数,但是我们不需要在使用的时候加 ()
直观上看起来也很简洁,并且计算属性是有缓存的
在上面的案例实现方案中,我们会发现计算属性和 methods 的实现看起来差别不是很大
但区别还是很大的,主要表现在 methods 的每一次调用都需要重新去执行相应的函数
就好比计算 1 + 1 = 2
methods 每次都要重新算,而 computed 在执行一次运算之后就可以将结果缓存起来
下次再算的时候可以直接返回结果
6.2 计算属性的缓存
计算属性会基于它们的 依赖关系 进行缓存
在数据不发生变化时,计算属性是不需要重新计算的
但是如果 依赖的数据发生变化,在使用时,计算属性依然会 重新进行计算
6.3 计算属性的 setter 和 getter
计算属性在大多数情况下,只需要一个 getter 方法即可
对于上述的 fullName 函数,我们有一个较为完整的写法
如下:
computed: {
// fullName 的 getter 方法
// fullName: function() {
// return this.firstName + " " + this.lastName;
// }
// 计算属性的完整写法
fullName: {
get: function() {
return this.firstName + " " + this.lastName;
},
set: function(newValue) {
// 修改计算属性的值时,就会触发 set 函数,执行一些自定义的操作
}
}
}
总而言之就是,对于计算属性我们有两种写法
但是一般情况,我们只会传入一个 getter,而不是一个包含 setter 和 getter 的对象
对于传入了什么,Vue 内部会有一个逻辑判断
7、侦听器
在代码逻辑中监听数据的变化
<div id="app"></div>
<template id="my-app">
<h2>{{question}}</h2>
<button @click="btnClick">点我</button>
</template>
<script src="../vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
question: 'Hello World !'
}
},
// question 侦听的是 data 中的属性
// newValue 是变化之后的新值
// oldValue 是变化之前的旧值
watch: {
question(newValue,oldValue) {
// 可以在这里将侦听到的值打印出来
console.log(newValue,oldValue)
}
},
// 通过 methods 改变 question 中的值
methods: {
btnClick() {
return this.question = "New World !"
}
},
}
Vue.createApp(App).mount('#app');
</script>
页面效果如图:
点击之后,我们可以在控制台看到输出
表示已经监听到了变化
默认情况下,我们的侦听器只会侦听到数据本身的改变
就好比我现在的数据是一个对象,对象里面的某个值发生改变是侦听不到的
只有当整个对象被重新赋值时才能侦听到
<div id="app"></div>
<template id="my-app">
<h2>{{info.name}}</h2>
<button @click="changInfoName()">点我</button>
</template>
<script src="../vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
info: {
name: "why",
age: 18
}
}
},
watch: {
// 传入一个对象
// info(newInfo,oldInfo) {
// console.log("newValue:",newInfo,"oldValue:",oldInfo);
// }
// 这种写法是下面 handler 的语法糖形式
info: {
handler: function(newInfo,oldInfo) {
console.log("newValue:",newInfo,"oldValue:",oldInfo);
},
// 深度侦听
deep: true,
// 立即执行,不管数据有没有发生改变,先侦听一次
immediate: true
},
// 还有一种写法
// 如果我们只想侦听对象中的某一个值的话,不使用深度侦听的办法
// "info.name": function(newName,oldName) {
// console.log(newName,oldName)
}
},
methods: {
changInfoName() {
this.info.name = "orange"
}
},
}
Vue.createApp(App).mount('#app');
</script>
上诉代码中,info 里面的 name 发生改变是侦听不到的
为了侦听到数据内部的改变,我们可以使用深度侦听
即设置 deep 的值为 true
这个时候我们就可以对内部数据进行监听
还有一种监听的方式就是使用 $watch 的 API ?
8、v-model
在表单控件或者组件上创建双向绑定
8.1 v-model 的本质
v-model 的内部其实是包含两个操作的
<div id="app"></div>
<template id="my-app">
<h2>{{message}}</h2>
<!-- 1、v-bind 绑定 value 2、监听 input 事件,更新 message 的值 -->
<!-- <input type="text" :value="message" @input="inputChange"> -->
<!-- 语法糖形式 -->
<input type="text" v-model="message">
</template>
<script src="../vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
message: 'Hello World !'
}
},
methods: {
// inputChange(event) {
// this.message = event.target.value;
// }
},
}
Vue.createApp(App).mount('#app');
</script>
8.2 基础用法
<!-- 1.绑定 textarea -->
<label for="intro">
自我介绍
<textarea name="intro" id="intro" cols="30" rows="10" v-model="intro"></textarea>
</label>
<h2>intro: {{intro}}</h2>
<!-- 2.checkbox -->
<!-- 2.1.单选框 -->
<label for="agree">
<input id="agree" type="checkbox" v-model="isAgree"> 同意协议
</label>
<h2>isAgree: {{isAgree}}</h2>
<!-- 2.2.多选框 -->
<span>你的爱好: </span>
<label for="basketball">
<input id="basketball" type="checkbox" v-model="hobbies" value="basketball"> 篮球
</label>
<label for="football">
<input id="football" type="checkbox" v-model="hobbies" value="football"> 足球
</label>
<label for="tennis">
<input id="tennis" type="checkbox" v-model="hobbies" value="tennis"> 网球
</label>
<h2>hobbies: {{hobbies}}</h2>
<!-- 3.radio -->
<span>你的性别: </span>
<label for="male">
<input id="male" type="radio" v-model="gender" value="male">男
</label>
<label for="female">
<input id="female" type="radio" v-model="gender" value="female">女
</label>
<h2>gender: {{gender}}</h2>
<!-- 4.select -->
<span>喜欢的水果: </span>
<select v-model="fruit" multiple size="2">
<option value="apple">苹果</option>
<option value="orange">橘子</option>
<option value="banana">香蕉</option>
</select>
<h2>fruit: {{fruit}}</h2>
data() {
return {
intro: "Hello World",
isAgree: false,
hobbies: ["basketball"],
gender: "",
fruit: "orange"
}
}
?
8.3 修饰符
8.3.1 .lazy
<!-- 在 change 时而非 input 时更新 -->
<input v-model.lazy="msg" />
比如说,我们在文本框中输入信息,不想实时同步
想在回车之后再同步数据,可以给 v-model 添加 lazy 修饰符
8.3.2 .number
如果想自动将用户的输入值转为数值类型,可以给 v-model 添加 number 修饰符
<input v-model.number="age" type="number" />
8.3.3 .trim
如果要自动过滤用户输入的首尾空白字符,可以给 v-model 添加 trim 修饰符
<input v-model.trim="msg" />
9、组件基础
- 全局注册
- 局部注册
9.1 注册全局组件
<div id="app"></div>
<template id="my-app">
<h2>{{message}}</h2>
<!-- 使用组件 -->
<my-component></my-component>
</template>
<!-- 组件模板 -->
<template id="my-component">
<h2>我是组件</h2>
<p>我是组件内容</p>
</template>
<script src="../vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
message: 'Hello World !'
}
}
}
const app = Vue.createApp(App);
// 注册全局组件
app.component('my-component',{
template: '#my-component',
data() {
return {
message: "我是组件"
}
}
})
app.mount('#app')
</script>
9.2 注册局部组件
<div id="app"></div>
<template id="my-app">
<h2>{{message}}</h2>
<component-a></component-a>
</template>
<!-- 组件模板 -->
<template id="component-a">
<h2>我是组件</h2>
<p>我是组件内容</p>
</template>
<script src="../vue.js"></script>
<script>
// 定义一个组件
const ComponentA = {
template: '#component-a'
}
const App = {
template: '#my-app',
// 局部注册,只能在 App 里面使用
components: {
// key: value
// 组件名称:组件对象
ComponentA
},
data() {
return {
message: 'Hello World !'
}
}
}
Vue.createApp(App).mount('#app');
</script>