Vue01-简介与入门
Vue
01. 简介
1.1 前端三大框架
目前前端最流行的三大框架:
- Vue
- React
- angular
1.2 Vue简介
Vue (读音 /vjuː/,类似于 view) ,也可以写成Vue.js。
vue.js是目前前端web开发最流行的工具库,由尤雨溪在2014年2月发布的。
- 一套用于构建用户界面的渐进式 JavaScript框架;
- 基于标准 HTML、CSS 和 JavaScript 构建,
- 提供了一套声明式的、组件化的编程模型;
VUE3文档:https://v3.cn.vuejs.org/guide/introduction.html
什么是渐进式框架呢?
表示我们可以在项目中一点点来引入和使用Vue,而不一定需要全部使用Vue来开发整个项目。
02. 快速上手
Vue的本质,就是一个JavaScript的库,对于Vue的使用:
- 在页面中通过CDN引入
- 下载Vue的JavaScript文件,并且自己手动引入
- 通过npm包管理工具安装并使用它
- 直接通过Vue CLI创建项目
2.1 引入
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <!--创建由Vue接管的区域--> <div id="app"></div> <!--引入Vue:CDN引入--> <script src="https://unpkg.com/vue@next"></script> <!--引入Vue:本地引入,需提前下载--> <!--<script src="../js/vue.js"></script>--> <script> // 创建Vue的实例 const app = Vue.createApp({ template: '<h2>Hello</h2>' }) // 挂载 app.mount('#app') </script> </body> </html>
2.2 体验插值语法
插值语法,官网中的说法是Mustache。(由于使用{{}},形似胡子,所以使用这个单词)。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <!--创建由Vue接管的区域--> <div id="app"> <h1>欢迎学习Vue.js</h1> <div>我是{{name}},微信{{wechat}}</div> </div> <script src="https://unpkg.com/vue@next"></script> <script> // 使用Vue const app = Vue.createApp({ data: function () { return { name: '子不语', wechat: '188888888' } }, }) // 挂载 app.mount('#app') </script> </body> </html>
插值语法的更多用法:
- JavaScript表达式
- 三元运算
- 调用函数
<div id="app"> <!-- 1. 基本使用--> <div>我叫{{name}},我喜欢{{hobby}}</div> <!-- 2. JavaScript表达式--> <div>{{ num + 1 + 1 }}</div> <!-- 3. 三元运算--> <div>{{ age>=18 ?"成年人":"未成年人"}}</div> <!-- 4. 调用函数--> <div>{{demoFunc()}}</div> <!-- 如果函数需要参数也可传入 --> </div> <script> // 使用Vue const app = Vue.createApp({ data: function () { return { num: 0, name: 子不语, age: 22, hobby: 羽毛球, dataInfo: { email: "xxxxx@163.com" }, } }, methods: { demoFunc: function () { return '2012-12-12' }, } }) // 挂载 app.mount('#app') </script>
2.3 动态展示数据
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <!--这里使用本地引入Vue--> <script src="./Vue.js"></script> </head> <body> <!--创建由Vue接管的区域--> <div id="app"></div> <script> // 使用Vue const app = Vue.createApp({ // 将内容放入app的template中 template: ` <h1>欢迎学习Vue.js</h1> <h2>电影列表</h2> <ul> <li v-for="item in movies">{{item}}</li> </ul> `, data: function () { return { movies: ['大话西游', '盗梦空间', '流浪地球'] } }, }) // 挂载 app.mount('#app') </script> </body> </html>
2.4 案例:计数器
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="./Vue.js"></script> </head> <body> <!--创建由Vue接管的区域--> <div id="app"></div> <script> // 使用Vue const app = Vue.createApp({ template: ` <h1>当前计数:{{num}}</h1> <button @click="add">+1</button> <button @click="minus">-1</button> `, data: function () { return { num: 0 } }, methods: { add: function () { this.num++ // 使用this,可以直接访问data中定义的数据 }, minus: function () { this.num-- }, } }) // 挂载 app.mount('#app') </script> </body> </html>
当app中定义了template,Vue会用template代替接管区域的内容;
如果app没有定义template,Vue默认渲染接管区域的内容。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="./Vue.js"></script> </head> <body> <!--创建由Vue接管的区域--> <div id="app"> <h1>当前计数:{{num}}</h1> <button @click="add">+1</button> <button @click="minus">-1</button> </div> <script> // 使用Vue const app = Vue.createApp({ data: function () { return { num: 0 } }, methods: { add: function () { this.num++ }, minus: function () { this.num-- }, } }) // 挂载 app.mount('#app') </script> </body> </html>
2.5 原生js实现计数器
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h2>当前计数:<span class="counter"></span></h2> <button class="add"> +1</button> <button class="minus"> -1</button> <script> // 1. 获取Dom const counterEl = document.querySelector('.counter') const addBtnEl = document.querySelector('.add') const MinusBtnEl = document.querySelector('.minus') // 2. 定义一个变量,用来记录数据 let counter = 100 counterEl.textContent = counter.toString() // 3. 监听按钮的点击 addBtnEl.onclick = function () { counter++ counterEl.textContent = counter.toString() } MinusBtnEl.onclick = function () { counter-- counterEl.textContent = counter.toString() } </script> </body> </html>
2.6 声明式编程与命令式编程
由2.1.4
与2.1.5
,我们可以体验到声明式编程与命令时编程的不同:
-
命令式编程关注的是 “how to do”,需要自己完成整个过程。
-
声明式编程关注的是 “what to do”,由框架(机器)完成整个过程。
在2.1.5
中,我们通过JavaScript编写每一条代码,来给浏览器传达每一个指令,这样的编写代码的过程,我们称之为命令式编程;
在早期的原生JavaScript和jQuery开发的过程中,我们都是通过这种命令式的方式在编写代码的;
而在2.1.4
中,我们使用Vue,在createApp传入的对象中声明需要的内容:
- 模板template
- 数据data
- 方法methods
这样编写代码的过程,我们称之为是声明式编程;
03. 基本属性-optionAPI
3.1 data
data属性是传入一个函数,并且该函数需要返回一个对象:
- 在Vue2.x的时候,也可以传入一个对象(虽然官方推荐是一个函数);
- 在Vue3.x的时候,必须传入一个函数,否则就会直接在浏览器中报错;
data中返回的对象会被Vue的响应式系统劫持,之后对该对象的修改或者访问都会在劫持中被处理。
所以我们在template或者app中通过 {{counter}} 访问counter,可以从对象中获取到数据;修改counter的值时,app中的 {{counter}}也会发生改变;
3.2 methods
methods属性是一个对象,通常我们会在这个对象中定义很多的方法,这些方法可以被绑定到模板中。
在这些方法中,我们可以使用this关键字来直接访问到data中返回的对象的属性。
对官方文档有这么一段描述:
问题一:为什么不能使用箭头函数(官方文档有给出解释)?
问题二:不使用箭头函数的情况下,this到底指向的是什么?(可以作为一道面试题)
事实上Vue的源码当中就是对methods中的所有函数进行了遍历,并且通过bind绑定了this。
3.3 computed
computed称为计算属性。
对于computed,官方并没有给出直接的概念解释。
而是说:对于任何包含响应式数据的复杂逻辑,都应该使用计算属性。
我们通过案例来理解,以下有三个案例:
案例一:我们有两个变量:firstName和lastName,希望它们拼接之后在界面上显示;
案例二:我们有一个分数:score
当score大于60的时候,在界面上显示及格;
当score小于60的时候,在界面上显示不及格;
案例三:我们有一个变量message,记录一段文字:比如Hello World
某些情况下我们是直接显示这段文字;
某些情况下我们需要对这段文字进行反转;
方案一:在模板语法中直接使用表达式;
<div id="app"> <!-- 1.拼接名字 --> <h2>{{ firstName + " " + lastName }}</h2> <!-- 2.显示分数等级 --> <h2>{{ score >= 60 ? '及格': '不及格' }}</h2> <!-- 3.反转单词显示文本 --> <h2>{{ message.split(" ").reverse().join(" ") }}</h2> </div>
思路一虽然直接,但是存在不可避免的缺点:
-
模板中存在大量的复杂逻辑,不便于维护(模板中表达式的初衷是用于简单的计算);
-
当需要多次实现同样逻辑时,存在重复的代码;
-
多次使用时,很多运算也需要多次执行,没有缓存;
方案二:使用method对逻辑进行抽取,模板中进行函数调用;
<div id="app"> <!-- 1.拼接名字 --> <h2>{{ getFullname() }}</h2> <!-- 2.显示分数等级 --> <h2>{{ getScoreLevel() }}</h2> <!-- 3.反转单词显示文本 --> <h2>{{ reverseMessage() }}</h2> </div> <script> // 1.创建app const app = Vue.createApp({ // data: option api data() { return { // 1.姓名 firstName: "kobe", lastName: "bryant", // 2.分数: 及格/不及格 score: 80, // 3.一串文本: 对文本中的单词进行反转显示 message: "Hello World!" } }, methods: { getFullname() { return this.firstName + " " + this.lastName }, getScoreLevel() { return this.score >= 60 ? "及格": "不及格" }, reverseMessage() { return this.message.split(" ").reverse().join(" ") } } }) // 2.挂载app app.mount("#app") </script>
方案二比方案一有优化,但仍然有缺点:
- 我们想显示的是一个结果,但变成了一种方法调用;
- 多次使用方法的时候,没有缓存,也需要多次计算;
方案三:使用计算属性computed;
<div id="app"> <!-- 1.拼接名字 --> <h2>{{ fullname }}</h2> <!-- 2.显示分数等级 --> <h2>{{ scoreLevel }}</h2> <!-- 3.反转单词显示文本 --> <h2>{{ reverseMessage }}</h2> </div> <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>
注意:
无论是直观上还是效果上,计算属性都是更好的选择;
计算属性看起来像是一个函数,但是我们在使用的时候不需要加();并且计算属性是有缓存的。
在computed中,对于函数的调用,运行一次之后,结果会被缓存起来,在数据不发生变化时,计算属性是不需要重新计算的。
计算属性在大多数情况下,只需要一个getter方法即可,所以我们会将计算属性直接写成一个函数。
如果我们需要设置计算属性的值,这时候我们也可以给计算属性设置一个setter的方法。
<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: "easy" } }, 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中可以直接访问到computed中的值。 this.fullname = "kobe bryant" } } }) // 2.挂载app app.mount("#app") </script>
3.4 watch
监听器watch。
在某些情况下,我们希望在代码逻辑中监听某个数据的变化,当该数据发生变化时,执行某一些代码。
这个时候就需要用到监听器watch了。
以下案例:
data中的message发生变化时,watch中的message函数就会执行。
watch中函数的名称必须是数据名称。
<div id="app"> <h2>{{message}}</h2> <button @click="changeMessage">修改message</button> </div> <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.如果是对象类型, 那么拿到的是代理对象 console.log("info数据发生了变化:", newValue, oldValue) // console.log(newValue.name, oldValue.name) // 3.如果想获取原生对象 // console.log({ ...newValue }) // console.log(Vue.toRaw(newValue)) } } }) // 2.挂载app app.mount("#app") </script>
watch中监听的如果是对象类型, 那么数据变化后,拿到的是代理对象。
如果要获取原生对象,有两种方法:
console.log({ ...newValue }) console.log(Vue.toRaw(newValue))
深度监听
默认情况下,watch只是在侦听info的引用变化,对于内部属性的变化是不会做出响应的。
下面的例子中,changeInfo函数如果是直接赋予了一个新对象,所以,watch可以监听到,
但是,如果是info属性发生变化,默认是监听不到的。
methods: { changeInfo() { // 1.创建一个新对象, 赋值给info // this.info = { name: "kobe" } // 2.直接修改原对象某一个属性,这种情况监听不到。 this.info.name = "kobe" }
watch的深度监听:
watch: { // 进行深度监听 info: { // 以下为完整写法,handler函数用于处理数据变化逻辑。之前是语法糖写法。 handler(newValue, oldValue) { console.log("侦听到info改变:", newValue, oldValue) console.log(newValue === oldValue) // True }, // 监听器选项: deep: true, // info进行深度监听 // 第一次渲染直接执行一次监听器 immediate: true }, // Vue2的语法(了解即可) "info.name": function(newValue, oldValue) { console.log("name发生改变:", newValue, oldValue) } }
watch的其他写法
但组件被建立时,系统会自动执行created函数,
我们可以在created函数中,使用 $watch
来监听。需要传入三个参数:
-
第一个参数是要侦听的源;
-
第二个参数是侦听的回调函数callback;
-
第三个参数是额外的其他选项,比如deep、immediate;
<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>
3.5 其他属性
当然,这里还可以定义很多其他的属性,比如props、emits、setup、components等等;也包括很多的生命周期函数。
04. v-once
当某些值只需要被渲染一次时,可以使用v-once。
需要注意的是,一旦某个标签使用了v-once指令,其后代标签都只渲染一次。
<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"> <!-- 指令: 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: 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> </body> </html>
05. v-text
v-text指令并不常用,了解即可。
其以指令的形式来渲染内容,比如插值语法灵活。
<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"> <h2> 前面内容 -- {{message}} -- 后面内容 </h2> <h2 v-text="message"></h2> </div> <script src="../lib/vue.js"></script> <script> // 1.创建app const app = Vue.createApp({ data: function() { return { message: "Hello Vue" } }, }) // 2.挂载app app.mount("#app") </script> </body> </html>
效果:
06. v-html
如果data中的数据是标签语法,v-text并不会渲染它。
使用v-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"> <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;">你好,Vue</span>` } }, }) // 2.挂载app app.mount("#app") </script> </body> </html>
效果:
07. v-pre
v-pre用于跳过元素和它的子元素的编译过程,显示原始的内容。
<div id="app"> <div v-pre> <h2>{{ message }}</h2> <p>当前计数: {{ counter }}</p> <p>{{}}</p> </div> </div>
效果:
08. v-cloak
浏览器的加载原则是:更快地让用户看到内容。
浏览器在渲染页面的时候,实际上是先展示类似下面的内容:
当执行到Vue.createApp
时,再根据Vue的语法去渲染页面,显示:
在某些极端的情况下,Vue的渲染过程太慢,用户会看到类似{{message}}
的内容。
如果不希望让用户看到这些内容,则可以使用v-cloak指令。
cloak是斗篷的意思。
v-cloak指令需要搭配css语法使用,
通过css语法,指定在未渲染完全时的显示样式。
<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> <style> [v-cloak] { display: none; } </style> </head> <body> <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> </body> </html>
09. v-memo
使用v-memo时,只有规定的值发生变化,才会重新渲染。
<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 v-memo="[name, age]"> <h2>姓名: {{ name }}</h2> <h2>年龄: {{ age }}</h2> <h2>身高: {{ height }}</h2> </div> <button @click="updateInfo">改变信息</button> </div> <script src="../lib/vue.js"></script> <script> // 1.创建app const app = Vue.createApp({ // data: option api data: function() { return { name: "why", age: 18, height: 1.88 } }, methods: { updateInfo: function() { // this.name = "kobe" this.age = 20 } } }) // 2.挂载app app.mount("#app") </script> </body> </html>
10. v-bind
前面的指令都是用来渲染标签内容的,如果需要动态渲染标签的属性,则需要使用到v-bind。
10.1 绑定属性
<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> <button @click="switchImage">切换图片</button> </div> <!-- 1.绑定img的src属性 --> <img v-bind:src="showImgUrl" alt=""> <!-- 语法糖: v-bind -> : --> <img :src="showImgUrl" alt=""> <!-- 2.绑定a的href属性 --> <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/agGc1qkogHtJQzjjyS-kAA==/109951167643767467.jpg", imgUrl2: "http://p1.music.126.net/_Q2zGH5wNR9xmY1aY7VmUw==/109951167643791745.jpg", showImgUrl: "http://p1.music.126.net/_Q2zGH5wNR9xmY1aY7VmUw==/109951167643791745.jpg", href: "http://www.baidu.com" } }, methods: { switchImage: function() { this.showImgUrl = this.showImgUrl === this.imgUrl1 ? this.imgUrl2: this.imgUrl1 } } }) // 2.挂载app app.mount("#app") </script> </body> </html>
10.2 绑定class
<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> <style> .active { color: red; } </style> </head> <body> <div id="app"> <!-- 1.基本绑定class --> <h2 :class="classes">Hello World</h2> <!-- 2.动态class可以写三元运算 --> <button :class=" isActive ? 'active': '' " @click="btnClick">我是按钮</button> <!-- 3.class的绑定内容可以传入对象,值必须为布尔值 --> <button :class="{ active: isActive }" @click="btnClick">我是按钮</button> <!-- 4.对象语法:传入多个键值对 --> <button :class="{ active: isActive, why: true, kobe: false }" @click="btnClick">我是按钮</button> <!-- 5.动态绑定的class是可以和普通的class同时的使用 --> <button class="abc cba" :class="{ active: isActive, why: true, kobe: false }" @click="btnClick">我是按钮</button> <!-- 6.通过函数的方式获取对象 --> <button class="abc cba" :class="getDynamicClasses()" @click="btnClick">我是按钮</button> <!-- 7.动态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> </body> </html>
10.3 绑定style
使用v-bind绑定style,可以传入对象类型,但要注意,以下的写法是不对的。
<h2 v-bind:style="{ color: red, font-size: 30px }">你好,Vue!</h2>
因为-与30px这种写法是不能被检测的。
CSS property 名可以用驼峰式 (camelCase) 或短横线分隔 (kebab-case,记得用引号括起来) 来命名。
<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"> <!-- 1.普通的html写法 --> <h2 style="color: red; font-size: 30px;">你好,Vue!</h2> <!-- 2.style中的某些值, 来自data中 --> <!-- 2.1.动态绑定style, 在后面跟上 对象类型 (重要)--> <h2 v-bind:style="{ color: fontColor, fontSize: fontSize + 'px' }">你好,Vue!</h2> <!-- 2.2.动态的绑定属性, 这个属性是一个对象 --> <h2 :style="objStyle">你好,Vue!</h2> <!-- 3.style的数组语法,元素为对象 --> <h2 :style="[objStyle, { backgroundColor: 'purple' }]">你好,Vue!</h2> </div> <script src="../lib/vue.js"></script> <script> // 1.创建app const app = Vue.createApp({ data: function() { return { fontColor: "blue", fontSize: 30, objStyle: { fontSize: '50px', color: "green" } } }, }) // 2.挂载app app.mount("#app") </script> </body> </html>
如果属性名称不是固定的,我们可以使用 :[属性名]=“值” 的格式来定义;
这种绑定的方式,我们称之为动态绑定属性。
<div id="app"> <h2 :[name]="'aaaa'">Hello World</h2> </div> <script> // 1.创建app const app = Vue.createApp({ // data: option api data: function() { return { name: "class" } }, }) // 2.挂载app app.mount("#app") </script>
10.4 绑定对象
<div id="app"> <!-- 各个属性逐一绑定,较为繁琐 --> <h2 :name="name" :age="age" :height="height">Hello World</h2> <!-- v-bind绑定对象,常用于给组件传递参数 --> <h2 v-bind="infos">Hello Bind</h2> </div> <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>
11. v-on
前面的指令是绑定元素的内容和属性,在开发中,还有一个非常重要的特性久是交互。
Vue提供了v-on来绑定事件,比如点击、拖拽、键盘事件等。
11.1 基本使用
<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="increment">+1</button> <button @click="counter++">+1</button> <!-- 4.鼠标移动方法(掌握) --> <div class="box" @mousemove="divMousemove"></div> <!-- 5.元素绑定多个事件(掌握) --> <div class="box" @click="divClick" @mousemove="divMousemove"></div> <!-- 6.元素绑定多个事件,直接绑定对象 --> <!-- <div class="box" v-on="{ click: divClick, mousemove: divMousemove }"></div> --> <!-- <div class="box" @="{ 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") }, increment() { this.counter++ }, divMousemove() { console.log("divMousemove") } } }) // 2.挂载app app.mount("#app") </script>
11.2 传递参数
在绑定事件时, 没有传递任何的参数, 那么event对象会被默认传递进来;
如果有明确传递参数,则event不会被传递;
如果有明确传递参数,又希望有event参数,可以使用$event传递。
<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> // 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>
11.3 修饰符
v-on支持修饰符,修饰符相当于对事件进行了一些特殊的处理:
- .stop - 调用 event.stopPropagation()。
- .prevent - 调用 event.preventDefault()。
- .capture - 添加事件侦听器时使用 capture 模式。
- .self - 只当事件是从侦听器绑定的元素本身触发时才触发回调。
- .{keyAlias} - 仅当事件是从特定键触发时才触发回调。
- .once - 只触发一次回调。
- .left - 只当点击鼠标左键时触发。
- .right - 只当点击鼠标右键时触发。
- .middle - 只当点击鼠标中键时触发。
- .passive - { passive: true } 模式添加侦听器
<head> <meta charset="UTF-8"> <title>Document</title> <style> .box { width: 100px; height: 100px; background-color: orange; } </style> </head> <div id="app"> <div class="box" @click="divClick"> <button @click.stop="btnClick">按钮</button> </div> </div> <script> // 1.创建app const app = Vue.createApp({ // data: option api data: function() { return { message: "Hello Vue" } }, methods: { btnClick(event) { console.log("btnClick") }, divClick() { console.log("divClick") } } }) // 2.挂载app app.mount("#app") </script>
12. v-if
Vue提供了一些指令进行条件渲染。
- v-if
- v-else
- v-else-if
- v-show
<div id="app"> <ul v-if="names.length > 0"> <li v-for="item in names">{{item}}</li> </ul> <h2 v-else>当前names没有数据, 请求获取数据后展示</h2> </div> <script> // 1.创建app const app = Vue.createApp({ data: function() { return { names: [] } }, }) // 2.挂载app app.mount("#app") </script>
<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> </div> <script> // 1.创建app const app = Vue.createApp({ // data: option api data() { return { info: {} } } }) // 2.挂载app app.mount("#app") </script>
因为Vue的指令必须添加到一个元素上才能产生作用。
一般我们选择一个div作为顶层,作为根节点。
但是如果我们希望切换的是多个元素呢?这个时候,我们可以选择使用template;
template元素可以当做不可见的包裹元素,并且在v-if上使用,但是最终template不会被渲染出。
13. v-show
v-show的功能与v-if类似,但有本质的不同。
-
v-show元素无论是否需要显示到浏览器上,它的DOM实际都是有存在的,只是通过CSS的display属性来进行切换;
-
v-if当条件为false时,其对应的原生压根不会被渲染到DOM中;
其次要注意的是:v-show是不支持template;
v-show也不可以和v-else一起使用;
开发中如何进行选择呢?
如果我们的原生需要在显示和隐藏之间频繁的切换,那么使用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" alt=""> </div> <div v-if="isShowCode"> <img src="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>
14. v-for
遍历数组:
// 1.数组: 存放的字符串 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秒杀, 快来抢购!" }, ]
<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>
遍历对象
message: "Hello Vue", movies: [], info: { name: "why", age: 18, height: 1.88 }
<div id="app"> <!-- 1.遍历对象 --> <ul> <li v-for="(value, key, index) in info">{{value}}-{{key}}-{{index}}</li> </ul> <!-- 2.遍历字符串(iterable) --> <ul> <li v-for="item in message">{{item}}</li> </ul> <!-- 3.遍历数字 --> <ul> <li v-for="item in 100">{{item}}</li> </ul> </div>
14.1 数组更新检测
Vue 将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新。
这些被包裹过的方法包括:
- push()
- pop()
- shift()
- unshift()
- splice()
- sort()
- reverse()
替换数组的方法:
-
上面的方法会直接修改原来的数组;
-
但是某些方法不会替换原来的数组,而是会生成新的数组,比如 filter()、concat() 和 slice();
<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.不修改原数组的方法是不能侦听的 const newNames = this.names.map(item => item + "why") this.names = newNames } } }) // 2.挂载app app.mount("#app") </script>
14.2 v-for中的key
在使用v-for进行列表渲染时,我们通常会给元素或者组件绑定一个key属性。
key属性的要求是唯一,一般为id值。
这个key属性有什么作用呢?我们先来看一下官方的解释:
key属性主要用在Vue的虚拟DOM算法,在新旧nodes对比时辨识VNodes;
如果不使用key,Vue会使用一种最大限度减少动态元素并且尽可能尝试就地修改/复用相同类型元素的算法;
而使用key时,它会基于key的变化重新排列元素顺序,并且会移除/销毁key不存在的元素;
<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() { return { letters: ["a", "b", "c", "d", "e"] } }, methods: { insertF() { this.letters.splice(2, 0, "f") this.letters.splice() } } }) // 2.挂载app app.mount("#app") </script>
14.3 VNode概念
VNode的全称是Virtual Node,也就是虚拟节点;
事实上,无论是组件还是元素,它们最终在Vue中表示出来的都是一个个VNode。
这样做的好处之一是可以更好地实现跨平台。
针对不同的最终设备,比如浏览器、安卓端、iOS、VR等,渲染成不同的内容。
VNode的本质是一个JavaScript的对象。
比如,如果有这一个标签:
<div class="title" style="font-size: 30px;color: red">哈哈哈</div>
那么,Vue会将其转换成为一个VNode对象,
const vnode = { type: 'div', props: { class:'title', style: { 'font-size': '30px', 'color': 'red', }, }, children:'哈哈哈' }
渲染成VNode之后,再将其渲染成正在的DOM对象。
如果不只是一个简单的div,而是有一大堆的元素,那么它们会形成一个VNode Tree
15. v-model
前端开发中,一个很重要的环节就是与用户互动。
- 用户在登录、注册时需要提交账号密码;
- 用户在检索、创建、更新信息时,需要提交一些数据;
此时,数据就需要做一个双向绑定。
15.1 手动实现双向绑定
<div id="app"> <!-- 手动的实现双向绑定 --> <input type="text" :value="message" @input="inputChange"> <h2>{{message}}</h2> </div> <script src="../lib/vue.js"></script> <script> // 1.创建app const app = Vue.createApp({ // data: option api data() { return { message: "Hello Vue", } }, methods: { inputChange(event) { this.message = event.target.value }, } }) // 2.挂载app app.mount("#app") </script>
:value="message"
实现了数据的动态绑定,读取message的值,将其显示在页面中;
@input="inputChange"
对“输入”进行监听,本质上是写了inputChange方法,但用户进行输入时,执行inputChange方法;inputChange方法将用户输入的值重新赋值给message。
15.2 绑定input
其实v-model的原理就是这两个操作:
-
v-bind绑定value属性的值;
-
v-on绑定input事件监听到函数中,函数会获取最新的值赋值到绑定的属性中;
<div id="app"> <!-- 登录功能 --> <label for="account"> 账号:<input id="account" type="text" v-model="account"> </label> <label for="password"> 密码:<input id="password" type="password" v-model="password"> </label> <button @click="loginClick">登录</button> </div> <script> // 1.创建app const app = Vue.createApp({ // data: option api data() { return { account: "", password: "" } }, methods: { loginClick() { const account = this.account const password = this.password // url发送网络请求 console.log(account, password) } } }) // 2.挂载app app.mount("#app") </script>
15.3 绑定textarea
<div id="app"> <textarea cols="30" rows="10" v-model="content"></textarea> <p>输入的内容: {{content}}</p> </div> <script> // 1.创建app const app = Vue.createApp({ // data: option api data() { return { content: "" } }, }) // 2.挂载app app.mount("#app") </script>
15.4 绑定checkbox
checkbox有多选框和单选框:
-
单选框: 绑定到属性中的值是一个Boolean
-
多选框当中, 必须明确的绑定一个value值,对应的data中属性是一个数组。
<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> <label for="rap"> <input id="rap" type="checkbox" v-model="hobbies" value="rap"> rap </label> <label for="basketball"> <input id="basketball" type="checkbox" v-model="hobbies" value="basketball"> 篮球 </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>
15.5 绑定radio
radio与多选框不同,其值是互斥的。比如性别,要么男要么女。
在input标签中,是通过name属性来区分的。只要input标签中,拥有相同的name属性,就可以实现互斥。
因此v-model就绑定在name属性上。
<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> // 1.创建app const app = Vue.createApp({ // data: option api data() { return { gender: "female" } }, }) // 2.挂载app app.mount("#app") </script>
15.6 绑定select
<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> // 1.创建app const app = Vue.createApp({ // data: option api data() { return { fruit: "orange", fruits: [] } }, }) // 2.挂载app app.mount("#app") </script>
15.7 修饰符
- .lazy:默认v-model绑定的是input事件,lazy修饰符将绑定change事件;
- .number:自动将内容转换成数字;
- .trim:去除收尾的空格
<div id="app"> <!-- 1.lazy: 绑定change事件 --> <input type="text" v-model.lazy="message"> <h2>message: {{message}}</h2> <hr> <!-- 2.number: 自动将内容转换成数字 --> <input type="text" v-model.number="counter"> <h2>counter:{{counter}}-{{typeof counter}}</h2> <input type="number" v-model="counter2"> <h2>counter2:{{counter2}}-{{typeof counter2}}</h2> <hr> <!-- 3.trim: 去除收尾的空格 --> <input type="text" v-model.trim="content"> <h2>content: {{content}}</h2> <hr> <!-- 4.使用多个修饰符 --> <input type="text" v-model.lazy.trim="content"> <h2>content: {{content}}</h2> </div> <script> // 1.创建app const app = Vue.createApp({ // data: option api data() { return { message: "Hello Vue", counter: 0, counter2: 0, content: "" } }, watch: { content(newValue) { console.log("content:", newValue) } } }) // 2.挂载app app.mount("#app") </script>
(结束)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通