method中this的指向
注意,不应该使用箭头函数来定义method函数(例如plus: () => this.a++)。理由是箭头函数绑定了父级作用域的上下文,所以 this 将不会按照期望指向组件实例,this.a 将是undefined。
写一个箭头函数时,这个this就是window
快捷模板
文件->首选项->配置用户代码->
html.json:
{
"Print to vue": {
"prefix": "vue",
"body": [
"<!DOCTYPE html>",
"<html lang=\"en\">",
"<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>Document</title>",
"</head>",
"<body>",
" <div id=\"app\"></div>",
" <template id=\"my-app\">",
"",
" </template>",
" <script src=\"../js/vue.js\"></script>",
" <script>",
" const App = {",
" template: '#my-app',",
" data() {",
" return {",
"",
" }",
" },",
" methods: {",
"",
" }",
" }",
"",
" Vue.createApp(App).mount('#app');",
" </script>",
"</body>",
"</html>"
],
"description": ""
}
}
}
vue.json
{
"Print to vue": {
"prefix": "vue",
"body": [
" <template id=\"app\">",
"",
" </template>",
" <script src=\"../js/vue.js\"></script>",
" <script>",
" const App = {",
" template: '#app',",
" data() {",
" return {",
"",
" }",
" },",
" methods: {",
"",
" }",
" }",
"",
" Vue.createApp(App).mount('#app');",
" </script>",
],
"description": ""
}
}
mustache基本语法
四种方式:
<!DOCTYPE html>
<html lang="en">
<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>Document</title>
</head>
<body>
<div id="app"></div>
<template id="my-app">
<!-- 1.基本使用 -->
<h2>{{message}} - {{message}}</h2>
<!-- 2.表达式 -->
<h2>{{counter * 10}}</h2>
<h2>{{ message.split(" ").reverse().join(" ") }}</h2>
<!-- 3.调用函数 -->
<!-- 可以使用computed(计算属性) -->
<h2>{{getReverseMessage()}}</h2>
<!-- 4.三元运算符 -->
<h2>{{ isShow ? "哈哈哈": "" }}</h2>
<button @click="toggle">切换</button>
<!-- 错误用法 -->
<!-- var name = "abc" -> 赋值语句 -->
<!-- <h2>{{var name = "abc"}}</h2>
<h2>{{ if(isShow) { return "哈哈哈" } }}</h2> -->
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
message: "Hello World !",
counter: 100,
isShow: true
}
},
methods: {
getReverseMessage() {
return this.message.split(" ").reverse().join(" ");
},
toggle() {
this.isShow = !this.isShow;
}
}
}
Vue.createApp(App).mount('#app');
</script>
</body>
</html>
基本指令
v-once指令
v-once用于指定元素或者组件只渲染一次:
当数据发生变化时,元素或者组件以及其所有的子元素将视为静态内容并且跳过;
该指令可以用于性能优化;
v-text指令
用于更新元素的 textContent
v-html指令
默认情况下,如果我们展示的内容本身是 html 的,那么vue并不会对其进行特殊的解析。
如果我们希望这个内容被Vue可以解析出来,那么可以使用 v-html 来展示;
v-pre指令
v-pre用于跳过元素和它的子元素的编译过程,显示原始的Mustache标签:
跳过不需要编译的节点,加快编译的速度;
v-cloak指令
这个指令保持在元素上直到关联组件实例结束编译。
和 CSS 规则如 [v-cloak] { display: none } 一起用时,这个指令可以隐藏未编译的 Mustache 标签直到组件实
例准备完毕。
vue3中允许template模板中有多个根元素
绑定基本属性
v-bind用于绑定一个或多个属性值,或者向另一个组件传递props值
动态绑定属性
如果属性名称不是固定的,我们可以使用 :[属性名]=“值” 的格式来定义
绑定一个对象
如果我们希望将一个对象的所有属性,绑定到元素上的所有属性,
可以直接使用 v-bind 绑定一个对象
如v-bind="info"
此时会变成<div name="abc" age="18" height="1.88">123</div>
v-on绑定事件监听
缩写:@
参数:event
修饰符:
- .stop - 调用 event.stopPropagation()。
- .prevent - 调用 event.preventDefault()。
- .capture - 添加事件侦听器时使用 capture 模式。
- .self - 只当事件是从侦听器绑定的元素本身触发时才触发回调。
- .{keyAlias} - 仅当事件是从特定键触发时才触发回调。
- .once - 只触发一次回调。
- .left - 只当点击鼠标左键时触发。
- .right - 只当点击鼠标右键时触发。
- .middle - 只当点击鼠标中键时触发。
- .passive - { passive: true } 模式添加侦听器
条件渲染
v-show
和v-if
的区别
- 用法上的区别:
- v-show不支持template
- v-show不可以和v-else一起使用
- 本质区别:
- v-show无论是否显示到浏览器,它的dom实际都是有渲染的,只是通过css的display属性进行切换
- v-if当条件为flase时,其对应的原生不会渲染到dom中
- 开发中如何选择:
- 原生需要在显示和隐藏之间频繁切换,则使用v-show,否则就v-if
列表渲染
v-for
- v-for也支持遍历对象,并且支持有一二三个参数:
- 一个参数:
"value in object";
- 二个参数:
"(value, key) in object";
- 三个参数:
"(value, key, index) in object"
数组更新检测
Vue 将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新。这些被包裹过的方法包括:
- push()
- pop()
- shift()
- unshift()
- splice()
- sort()
- reverse()
上面的方法会直接修改原来的数组,但是某些方法不会替换原来的数组,而是会生成新的数组
,比如filter()
、
concat()
和slice()
v-for中的key是什么作用?
- key属性主要用在Vue的虚拟DOM算法,在新旧nodes对比时辨识VNodes;
- 如果不使用key,Vue会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法;
- 而使用key时,它会基于key的变化重新排列元素顺序,并且会移除/销毁key不存在的元素;
举栗:向列表插入一个元素,如何最快渲染?
* 没有key的时候采用Diff算法:比较新旧节点,找到可复用的节点,比对进行增删移的操作,节省性能。 从前遍历到更改后面的节点。(三步,从头,增删,移动尾部元素)
* 有key的时候,先从前遍历到后遍历,根据元素的个数决定挂载新节点还是删除旧节点。如果是位置节点的序列,会先移动位置再比较(五步:从头,从尾,新元素挂载,旧元素删除,位置节点排序)
官方的解释对于初学者来说并不好理解,比如下面的问题:
- 什么是新旧nodes,什么是VNode?
- VNode的全称是Virtual Node,也就是虚拟节点
- 无论是组件还是元素,它们最终在Vue中表示出来的都是一个个VNode;
template-->VNode-->真实DOM
- 如果不止一个简单的div,而是有一大堆的元素,那么它们还会形成一个VNode Tree,也叫做虚拟DOM,然后浏览器渲染成真实DOM。
- 为什么要有虚拟DOM而不直接渲染成真实DOM:是为了实现跨平台
- VNode的本质是一个JavaScript的对象;
- 没有key的时候,如何尝试修改和复用的?
- 有key的时候,如何基于key重新排列的?
key中放index的时候不会提高性能。
Vue3的Options-API
computed计算属性
- 计算属性会基于它们的依赖关系进行缓存;
- 在数据不发生变化时,计算属性是不需要重新计算的;
- 但是如果依赖的数据发生变化,在使用时,计算属性依然会重新进行计算
计算属性包含有getter和setter
computed会判断每个key传入的是函数还是对象,然后读取option是否存在get或set函数。
watch监听器
默认情况下侦听器只会针对监听的数据本身的改变(内部发生的改变是不能侦听的)
举个栗子:
<!DOCTYPE html>
<html lang="en">
<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>Document</title>
</head>
<body>
<div id="app"></div>
<template id="my-app">
<h2>{{info.name}}</h2>
<button @click="changeInfo">改变info</button>
<button @click="changeInfoName">改变infoName</button>
<button @click="changeNBAname">改变NBAName</button>
<!-- 改变nbaname两次及以上会报错 -->
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
info: {
name: "why",
age: 18,
nba:{
name:'snakeNBA'
}
}
}
},
watch: {
// 默认情况下侦听器只会针对监听的数据本身的改变(内部发生的改变是不能侦听的)
/* info(newInfo, oldInfo) {
console.log("newValue:", newInfo, "oldValue:", oldInfo);
newInfo和oldInfo此时都是proxy对象
// 此时单纯改变info.name是侦听不到的
}, */
// 深度侦听
info: {
handler: function (newInfo, oldInfo) {
console.log("newValue:", newInfo, "oldValue:", oldInfo);
// console.log("newValue:", newInfo.nba.name, "oldValue:", oldInfo.nba.name); //此时由于引用类型,oldinfo也指向了新对象,如果需要获取旧值需要自己深拷贝一份
},
deep: true, //深度侦听
// 此时info.name改变也能被侦听到
immediate:true
}
},
methods: {
changeInfo() {
this.info = {
name: "kobe"
}
},
changeInfoName() {
this.info.name = "AAA"
},
changeNBAname(){
this.info.nba.name='nbaaaaaaaaaaaaa'
}
}
}
Vue.createApp(App).mount('#app');
</script>
</body>
</html>
注意:
- 深度监听的新旧值指向同一个地址,官方文档有声明
- 错误的复杂类型侦听方法:
friends:[{name:'why'},{name:'code'}]
watch侦听"friends[0].name":function(newName,oldName){console.log(newName,oldName)}
- 对数组的对象进行侦听可以采用两种方法:
1.深度侦听
2.组件侦听
使用 $watch 的API:
- 我们可以在created的生命周期中,使用
this.$watchs
来侦听;- 第一个参数是要侦听的源;
- 第二个参数是侦听的回调函数callback;
- 第三个参数是额外的其他选项,比如deep、immediate
举个栗子:
created () {
const unwatch= this.$watch("info",function(newInfo,oldInfo) {
console.log(newInfo,oldInfo);
},{
deep:true,
immediate:true
})
}
// unwatch()
this.$watch
返回的是一个回调函数值,再次调用这个unwatch可以取消侦听
综合案例——购物车
<!DOCTYPE html>
<html lang="en">
<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>Document</title>
</head>
<body>
<div id="app"></div>
<template id="my-app">
<template v-if="books.length>0">
<table>
<thead>
<th></th>
<th>书籍名称</th>
<th>出版日期</th>
<th>价格</th>
<th>购买数量</th>
<th>操作</th>
</thead>
<tbody>
<tr v-for="(book,index) in books">
<!-- <tr v-for="(book,index) in filterBooks"> -->
<td>{{index+1}}</td>
<td>{{book.name}}</td>
<td>{{book.date}}</td>
<td>{{formatPrice(book.price)}}</td>
<td>
<button :disabled="book.count<=1" @click="decrement(index)">-</button>
<span class="counter">{{book.count}}</span>
<button @click="increment(index)">+</button>
</td>
<td><button @click="removeOne(index)">移除</button></td>
</tr>
</tbody>
</table>
<h2>总价:{{formatPrice(totalPrice)}}</h2>
</template>
<template v-else>
<h1>购物车为空~~~!</h1>
</template>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
books: [{
name: '《算法导论》',
date: '2006-9',
price: '85',
count: '1'
},
{
name: '《UNIX编程艺术》',
date: '2006-2',
price: '59',
count: '1'
},
{
name: '《编程珠玑》',
date: '2008-10',
price: '39',
count: '1'
},
{
name: '《代码大全》',
date: '2006-3',
price: '128',
count: '1'
},
]
}
},
computed: {
totalPrice(){
let finalPrice=0;
for(let book of this.books){
finalPrice+=book.count*book.price
}
return finalPrice;
},
// vue3不支持过滤器了,推荐两种做法:计算属性、全局方法
// filterBooks() {
// return this.books.map(item=>{
// item.price='¥'+item.price;
// return item;
// })
// }
},
methods: {
increment(index) {
this.books[index].count++;
},
decrement(index) {
this.books[index].count--;
},
removeOne(index) {
this.books.splice(index, 1)
},
formatPrice(price){
return '¥'+price
}
}
}
Vue.createApp(App).mount('#app');
</script>
</body>
<style>
table {
border: 1px solid #e9e9e9;
border-collapse: collapse;
border-spacing: 0;
}
th,
td {
padding: 8px, 16px;
border: 1px solid #e9e9e9;
text-align: left;
}
th {
background-color: #f7f7f7;
color: #5c6b77;
font-weight: 600;
}
.counter {
margin: 0, 5px;
}
</style>
</html>