一、Vue模板(插值、指令)
1.Mustache
插值表达式 {{}}
数据绑定最常见的形式就是使用 “Mustache” 语法(双大括号)的文本插值。例如:
<span>Message: {{ msg }}</span>
Mustache 标签将会被替代为对应数据对象上 msg 属性(msg定义在data对象中)的值。
无论何时,绑定的数据对象上 msg 属性发生了改变,插值处的内容都会自动更新。
语法 | {{}} JavaScript 表达式支持 |
---|---|
普通表达式 | {{ number + 1 }} |
三元表达式 | {{ ok ? 'YES' : 'NO' }} |
三元表达式 | {{ name == 'smyhvae' ? 'true' : 'false' }} |
调用方法 | {{ message.split('').reverse().join('') }} |
2.v-once
该指令后面不需要跟任何表达 该指令表示元素只渲染一次,不会随着数据的改变而改变。
<h2 v-once>{{message}}</h2>
3.v-html
注:会有XSS风险,会覆盖子组件
解析数据中的html代码,渲染到页面中
<h2 v-html="url"></h2>
4.v-text
v-text作用和Mustache比较相似:都是用于将数据显示在界面中, 不同的是 v-text 是写在属性中 v-text通常情况下,接收一个string类型
<h2 v-text="text"></h2>
5.v-pre
v-pre用于跳过这个元素和它子元素的编译过程,用于显示原本的Mustache语法。
<h2 v-pre>{{message}}</h2>
6.v-cloak
在某些情况下,我们浏览器可能会直接显然出未编译的Mustache标签。 v-cloak指令和CSS 规则一起用的时候,能够解决插值表达式闪烁的问题(即:可以隐藏未编译的标签直到实例准备完毕)
<!-- 在vue解析之前, div中有一个属性v-cloak
在vue解析之后, div中没有一个属性v-cloak -->
<div id="app" v-cloak>
<h2>{{message}}</h2>
</div>
总结
- Mustache: {{}}语法, 可以写变量/逻辑表达式/计算值...
- v-once : 元素只渲染一次,不会随着数据的改变而改变。
- v-html="" : 解析数据中的html代码,渲染到页面中
- v-text="msg": 写在属性当中, 将数据显示在界面中
- v-pre: 原文输出, 显示原本的Mustache语法
- v-cloak : 一般配合CSS规则一起使用, 够解决插值表达式闪烁的问题
Vue模板例子🌰:codesandbox.io/s/vuebaseus…
<template>
<div>
<p>文本插值 {{ message }}</p>
<p>JS 表达式 {{ flag ? "yes" : "no" }} (只能是表达式,不能是 js 语句)</p>
<p :id="dynamicId">动态属性 id</p>
<p v-html="rawHtml">
<span>v-html:有 xss 风险</span>
<span>【注意】使用 v-html 之后,将会覆盖子元素</span>
</p>
</div>
</template>
<script>
export default {
data() {
return {
message: "hello vue",
flag: true,
rawHtml: "指令 - 原始 html <b>加粗</b> <i>斜体</i>",
dynamicId: `id-${nDate.now()}`,
};
},
};
</script>
二、computed和watch
- computed有缓存,data不变则不会重新计算
- watch默认是浅监听,如何深度监听?
- watch监听引用类型,拿不到oldVal
1.computed计算属性
- 计算属性出现的目的是解决模板中放入过多的逻辑会让模板过重且难以维护的问题
- 计算属性是基于它们的响应式依赖进行缓存的
- 在某些情况,我们可能需要对数据进行一些转化后再显示,或者需要将多个数据结合起来进行显示
- 比如我们有
firstName
和lastName
两个变量,我们需要显示完整的名称。 - 但是如果多个地方都需要显示完整的名称,我们就需要写多个
{{firstName}} {{lastName}}
- 比如我们有
computed例子🌰:codesandbox.io/s/vuebaseus…
<template>
<div>
<!-- - 计算属性出现的目的是解决模板中放入过多的逻辑会让模板过重且难以维护的问题
- 计算属性是基于它们的响应式依赖进行缓存的
- 在某些情况,我们可能需要对数据进行一些转化后再显示,或者需要将多个数据结合起来进行显示
- 比如我们有`firstName`和`lastName`两个变量,我们需要显示完整的名称。
- 但是如果多个地方都需要显示完整的名称,我们就需要写多个`{{firstName}} {{lastName}}` -->
<h2>{{ fullName1 }}</h2>
<h2>{{ fullName2 }}</h2>
<p>num {{ num }}</p>
<p>double1 {{ double1 }}</p>
<input v-model="double2" />
</div>
</template>
<script>
export default {
data() {
return {
num: 10,
firstName: "晓雪",
lastNmae: "小圆脸儿",
};
},
computed: {
double1() {
// computed有缓存,data不变则doubles不会重新计算
return this.num * 2;
},
// 深度计算
double2: {
get() {
return this.num * 2;
},
set(val) {
this.num = val / 2;
},
},
fullName1: function () {
return this.firstName + " " + this.lastNmae;
},
fullName2: {
set(newVal) {
console.log("---调用了fullName的set---");
console.log("---调用了fullName的set---", newVal);
let arr = newVal.split(" ");
this.firstName = arr[0];
this.lastNmae = arr[1];
},
get() {
console.log("---调用了fullName的set---");
return this.firstName + " " + this.lastNmae;
},
},
},
};
</script>
结果:
methods和computed区别
methods
里面的数据不管发没发生变化, 只要调用了都会执行函数(有的时候数据没发生变化我们不希望调用函数)computed
计算属性会进行缓存, 如果数据没发生变化,函数只会被调用一次(数据发生变化才会调用函数)
总结:
methods
不管数据发没发生变化都会调用函数computed
只有在依赖数据发生变化时才回调函数
2.侦听器 watch
侦听器的应用场景:数据变化时执行
异步
或开销较大的操作
(eg:我们可以使用watch来进行路由的监听)注意: watch 中的属性,一定是data 中 已经存在的数据
watch例子🌰:codesandbox.io/s/vuebaseus…
// 侦听器的应用场景:数据变化时执行`异步`或`开销较大的操作`
// **注意**: watch 中的属性,一定是data 中 已经存在的数据
<template>
<div>
<p>fullName:{{ fullName }}</p>
<p>FirstName: <input type="text" v-model="firstName" /></p>
<p>LastName: <input type="text" v-model="lastName" /></p>
name:<input v-model="name" /> city:<input v-model="info.city" />
</div>
</template>
<script>
export default {
data() {
return {
name: "小圆脸儿",
info: {
city: "北京",
},
firstName: "晓雪",
lastName: "小圆脸儿",
fullName: "晓雪 小圆脸儿",
};
},
watch: {
name(oldVal, val) {
console.log("watch name", oldVal, val);
// 值类型,可正常拿到 oldVal 和 val
},
// handle就是你watch中需要具体执行的方法
// deep:就是你需要监听的数据的深度,一般用来监听对象中某个属性的变化
// immediate:在选项参数中指定 immediate: true 将立即以表达式的当前值触发回调:
// info: {
// handler(oldVal, val) {
// console.log("watch info", oldVal, val);
// // watch info {city: "北京11"}{city: "北京11"}
// // 引用类型,拿不到 oldVal 。因为指针相同,此时已经指向了新的 val
// },
// deep: true, // 深度监听
// //deep的意思就是深入观察,监听器会一层层的往下遍历,给对象的所有属性都加上这个监听器,但是这样性能开销就会非常大了,任何修改obj里面任何一个属性都会触发这个监听器里的 handler。
// },
// 优化,我们可以是使用字符串形式监听。
"info.city": {
handler(oldVal, val) {
console.log("watch info", oldVal, val);
},
immediate: true,
},
// firstName(val) {
// // val: 表示变化后的值
// this.fullName = val + " " + this.lastName;
// },
// watch 的一个特点是,最初绑定的时候是不会执行的,要等到 firstName 改变时才执行监听计算。那我们想要一开始就让他最初绑定的时候就执行改怎么办呢?
// 我们需要修改一下我们的 watch 写法,修改过后的 watch 代码如下:
firstName: {
handler(newName, oldName) {
this.fullName = newName + " " + this.lastName;
},
// 代表在wacth里声明了firstName这个方法之后立即先去执行handler方法
immediate: true,
},
lastName(val) {
this.fullName = this.firstName + " " + val;
},
},
};
</script>
结果:
三、class和style
使用动态属性
使用驼峰式写法
class与style如何动态绑定?
class和style🌰:codesandbox.io/s/vuebaseus…
Class 可以通过对象语法和数组语法进行动态绑定:
- 对象语法:对象语法的含义是
:class
后面跟的是一个对象。
<div v-bind:class="{ active: isActive, 'text-danger': hasError }"></div>
<p :class="{ black: isBlack, yellow: isYellow }">使用 class</p>
data: {
isActive: true,
hasError: false,
isBlack: true,
isYellow: true,
}
<style scoped>
.black {
background-color: #999;
}
.yellow {
color: yellow;
}
</style>
- 数组语法:数组语法的含义是
:class
后面跟的是一个数组。
<div v-bind:class="[isActive ? activeClass : '', errorClass]"></div>
<p :class="[black, yellow]">使用 class (数组)</p>
data: {
activeClass: 'active',
errorClass: 'text-danger',
black: 'black',
yellow: 'yellow',
}
<style scoped>
.black {
background-color: #999;
}
.yellow {
color: yellow;
}
</style>
Style 也可以通过对象语法和数组语法进行动态绑定:
- 对象语法:
style
后面跟的是一个对象类型- 对象的
key
是css属性名 - 对象的
value
是具体赋的值, 可以来自data
中的属性
<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
<p :style="styleData">使用 style</p>
data: {
activeColor: 'red',
fontSize: 30
}
- 数组语法:
style
后面跟的是一个数组类型, 多个值以 , 分隔
<div v-bind:style="[styleColor, styleSize]"></div>
data: {
styleColor: {
color: 'red'
},
styleSize:{
fontSize:'23px'
},
styleData: {
fontSize: '40px', // 转换为驼峰式
color: 'red',
backgroundColor: '#ccc' // 转换为驼峰式
}
}
结果:
四、条件
V-if v-else 的用法,可使用变量,也可以使用===表达式
v-if和v-show的区别?
1.vi-f与v-else-if与v-else
- Vue的条件指令可以根据表达式的值在DOM中渲染或销毁元素或组件
<h2 v-if="score>=90">优秀</h2>
<h2 v-else-if="score>=80">良好</h2>
<h2 v-else-if="score>=60">及格</h2>
<h2 v-else>不及格</h2>
- 逻辑较多不建议在模板中使用
v-if-else-if
2.复用元素渲染问题
一个问题: 涉及到了Vue底层, 虚拟DOM virtual DOM 点击切换表单后,input的value值并没有被清空,为什么?
- 引出: 当实现点击按钮切换
input
表单时, 我们输入上的value
, 点击按钮切换表单时会发现value
值还存在, 但是input
元素确实切换了, 这是因为什么呢?
<span v-if="isUser">
<label for="user">用户名</label>
<input type="text" placeholder="用户名" id="user" key="user">
</span>
<span v-else>
<label for="email">邮箱</label>
<input type="text" placeholder="邮箱" id="email" key="email">
</span>
<button @click="isUser=!isUser">切换类型</button>
<script>
const app = new Vue({
el: '#app',
data: {
isUser: true
}
})
</script>
- 原因:
- 这是因为Vue在进行DOM渲染时, 出于性能考虑, 会尽可能复用已经存在的元素, 而不是创建新的元素
- 上面的案例中, Vue内部会进行对比发现两部分都相似只会替换属性, 不会给你创建全新的元素
- 上面 if 的 input不再使用, 直接作为 else 的 input来使用
- 解决方案
- 如果我们不希望
Vue
出现类似重复利用的问题, 可以给对应的input
添加key
- 并且保证要们需要的
key
不同, 这样vue
就会创建一个全新input
元素
- 如果我们不希望
3.v-show
v-show的用法和v-if非常相似,也用于决定一个元素是否渲染
- v-if 和 v-show对比
- v-if 当条件为false时,压根不会有对应的元素在DOM中
- v-show 当条件为false时, 仅仅是将元素的 display 属性设置 none 而已
- 开发中如何选择呢?
- 当需要在显示与隐藏之间切换很频繁时,使用v-show
- 当只有一次切换时,通过使用v-if
<h2 v-show="isShow">{{message}}</h2>
条件的例子🌰:codesandbox.io/s/vuebaseus…
<template>
<div>
<p v-if="type === 'a'">A</p>
<p v-else-if="type === 'b'">B</p>
<p v-else>other</p>
<p v-show="type === 'a'">A by v-show</p>
<p v-show="type === 'b'">B by v-show</p>
</div>
</template>
<script>
export default {
data() {
return {
type: "a",
};
},
};
</script>
结果:
五、循环(列表)渲染
- 如何遍历对象?
- key的重要性:key不能乱写(如random或者index),why?
- v-for和v-if不能一起使用.why?
1.v-for遍历数组
- 作用:根据数组中的元素遍历指定模板内容生成内容。
- 语法:
v-for="(item, index) in listArr"
<p>遍历数组</p>
<ul>
<!-- item: 是每一项元素 index: 下标/索引 -->
<li v-for="(item, index) in listArr" :key="item.id">
{{index}} - {{item.id}} - {{item.title}}
</li>
</ul>
2.v-for遍历对象
- 作用: 遍历对象
- 语法:
v-for="(value, key, index) in listObj"
<p>遍历对象</p>
<ul >
<!-- value:属性值 key:属性名 index:下标/索引 -->
<li v-for="(val, key, index) in listObj" :key="key">
{{index}} - {{key}} - {{val.title}}
</li>
</ul>
3.Vue中Key属性
- 官方推荐我们在使用
v-for
时,给对应的元素或组件添加上一个:key
属性。 - 为什么需要这个key属性呢(了解) ?
- 这个其实和Vue的虚拟DOM的
Diff
算法有关系。 - 当某一层有很多相同的节点时,也就是列表节点时,我们希望插入一个新的节点
- 我们希望可以在B和C之间加一个F,Diff算法默认执行起来是这样的。
- 即把C更新成F,D更新成C,E更新成D,最后再插入E,是不是很没有效率?
- 所以我们需要使用key来给每个节点做一个唯一标识
- Diff算法就可以正确的识别此节点
- 找到正确的位置区插入新的节点
- key的作用主要是为了高效的更新虚拟DOM
- 这个其实和Vue的虚拟DOM的
使用
v-for
更新已渲染的元素列表时,默认用就地复用策略; 如果列表数据修改的时候, 它会根据key值去判断某个值是否修改, 如果修改, 则重新渲染这一项, 否则复用之前的元素; 我们在使用的使用经常会使用index
(即数组的下标)来作为key
,但其实这是不推荐的一种使用方法;
4.v-if 和 v-for
当它们处于同一节点,
v-for
的优先级比v-if
更高,这意味着
v-if
将分别重复运行于每个v-for
循环中。
避免 v-if
和 v-for
用在一起
-
原因
- 如果使用了 if 判断, 每次渲染在Vue内部都会遍历整个列表, 不论判断条件是否发生了变化
-
一般我们在两种常见的情况下会倾向于这样做:
-
为了过滤一个列表中的项目 (比如
v-for="user in users" v-if="user.isActive"
)。在这种情形下,请将users
替换为一个计算属性 (比如activeUsers
),让其返回过滤后的列表。 -
为了避免渲染本应该被隐藏的列表 (比如
v-for="user in users" v-if="shouldShowUsers"
)。这种情形下,请将v-if
移动至容器元素上 (比如ul
、ol
)。
-
5.数组响应式方法
- 因为Vue是响应式的, 所以当数据发生变化时, Vue会自动检测数据变化, 视图会对应的更新
- Vue中包含了一组观察数组编译的方法, 使它们改变数组也会触发更新视图
- push()
- pop()
- shift()
- unshift()
- splice()
- sort()
- reverse()
6.Vue.set修改响应式数据
-
Vue.set(vm.items, indexOftem, newValue)
-
vm.$set(vm.items ,indexOften,newValue)
- 参数1: 要修改的数组/对象
- 参数2: 要设置的索引/添加的属性名
- 参数3: 设置的值
六、事件
在前端开发中,我们需要经常和用户交互。这个时候我们就必须监听用户的发生时间,比如点击, 拖拽事件等等
- event参数,自定义参数
- 事件修饰符,按键修饰符
- 【观察】事件被绑定到哪里?
事件的例子🌰:codesandbox.io/s/vuebaseus…
<template>
<div>
<p>{{ num }}</p>
<button @click="increment1">+1</button>
<button @click="increment2(2, $event)">+2</button>
</div>
</div>
</form>
</form>
</div>
</template>
<script>
export default {
data() {
return {
num: 0,
};
},
methods: {
increment1(event) {
// eslint-disable-next-line
console.log("event", event, event.__proto__.constructor); // 是原生的 event 对象
// eslint-disable-next-line
console.log(event.target);
// eslint-disable-next-line
console.log(event.currentTarget); // 注意,事件是被注册到当前元素的,和 React 不一样
this.num++;
// 1. event 是原生的
// 2. 事件被挂载到当前元素
// 和 DOM 事件一样
},
increment2(val, event) {
console.log(event.target);
this.num = this.num + val;
},
loadHandler() {
// do some thing
},
},
mounted() {
window.addEventListener("load", this.loadHandler);
},
beforeDestroy() {
//【注意】用 vue 绑定的事件,组建销毁时会自动被解绑
// 自己绑定的事件,需要自己销毁!!!
window.removeEventListener("load", this.loadHandler);
},
};
</script>
事件修饰符
<!--阻止单击事件继续传播-->
<a v-on:click.stop="doThis"></a>
<!--提交事件不在重载页面-->
<form v-on:submit.prevent="onSubmit"></form>
<!--修饰符可以串联-->
<a v-on:click.stop.prevent="daThat"></a>
<!--只有修饰符-->
<form v-on:submit.prevent></form>
<!--添加事件监听器时使用事件捕获模式,即内部元素触发事件先在此处理,然后才交由内部元素处理-->
<div v-on:click.capture="dpThat">...</div>
<!--只当在event.target 是当前元素自身时触发处理函数,即事件不是从内部元素触发的-->
<div v-on:click.self="doThat">...</div>
按键修饰符
<!--即使Alt或Shift被一同按下也会被触发-->
<button @click.ctrl="onClick">A</button>
<!--有且只有Ctrl被按下的时候才会被触发-->
<button @click.ctrl.exact="onCtrlClick">A</button>
<!--没有任何系统修饰符被按下的时候才触发-->
<button @click.exact="onClick">A</button>
七、表单
1.v-model
Vue中使用v-model指令来实现表单元素和数据的双向绑定。数据与模板是相互影响的, 一方发生变化, 另一方立即做出更新
v-bind与v-model的区别:
v-bind
: 只能实现数据的单向绑定, 从M自动绑定到v。v-model
: 只有v-model
才能实现双向数据绑定。注意,v-model后
面不需要跟冒号
注意
v-model
只能运用在表单元素中, 或者用于自定义组件。常见的表单元素包括:input(radio,text,address,email...) ,select, checkbox, textarea.
2.v-model原理
- v-model其实是一个语法糖, 他背后本质是包含两个操作
- v-bind绑定一个value属性
- v-on指令绑定当前元素的input事件
- 也就是说下面的代码:等同于下面的代码:
<input type="text" v-model="message">
<!-- 等同于下面的代码 -->
<input type="text" :value="message" @input="message = $event.target.value">
3.修饰符 lazy number trim
修饰符 | 作用 |
---|---|
.lazy | 当表单失去焦点 或按下回车 时,data 中的数据才会更新 |
.number | 输入的内容转换为number 数据类型 |
.trim | 过滤内容的两侧空格 |
表单例子🌰:codesandbox.io/s/vuebaseus…
<template>
<div>
<p>输入框: {{ name }}</p>
<input type="text" v-model.trim="name" />
<input type="text" v-model.lazy="name" />
<input type="text" v-model.number="age" />
<p>多行文本: {{ desc }}</p>
<textarea v-model="desc"></textarea>
<!-- 注意,<textarea>{{desc}}</textarea> 是不允许的!!! -->
<p>复选框 {{ checked }}</p>
<input type="checkbox" v-model="checked" />
<p>多个复选框 {{ checkedNames }}</p>
<input type="checkbox" id="jack" value="Jack" v-model="checkedNames" />
<label for="jack">Jack</label>
<input type="checkbox" id="john" value="John" v-model="checkedNames" />
<label for="john">John</label>
<input type="checkbox" id="mike" value="Mike" v-model="checkedNames" />
<label for="mike">Mike</label>
<p>单选 {{ gender }}</p>
<input type="radio" id="male" value="male" v-model="gender" />
<label for="male">男</label>
<input type="radio" id="female" value="female" v-model="gender" />
<label for="female">女</label>
<p>下拉列表选择 {{ selected }}</p>
<select v-model="selected">
<option disabled value="">请选择</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<p>下拉列表选择(多选) {{ selectedList }}</p>
<select v-model="selectedList" multiple>
<option disabled value="">请选择</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
</div>
</template>
<script>
export default {
data() {
return {
name: "小圆脸儿",
age: 18,
desc: "前端开发",
checked: true,
checkedNames: [],
gender: "female",
selected: "",
selectedList: [],
};
},
};
</script>
总结Vue基本使用
本文所有例子🌰demo都在如下链接中:codesandbox.io/s/vuebaseus…
主要讲解了vue的基本使用:
- 模板
- computed和watch
- class和style
- 条件
- 循环
- 事件
- 表单
感谢阅读
Vue组件、Vue高级用法、Vue3、Vue原理分四篇文章分别梳理发布
❤️关注+点赞+收藏+评论+转发❤️,原创不易,鼓励笔者创作更好的文章
关注公众号小圆脸儿
,一个专注于web前端基础、工程化、面试的前端公众号