一. 初识 Vue.js 开发
Vue 是一套用于构建用户界面的渐进式 JavaScript框架,它基于标准 HTML、CSS 和 JavaScript 构建,并提供了一套声明式的、组件化的编程模型。
1. Vue 下载和使用
安装和使用Vue这个JavaScript库有一些方式:
- 方式一:在页面中通过CDN的方式来引入
- 方式二:下载Vue的JavaScript文件,并且自己手动引入
- 方式三:通过npm包管理工具安装使用它(webpack再讲)
- 方式四:直接通过Vue CLI创建项目,并且使用它
1.1 方式一:CDN引入
<div id="app"></div>
<script src="https://unpkg.com/vue@next"></script>
<script>
// 使用Vue
const app = Vue.createApp({
template: `<h2>Hello world</h2><span>哈哈哈</span>`,
});
// 挂载
app.mount("#app");
</script>
1.2 方式二:下载和引入
下载Vue的源码(https://unpkg.com/vue@next)到vue.js文件中
<div id="app"></div>
<script src="./lib/vue.js"></script>
<script>
// 1.创建app
const app = Vue.createApp({
template: `<h1>Hello world</h1><span>哈哈哈</span>`
});
// 2.挂载app
app.mount("#app");
</script>
2. Vue 初体验之三个案例
2.1 动态数据展示
<div id="app"></div>
<script src="./lib/vue.js"></script>
<script>
const app = Vue.createApp({
template: `<h2>{{message}}</h2>`,
data: function () {
return {
title: "Hello World",
message: "你好呀,Vue3",
};
},
});
app.mount("#app");
</script>
2.2 动态展示列表
<div id="app"></div>
<script src="./lib/vue.js"></script>
<script>
const app = Vue.createApp({
template: `
<h2>电影列表</h2>
<ul>
<li v-for="item in movies">{{item}}</li>
</ul>
`,
data: function () {
return {
movies: ["情书", "怦然心动", "傲慢与偏见"],
};
},
});
app.mount("#app");
</script>
2.3 计数器案例
<div id="app"></div>
<script src="./lib/vue.js"></script>
<script>
const app = Vue.createApp({
template: `
<h2>当前计数: {{counter}}</h2>
<button @click="increment">+1</button>
<button @click="decrement">-1</button>
`,
data: function () {
return {
counter: 0,
};
},
methods: {
increment: function () {
this.counter++;
},
decrement: function () {
this.counter--;
},
},
});
app.mount("#app");
</script>
3. 命令式和声明式编程
原生开发和Vue开发的模式和特点涉及到两种不同的编程范式:
- 命令式编程和声明式编程
==声明式编程==
- 声明式编程关注的是 “what to do”,由框架(机器)完成 “how”的过程
- 我们会在createApp传入的对象中声明需要的内容,模板template、数据data、方法methods
- 目前Vue、React、Angular、小程序的编程模式,我们称之为声明式编程
==命令式编程==
- 命令式编程关注的是 “how to do”,自己完成整个how的过程
- 我们每完成一个操作,都需要通过JavaScript编写一条代码,来给浏览器一个指令
- 在早期的原生JavaScript和jQuery开发的过程中,我们都是通过这种命令式的方式在编写代码的
3.1 MVVM 模型
MVC和MVVM都是一种软件的体系结构
- MVC是Model – View –Controller的简称,是在前期被使用非常框架的架构模式,比如iOS、前端
- MVVM是Model-View-ViewModel的简称,是目前非常流行的架构模式
通常情况下,我们也经常称Vue是一个MVVM的框架
4. data 属性
- data属性必须是一个函数,并且该函数需要返回一个对象:
- data中返回的对象会被Vue的响应式系统劫持,之后对该对象的修改或者访问都会在劫持中被处理:
- 所以我们在template或者app中通过 访问counter,可以从对象中获取到数据
- 所以我们修改counter的值时,app中的 也会发生改变
5. methods 属性
- methods属性是一个对象,通常我们会在这个对象中定义很多的方法
- 这些方法可以被绑定到模板中
- 在该方法中,我们可以使用this关键字来直接访问到data中返回的对象的属性
- 方法不能使用箭头函数,否则this就会是window了
二、Vue基础 - 模板语法
1. Mustache 语法(插值语法)
- 如果我们希望把数据显示到模板(template)中,使用最多的语法是 “Mustache”语法 (双大括号) 的文本插值
- Mustache中不仅仅可以是data中的属性,也可以是一个JavaScript的表达式
<div id="app">
<!-- 1.基本使用 -->
<h2>{{ message }}</h2>
<h2>当前计数: {{ counter }}</h2>
<!-- 2.表达式 -->
<h2>计数双倍: {{ counter * 2 }}</h2>
<h2>展示的信息: {{ info.split(" ") }}</h2>
<!-- 3.三元运算符 -->
<h2>{{ age >= 18 ? "成年人" : "未成年人" }}</h2>
<!-- 4.调用methods中函数 -->
<h2>{{ formatDate(time) }}</h2>
<!-- 5.注意: 这里不能定义语句 -->
<!-- <h2>{{ const name = "why" }}</h2> -->
</div>
<script src="./lib/vue.js"></script>
<script>
// 1.创建app
const app = Vue.createApp({
// data: option api
data: function () {
return {
message: "Hello Vue",
counter: 100,
info: "my name is why",
age: 22,
time: 123,
};
},
methods: {
formatDate: function (date) {
return "2022-10-10-" + date;
},
},
});
// 2.挂载app
app.mount("#app");
</script>
2. 基本指令
2.1 不常见的指令
- v-once 用于指定元素或者组件只渲染一次:
- 当数据发生变化时,元素或者组件以及其所有的子元素将视为静态内容并且跳过
```
```js
<div id="app">
<!-- 指令: v-once -->
<h2 v-once>
{{ message }}
<span>数字: {{counter}}</span>
</h2>
// 此中内容,点击按钮后均不变
<h1>{{message}}</h1>
<button @click="changeMessage">改变message</button>
</div>
<script src="../lib/vue.js"></script>
<script>
// 1.创建app
const app = Vue.createApp({
// data: option api
data: function() {
return {
message: "Hello Vue",
counter: 100
}
},
methods: {
changeMessage: function() {
this.message = "你好啊, 李银河"
this.counter += 100
console.log(this.message, this.counter)
}
}
})
// 2.挂载app
app.mount("#app")
</script>
- v-text 用于更新元素的 textContent:
<div id="app">
// 展示出的内容:aaHello Worldbbb
<h2> aa {{message}} bbb</h2>
// 展示出的内容:Hello World,覆盖aaa
<h2 v-text="message">aaa</h2>
<span v-text="msg"></span>
<!-- 等价于 -->
<span>{{msg}}</span>
</div>
- v-html 使 html 的内容可以被Vue解析出来
<div id="app">
<h2>{{ content }}</h2>
<h2 v-html="content"></h2>
</div>
<script src="../lib/vue.js"></script>
<script>
// 1.创建app
const app = Vue.createApp({
// data: option api
data: function() {
return {
content: `<span style="color: red; font-size: 30px;">哈哈哈</span>`
}
},
})
// 2.挂载app
app.mount("#app")
</script>
- v-pre 用于跳过元素和它的子元素的编译过程,显示原始的Mustache标签:
<div id="app">
<div v-pre>
<h2>{{ message }}</h2>
</div>
</div>
// 浏览器界面中展示:{{ message }}
- v-cloak 保持在元素上直到关联组件实例结束编译
可以隐藏未编译的 Mustache 标签直到组件实例准备完毕
<style>
[v-cloak] {
display: none;
}
</style>
<div id="app">
<h2 v-cloak>{{message}}</h2>
</div>
<script src="../lib/vue.js"></script>
<script>
setTimeout(() => {
// 1.创建app
const app = Vue.createApp({
// data: option api
data: function() {
return {
message: "Hello Vue"
}
},
})
// 2.挂载app
app.mount("#app")
}, 3000)
</script>
2.2 新的指令 v-memo
<div id="app">
<div v-memo="[name, age]">
<h2>姓名: {{ name }}</h2>
<h2>年龄: {{ age }}</h2>
<h2>身高: {{ height }}</h2>
</div>
<button @click="updateInfo">改变信息</button>
</div>
- 若name和age都保持不变,这个div及其子项的所有更新都将被跳过
- 优化性能
3. v-bind 绑定属性
除了内容需要动态来决定外,我们也希望动态绑定某些属性
3.1 v-bind 绑定基本属性
- v-bind用于绑定一个或多个属性值,或者向另一个组件传递props值(这个学到组件时再介绍)
- 可以绑定图片的链接src、网站的链接href、动态绑定一些类、样式等等
- v-bind有一个对应的语法糖,也就是简写方式:
:
<div id="app">
<button @click="switchImg">切换图片</button>
<img v-bind:src="showImgUrl" alt="" />
<!-- 语法糖写法 -->
<a :href="href">百度一下</a>
</div>
<script src="./lib/vue.js"></script>
<script>
// 1.创建app
const app = Vue.createApp({
// data: option api
data: function () {
return {
imgUrl1:"http://p1.music.126.net/P89nasGuX_gjdBe_2PpQuw==/109951168455849588.jpg",
imgUrl2: "http://p1.music.126.net/zLNyOeVM5CVdx0OQ9TU11A==/109951168454700007.jpg",
showImgUrl: "http://p1.music.126.net/P89nasGuX_gjdBe_2PpQuw==/109951168455849588.jpg",
href: "http://www.baidu.com",
};
},
methods: {
switchImg: function () {
this.showImgUrl = this.showImgUrl === this.imgUrl1 ? this.imgUrl2 : this.imgUrl1;
},
},
});
// 2.挂载app
app.mount("#app");
</script>
3.2 v-bind 绑定 class
- 基本绑定
- 对象语法: 我们可以传给 :class (v-bind:class 的简写) 一个对象(
{ className: Boolean }
),以动态地切换 class- 若为false,则不会加载该class
- 数组语法: 我们可以把一个数组传给 :class,以应用一个 class 列表
<style>
.active {
color: red;
}
</style>
<div id="app">
<!-- 1.基本绑定class -->
<h2 :class="classes">Hello World</h2>
<!-- 2.动态class可以写对象语法 -->
<!-- 2.1.对象语法的基本使用(掌握) -->
<button :class="{ active: isActive, why: true, kobe: false }" @click="btnClick">我是按钮</button>
<!-- 2.2.动态绑定的class是可以和普通的class同时的使用 -->
<button class="abc cba" :class="{ active: isActive, why: true, kobe: false }" @click="btnClick">我是按钮</button>
<!-- 2.3.动态绑定的class是可以从methods获取 -->
<button class="abc cba" :class="getDynamicClasses()" @click="btnClick">我是按钮</button>
<!-- 3.动态class可以写数组语法(了解) -->
<h2 :class="['abc', 'cba']">Hello Array</h2>
<h2 :class="['abc', className]">Hello Array</h2>
<h2 :class="['abc', className, isActive? 'active': '']">Hello Array</h2>
<h2 :class="['abc', className, { active: isActive }]">Hello Array</h2>
</div>
<script src="../lib/vue.js"></script>
<script>
// 1.创建app
const app = Vue.createApp({
// data: option api
data: function() {
return {
classes: "abc cba nba",
isActive: false,
className: "why"
}
},
methods: {
btnClick: function() {
this.isActive = !this.isActive
},
getDynamicClasses: function() {
return { active: this.isActive, why: true, kobe: false }
}
}
})
// 2.挂载app
app.mount("#app")
</script>
3.3 v-bind 绑定 style
可以利用v-bind:style来绑定一些CSS内联样式
CSS property 名可以用驼峰式 (camelCase) 或短横线分隔 (kebab-case,记得用引号括起来) 来命名
- 对象语法:
{ cssname: cssvalue }
- 数组语法:
[obj1, obj2]
<div id="app">
<!-- 1.普通的html写法 -->
<h2 style="color: red; font-size: 30px;">哈哈哈哈</h2>
<!-- 2.style中的某些值, 来自data中 -->
<!-- 2.1.动态绑定style, 在后面跟上对象类型 (重要)-->
<h2 :style="{ color: fontColor, fontSize: fontSize }">哈哈哈哈</h2>
<!-- 2.2.动态的绑定属性, 这个属性是一个对象 -->
<h2 :style="objStyle">呵呵呵呵</h2>
<!-- 3.style的数组语法 -->
<h2 :style="[objStyle, { backgroundColor: 'purple' }]">嘿嘿嘿嘿</h2>
</div>
<script src="../lib/vue.js"></script>
<script>
// 1.创建app
const app = Vue.createApp({
// data: option api
data: function() {
return {
fontColor: "blue",
fontSize: '30px',
objStyle: {
fontSize: '50px',
color: "green"
}
}
},
})
// 2.挂载app
app.mount("#app")
</script>
3.4 动态绑定属性名
- 如果属性名称不是固定的,我们可以使用 :[属性名]=“值” 的格式来定义
<!-- 动态绑定属性:属性名称不是固定的, -->
<div id="app">
<h2 :[name]="value">Hello World</h2>
</div>
<script src="../lib/vue.js"></script>
<script>
// 1.创建app
const app = Vue.createApp({
// data: option api
data: function() {
return {
name: "class"
}
},
})
// 2.挂载app
app.mount("#app")
</script>
3.5 v-bind 绑定对象
可以直接使用 v-bind 绑定一个对象,将一个对象的所有属性,绑定到元素上
<div id="app">
<h2 :name="name" :age="age" :height="height">Hello World</h2>
<!-- v-bind绑定对象: 给组件传递参数 -->
<h2 v-bind="infos">Hello Bind</h2>
</div>
<script src="../lib/vue.js"></script>
<script>
// 1.创建app
const app = Vue.createApp({
// data: option api
data: function() {
return {
infos: { name: "why", age: 18, height: 1.88, address: "广州市" },
name: "why",
age: 18,
height: 1.88
}
},
})
// 2.挂载app
app.mount("#app")
</script>
4. 事件绑定 v-on
在前端开发中,我们需要经常和用户进行各种各样的交互。使用v-on指令监听用户发生的事件,比如点击、拖拽、键盘事件等等
4.1 v-on 的基本使用
<div id="app">
<!-- 1.基本的写法 -->
<div class="box" v-on:click="divClick"></div>
<!-- 2.语法糖写法(重点掌握) -->
<div class="box" @click="divClick"></div>
<!-- 3.绑定的方法位置, 也可以写成一个表达式(不常用, 不推荐) -->
<h2>{{ counter }}</h2>
<button @click="counter++">+1</button>
<!-- 4.绑定其他方法(掌握) -->
<div class="box" @mousemove="divMousemove"></div>
<!-- 5.元素绑定多个事件(掌握) -->
<div class="box" @click="divClick" @mousemove="divMousemove"></div>
<!-- <div class="box" v-on="{ click: divClick, mousemove: divMousemove }"></div> -->
</div>
<script src="../lib/vue.js"></script>
<script>
// 1.创建app
const app = Vue.createApp({
// data: option api
data: function() {
return {
counter: 0
}
},
methods: {
divClick() {
console.log("divClick")
},
divMousemove() {
console.log("divMousemove")
}
}
})
// 2.挂载app
app.mount("#app")
</script>
4.2 v-on 参数传递
<div id="app">
<!-- 1.默认传递event对象 -->
<button @click="btn1Click">按钮1</button>
<!-- 2.只有自己的参数 -->
<button @click="btn2Click('why', age)">按钮2</button>
<!-- 3.自己的参数和event对象 -->
<!-- 在模板中想要明确的获取event对象: $event -->
<button @click="btn3Click('why', age, $event)">按钮3</button>
</div>
<script src="../lib/vue.js"></script>
<script>
// 1.创建app
const app = Vue.createApp({
// data: option api
data: function() {
return {
message: "Hello Vue",
age: 18
}
},
methods: {
// 1.默认参数: event对象
// 总结: 如果在绑定事件的时候, 没有传递任何的参数, 那么event对象会被默认传递进来
btn1Click(event) {
console.log("btn1Click:", event)
},
// 2.明确参数:
btn2Click(name, age) {
console.log("btn2Click:", name, age)
},
// 3.明确参数+event对象
btn3Click(name, age, event) {
console.log("btn3Click:", name, age, event)
}
}
})
// 2.挂载app
app.mount("#app")
</script>
4.3 v-on 的修饰符
v-on支持修饰符,修饰符相当于对事件进行了一些特殊的处理:
<div id="app">
// 此时点击button按钮不会冒泡到输出divClick
<div class="box" @click="divClick">
<button @click.stop="btnClick">按钮</button>
</div>
</div>
<script src="../lib/vue.js"></script>
<script>
// 1.创建app
const app = Vue.createApp({
// data: option api
data: function() {
return {}
},
methods: {
btnClick() {
console.log("btnClick")
},
divClick() {
console.log("divClick")
}
}
})
// 2.挂载app
app.mount("#app")
</script>
5. 条件渲染
在某些情况下,我们需要根据当前的条件决定某些元素或组件是否渲染,这个时候我们就需要进行条件判断
5.1 v-if/else/else-if
v-if的渲染原理:
- v-if是惰性的
- 当条件为false时,其判断的内容完全不会被渲染或者会被销毁掉
- 当条件为true时,才会真正渲染条件块中的内容
<div id="app">
<!-- v-if="条件" -->
<div class="info" v-if="Object.keys(info).length">
<h2>个人信息</h2>
<ul>
<li>姓名: {{info.name}}</li>
<li>年龄: {{info.age}}</li>
</ul>
</div>
<!-- v-else -->
<div v-else>
<h2>没有输入个人信息</h2>
<p>请输入个人信息后, 再进行展示~</p>
</div>
</div>
<script src="../lib/vue.js"></script>
<script>
// 1.创建app
const app = Vue.createApp({
// data: option api
data() {
return {
info: { name: "why", age: 18 }
}
}
})
// 2.挂载app
app.mount("#app")
</script>
<div id="app">
<h1 v-if="score > 90">优秀</h1>
<h2 v-else-if="score > 80">良好</h2>
<h3 v-else-if="score >= 60">及格</h3>
<h4 v-else>不及格</h4>
</div>
<script src="../lib/vue.js"></script>
<script>
// 1.创建app
const app = Vue.createApp({
// data: option api
data() {
return {
score: 40
}
},
})
// 2.挂载app
app.mount("#app")
</script>
5.1.1 template 元素
v-if是一个指令,所以必须将其添加到一个元素上:
- 如果需要切换多个元素,此时我们将元素放进一个div,但是我们并不希望div这种元素被渲染
- 这个时候,我们可以选择使用template
- template元素可以当做不可见的包裹元素,并且在v-if上使用,但是最终template不会被渲染出来
v-for 指令处也可使用template
<div id="app">
<!-- v-if="条件" -->
<template class="info" v-if="Object.keys(info).length">
<h2>个人信息</h2>
<ul>
<li>姓名: {{info.name}}</li>
<li>年龄: {{info.age}}</li>
</ul>
</template>
<!-- v-else -->
<template v-else>
<h2>没有输入个人信息</h2>
<p>请输入个人信息后, 再进行展示~</p>
</template>
</div>
<script src="../lib/vue.js"></script>
<script>
// 1.创建app
const app = Vue.createApp({
// data: option api
data() {
return {
info: { name: "why", age: 18 }
}
}
})
// 2.挂载app
app.mount("#app")
</script>
5.2 v-show
v-show和v-if的用法看起来是一致的,也是根据一个条件决定是否显示元素或者组件
<div id="app">
<div>
<button @click="toggle">切换</button>
</div>
<div v-show="isShowCode">
<img src="[https://game.gtimg.cn/images/yxzj/web201706/images/comm/floatwindow/wzry_qrcode.jpg](https://game.gtimg.cn/images/yxzj/web201706/images/comm/floatwindow/wzry_qrcode.jpg)" alt="">
</div>
<div v-if="isShowCode">
<img src="[https://game.gtimg.cn/images/yxzj/web201706/images/comm/floatwindow/wzry_qrcode.jpg](https://game.gtimg.cn/images/yxzj/web201706/images/comm/floatwindow/wzry_qrcode.jpg)" alt="">
</div>
</div>
<script src="../lib/vue.js"></script>
<script>
// 1.创建app
const app = Vue.createApp({
// data: option api
data() {
return {
isShowCode: true
}
},
methods: {
toggle() {
this.isShowCode = !this.isShowCode
}
}
})
// 2.挂载app
app.mount("#app")
</script>
与v-if用法区别:
- v-show是不支持template
- v-show不可以和v-else一起使用
与v-if的本质区别:
- v-show元素无论是否需要显示到浏览器上,它的DOM实际都是有存在的,只是通过CSS的display属性来进行切换
- v-if当条件为false时,其对应的原生压根不会被渲染到DOM中
开发中如何进行选择:
- 如果我们的原生需要在显示和隐藏之间频繁的切换,那么使用v-show
- 不频繁使用 v-if
6. v-for 列表渲染
6.1 v-for 的基本使用
- v-for的基本格式是 “item in 数组“:数组通常是来自data或者prop,也可以是其他方式
- 同时可以拿到数组的索引:**”(item, index) in 数组”**,需按顺序
<div id="app">
<!-- 1.电影列表进行渲染 -->
<h2>电影列表</h2>
<ul>
<li v-for="movie in movies">{{ movie }}</li>
</ul>
<!-- 2.电影列表同时有索引 -->
<ul>
<li v-for="(movie, index) in movies">{{index + 1}} - {{ movie }}</li>
</ul>
<!-- 3.遍历数组复杂数据 -->
<h2>商品列表</h2>
<div class="item" v-for="item in products">
<h3 class="title">商品: {{item.name}}</h3>
<span>价格: {{item.price}}</span>
<p>秒杀: {{item.desc}}</p>
</div>
</div>
<script src="../lib/vue.js"></script>
<script>
// 1.创建app
const app = Vue.createApp({
// data: option api
data() {
return {
// 1.movies
movies: ["星际穿越", "少年派", "大话西游", "哆啦A梦"],
// 2.数组: 存放的是对象
products: [
{ id: 110, name: "Macbook", price: 9.9, desc: "9.9秒杀, 快来抢购!" },
{ id: 111, name: "iPhone", price: 8.8, desc: "9.9秒杀, 快来抢购!" },
{ id: 112, name: "小米电脑", price: 9.9, desc: "9.9秒杀, 快来抢购!" },
]
}
},
})
// 2.挂载app
app.mount("#app")
</script>
6.2 v-for 其他的类型
- v-for也支持遍历对象,并且支持有一二三个参数:
- 一个参数: “value in object”
- 二个参数: “(value, key) in object”
- 三个参数: “(value, key, index) in object”
- v-for同时也支持数字的遍历:
- 每一个item都是一个数字
- v-for也可以遍历其他可迭代对象(Iterable)
<div id="app">
<!-- 1.遍历数组 -->
<!-- 2.遍历对象 -->
<ul>
<li v-for="(value, key, index) in info">{{value}}-{{key}}-{{index}}</li>
</ul>
<!-- 3.遍历字符串(iterable) -->
<ul>
<li v-for="item in message">{{item}}</li>
</ul>
<!-- 4.遍历数字 -->
<ul>
<li v-for="item in 100">{{item}}</li>
</ul>
</div>
<script src="../lib/vue.js"></script>
<script>
// 1.创建app
const app = Vue.createApp({
// data: option api
data() {
return {
message: "Hello Vue",
info: { name: "why", age: 18, height: 1.88 }
}
},
})
// 2.挂载app
app.mount("#app")
</script>
6.3 数组更新检测
被侦听的数组发生变更,也会触发视图更新
直接修改原来数组的方法:
- push()
- pop()
- shift()
- unshift()
- splice()
- sort()
- reverse()
<div id="app">
<ul>
<li v-for="item in names">{{ item }}</li>
</ul>
<button @click="changeArray">修改数组</button>
</div>
<script src="../lib/vue.js"></script>
<script>
// 1.创建app
const app = Vue.createApp({
// data: option api
data() {
return {
names: ["abc", "cba", "nba", "aaa", "ccc"]
}
},
methods: {
changeArray() {
// 1.直接将数组修改为一个新的数组
this.names = ["why", "kobe"]
// 2.通过一些数组的方法, 修改数组中的元素
this.names.push("why")
this.names.pop()
this.names.splice(2, 1, "why")
this.names.sort()
this.names.reverse()
// 3.不修改原数组的方法不能侦听(watch)
const newNames = this.names.map(item => item + "why")
this.names = newNames
}
}
})
// 2.挂载app
app.mount("#app")
</script>
6.4 v-for 绑定 key 属性
在使用v-for进行列表渲染时,我们通常会给元素或者组件绑定一个key属性,key要求是唯一: id
6.4.1 VNode & 虚拟 DOM
- VNode的全称是Virtual Node,也就是虚拟节点
- 事实上,无论是组件还是元素,它们最终在Vue中表示出来的都是一个个VNode
- VNode的本质是一个JavaScript的对象
- template元素 ->解析成 VNode—>转换为真实DOM元素
- 如果我们不只是一个简单的div,而是有一大堆的元素,那么它们应该会形成一个VNode Tree
- template元素—>一个个VNode虚拟节点—>VNode Tree –>虚拟DOM—>真实DOM
虚拟DOM的一大作用:方便代码跨平台运行
6.4.2 key 的作用
案例:点击按钮插入f
<div id="app">
<button @click="insertF">插入f</button>
<ul>
<!-- key要求是唯一: id -->
<li v-for="item in letters" :key="item">{{item}}</li>
</ul>
</div>
<script src="../lib/vue.js"></script>
<script>
// 1.创建app
const app = Vue.createApp({
// data: option api
data() {
return {
letters: ["a", "b", "c", "d", "e"]
}
},
methods: {
insertF() {
this.letters.splice(2, 0, "f")
}
}
})
// 2.挂载app
app.mount("#app")
</script>
- 没有key的操作:
- diff算法, 后续VNode复用性不强
- 有key的操作:
- 根据key找到之前的VNode进行复用;
- 没有VNode可以复用, 再创建新的VNode
三、Options API
1. 计算属性 computed
1.1 复杂数据的处理方式
在某些情况,我们可能需要对数据进行一些转化后再显示,或者需要将多个数据结合起来进行显示
- mustache插值语法自己写逻辑:在模板中放入太多的逻辑会让模板过重和难以维护
- methods完成逻辑:所有的data使用过程都会变成了一个方法的调用
1.2 computed 用法
对于任何包含响应式数据的复杂逻辑,都应该使用计算属性
computed: { fullname() {} }
<div id="app">
<!-- 插值语法表达式直接进行拼接 -->
<!-- 1.拼接名字 -->
<h2>{{ fullname }}</h2>
<!-- 2.显示分数等级 -->
<h2>{{ scoreLevel }}</h2>
<!-- 3.反转单词显示文本 -->
<h2>{{ reverseMessage }}</h2>
</div>
<script src="../lib/vue.js"></script>
<script>
// 1.创建app
const app = Vue.createApp({
// data: option api
data() {
return {
// 1.姓名
firstName: "kobe",
lastName: "bryant",
// 2.分数: 及格/不及格
score: 80,
// 3.一串文本: 对文本中的单词进行反转显示
message: "my name is why"
}
},
computed: {
// 1.计算属性默认对应的是一个函数
fullname() {
return this.firstName + " " + this.lastName
},
scoreLevel() {
return this.score >= 60 ? "及格": "不及格"
},
reverseMessage() {
return this.message.split(" ").reverse().join(" ")
}
}
})
// 2.挂载app
app.mount("#app")
</script>
1.3 computed 和 methods 区别
- computed底层会缓存, 性能更高
- 计算属性会基于它们的依赖关系进行缓存;
- 在数据不发生变化时,计算属性是不需要重新计算的
- 但是如果依赖的数据发生变化,在使用时,计算属性依然会重新进行计算
```
```js
<div id="app">
<!-- 1.methods -->
<h2>{{ getFullname() }}</h2>
<h2>{{ getFullname() }}</h2>
<h2>{{ getFullname() }}</h2>
<!-- 2.computed -->
<h2>{{ fullname }}</h2>
<h2>{{ fullname }}</h2>
<h2>{{ fullname }}</h2>
<!-- 修改name值 -->
<button @click="changeLastname">修改lastname</button>
</div>
<script src="../lib/vue.js"></script>
<script>
// 1.创建app
const app = Vue.createApp({
// data: option api
data() {
return {
firstName: "kobe",
lastName: "bryant"
}
},
methods: {
getFullname() {
console.log("getFullname-----")
return this.firstName + " " + this.lastName
},
changeLastname() {
this.lastName = "why"
}
},
computed: {
fullname() {
console.log("computed fullname-----")
return this.firstName + " " + this.lastName
}
}
})
// 2.挂载app
app.mount("#app")
</script>
1.4 计算属性的 setter 和 getter (了解)
计算属性在大多数情况下,只需要一个getter方法即可,所以我们会将计算属性直接写成一个函数
<div id="app">
<h2>{{ fullname }}</h2>
<button @click="setFullname">设置fullname</button>
</div>
<script src="../lib/vue.js"></script>
<script>
// 1.创建app
const app = Vue.createApp({
// data: option api
data() {
return {
firstname: "coder",
lastname: "why"
}
},
computed: {
// 语法糖的写法
// fullname() {
// return this.firstname + " " + this.lastname
// },
// 完整的写法:
fullname: {
get: function() {
return this.firstname + " " + this.lastname
},
set: function(value) {
const names = value.split(" ")
this.firstname = names[0]
this.lastname = names[1]
}
}
},
methods: {
setFullname() {
this.fullname = "kobe bryant"
}
}
})
// 2.挂载app
app.mount("#app")
</script>
2. 侦听器 watch
希望在代码逻辑中监听某个数据的变化,这个时候就需要用侦听器watch来完成
2.1 基本侦听 watch
<div id="app">
<h2>{{message}}</h2>
<button @click="changeMessage">修改message</button>
</div>
<script src="../lib/vue.js"></script>
<script>
// 1.创建app
const app = Vue.createApp({
// data: option api
data() {
return {
message: "Hello Vue",
info: { name: "why", age: 18 }
}
},
methods: {
changeMessage() {
this.message = "你好啊, 李银河!"
this.info = { name: "kobe" }
}
},
watch: {
// 1.默认有两个参数: newValue/oldValue
message(newValue, oldValue) {
console.log("message数据发生了变化:", newValue, oldValue)
},
info(newValue, oldValue) {
// 2.如果是对象类型, 那么拿到的是代理对象 Proxy
console.log("info数据发生了变化:", newValue, oldValue)
console.log(newValue.name, oldValue.name)
// 3.获取原生对象
console.log(Vue.toRaw(newValue))
}
}
})
// 2.挂载app
app.mount("#app")
</script>
2.2 侦听器 watch 的配置选项
若修改info.name,用watch侦听info,无法侦听到
- watch只侦听info的引用变化,对于内部属性的变化是不会做出响应的
- 这个时候我们可以使用一个选项deep进行更深层的侦听
- 还有另外一个属性,是希望一开始就会立即执行一次
- 使用immediate选项
- 无论后面数据是否有变化,侦听的函数都会有限执行一次
```
```js
<div id="app">
<h2>{{ info.name }}</h2>
<button @click="changeInfo">修改info</button>
</div>
<script src="[../lib/vue.js](../lib/vue.js)"></script>
<script>
// 1.创建app
const app = Vue.createApp({
// data: option api
data() {
return {
info: { name: "why", age: 18 }
}
},
methods: {
changeInfo() {
// 创建一个新对象, 赋值给info
// this.info = { name: "kobe" }
// 直接修改原对象某一个属性
this.info.name = "kobe"
}
},
watch: {
// 默认watch监听不会进行深度监听
// info(newValue, oldValue) {
// console.log("侦听到info改变:", newValue, oldValue)
// }
// 进行深度监听
info: {
handler(newValue, oldValue) {
console.log("侦听到info改变:", newValue, oldValue) // 侦听到info改变: Proxy {name: 'kobe', age: 18} Proxy {name: 'kobe', age: 18}
console.log(newValue === oldValue)
}, // true
// 1.info进行深度监听
deep: true,
// 2.第一次渲染直接执行一次监听器
immediate: true
},
"info.name": function(newValue, oldValue) {
console.log("name发生改变:", newValue, oldValue) // name发生改变: kobe why
}
}
})
// 2.挂载app
app.mount("#app")
</script>
2.3 其他的写法
在created的生命周期(后续会讲到)中,使用 this.$watchs 来侦听
- 第一个参数是要侦听的源
- 第二个参数是侦听的回调函数callback
- 第三个参数是额外的其他选项,比如deep、immediate
<div id="app">
<h2>{{message}}</h2>
<button @click="changeMessage">修改message</button>
</div>
<script src="[../lib/vue.js](../lib/vue.js)"></script>
<script>
// 1.创建app
const app = Vue.createApp({
// data: option api
data() {
return {
message: "Hello Vue"
}
},
methods: {
changeMessage() {
this.message = "你好啊, 李银河!"
}
},
// 生命周期回调函数: 当前的组件被创建时自动执行
// 一般在该函数中, 会进行网络请求
created() {
// ajax/fetch/axios
console.log("created")
this.$watch("message", (newValue, oldValue) => {
console.log("message数据变化:", newValue, oldValue)
}, { deep: true })
}
})
// 2.挂载app
app.mount("#app")
</script>
四、v-model 双向绑定
1. v-model 基本使用
表单提交是开发中非常常见的功能,也是和用户交互的重要手段
- 这要求我们可以在代码逻辑中获取到用户提交的数据,我们通常会使用v-model指令来完成
- v-model指令可以在表单 input、textarea以及select元素上创建双向数据绑定
- v-model 本质上不过是语法糖,它负责监听用户的输入事件来更新数据,并在某种极端场景下进行一些特殊处理
- v-model的原理是背后有两个操作:
- v-bind绑定value属性的值
- v-on绑定input事件监听到函数中,函数会获取最新的值赋值到绑定的属性中
```
```js
<input type="text" :value="message" @input="inputChange">
等价于
<input type="text" v-model="message">
2. v-model 绑定其他类型
2.1 textarea
<div id="app">
<textarea cols="30" rows="10" v-model="content"></textarea>
<p>输入的内容: {{content}}</p>
</div>
<script src="../lib/vue.js"></script>
<script>
// 1.创建app
const app = Vue.createApp({
// data: option api
data() {
return {
content: ""
}
},
})
// 2.挂载app
app.mount("#app")
</script>
2.2 checkbox
- 单个勾选框:
- v-model即为布尔值。
- 此时input的value属性并不影响v-model的值。
- 多个复选框:
- 当是多个复选框时,因为可以选中多个,所以对应的data中属性是一个数组。
- 当选中某一个时,就会将input的value添加到数组中
```
```js
<div id="app">
<!-- 1.checkbox单选框: 绑定到属性中的值是一个Boolean -->
<label for="agree">
<input id="agree" type="checkbox" v-model="isAgree"> 同意协议
</label>
<h2>单选框: {{isAgree}}</h2>
<hr>
<!-- 2.checkbox多选框: 绑定到属性中的值是一个Array -->
<!-- 注意: 多选框当中, 必须明确的绑定一个value值 -->
<div class="hobbies">
<h2>请选择你的爱好:</h2>
<label for="sing">
<input id="sing" type="checkbox" v-model="hobbies" value="sing"> 唱
</label>
<label for="jump">
<input id="jump" type="checkbox" v-model="hobbies" value="jump"> 跳
</label>
<h2>爱好: {{hobbies}}</h2>
</div>
</div>
<script src="../lib/vue.js"></script>
<script>
// 1.创建app
const app = Vue.createApp({
// data: option api
data() {
return {
isAgree: false,
hobbies: []
}
},
})
// 2.挂载app
app.mount("#app")
</script>
2.3 radio
此时不需要通过添加name属性使两选项排他,添加v-model可以自动排他
<div id="app">
<div class="gender">
<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}}</h2>
</div>
</div>
<script src="../lib/vue.js"></script>
<script>
// 1.创建app
const app = Vue.createApp({
// data: option api
data() {
return {
gender: "female" // 默认选女
}
},
})
// 2.挂载app
app.mount("#app")
</script>
2.4 select
- 单选:只能选中一个值
- v-model绑定的是一个值
- 当我们选中option中的一个时,会将它对应的value赋值到fruit中
- 多选:可以选中多个值
- v-model绑定的是一个数组
- 当选中多个值时,就会将选中的option对应的value添加到数组fruit中
```
```js
<div id="app">
<!-- select的单选 -->
<select v-model="fruit">
<option value="apple">苹果</option>
<option value="orange">橘子</option>
<option value="banana">香蕉</option>
</select>
<h2>单选: {{fruit}}</h2>
<hr>
<!-- select的多选 -->
<select multiple size="3" v-model="fruits">
<option value="apple">苹果</option>
<option value="orange">橘子</option>
<option value="banana">香蕉</option>
</select>
<h2>多选: {{fruits}}</h2>
</div>
<script src="../lib/vue.js"></script>
<script>
// 1.创建app
const app = Vue.createApp({
// data: option api
data() {
return {
fruit: "orange",
fruits: []
}
},
})
// 2.挂载app
app.mount("#app")
</script>
3. v-model 值绑定
在真实开发中,我们的数据可能是来自服务器的,那么我们就可以先将值请求下来,绑定到data返回的对象中,再通过v-bind来进行值的绑定,这个过程就是值绑定
<div id="app">
<!-- 1.select的值绑定 -->
<select multiple size="3" v-model="fruits">
<option v-for="item in allFruits"
:key="item.value"
:value="item.value">
{{item.text}}
</option>
</select>
<h2>多选: {{fruits}}</h2>
<hr>
<!-- 2.checkbox的值绑定 -->
<div class="hobbies">
<h2>请选择你的爱好:</h2>
<template v-for="item in allHobbies" :key="item.value">
<label :for="item.value">
<input :id="item.value" type="checkbox" v-model="hobbies" :value="item.value"> {{item.text}}
</label>
</template>
<h2>爱好: {{hobbies}}</h2>
</div>
</div>
<script src="../lib/vue.js"></script>
<script>
// 1.创建app
const app = Vue.createApp({
// data: option api
data() {
return {
// 水果
allFruits: [
{ value: "apple", text: "苹果" },
{ value: "orange", text: "橘子" },
{ value: "banana", text: "香蕉" },
],
fruits: [],
// 爱好
allHobbies: [
{ value: "sing", text: "唱" },
{ value: "jump", text: "跳" },
],
hobbies: []
}
}
})
// 2.挂载app
app.mount("#app")
</script>
4. v-model 修饰符
4.1 lazy
- 默认情况下,v-model在进行双向绑定时,绑定的是input事件,那么会在每次内容输入后就将最新的值和绑定的属性进行同步
- 如果我们在v-model后跟上lazy修饰符(
v-model.lazy
),那么会将绑定的事件切换为 change 事件,只有在提交时(比如回车)才会触发
4.2 number
- v-model绑定后的值总是string类型,即使在我们设置input的type为number也是string类型;
- 如果我们希望转换为数字类型,那么可以使用 .number 修饰符
4.3 trim
如果要自动过滤用户输入的空白字符,可以给v-model添加 trim 修饰符
这些修饰符可以叠加使用:v-model.lazy.trim