如今网络学习资源丰富,编程教育机构所制课程五花八门,层出不穷,
大字号的封面广告、响口号写的是一学就会……
看得叫人目不暇接、眼花缭乱,不知如何选择。
希望能直接筛选出最优质课程?渴求一条龙服务的零基础入门到学有所成的绝佳资源?
这些问题没有标准答案,没有最好只有更好;彼之蜜糖,汝之砒霜;一千个人眼中有一千个哈姆雷特……
笔者认为,应以平常心看待,如今百花齐放,百家争鸣,各有特色,干货多多,
可随笔者“货比三家”、“各学其长”,以达成我们的学习目的(这才是最重要的!!!)。
所以,本次学习的是黑马程序员官方认证账号发布在b站的
“2023新版Vue2+Vue3基础入门到实战项目全套教程”
—— Capybara与诸位共勉。
day01
Vue快速上手
Vue是什么
概念:Vue是一个 构建用户界面(基于数据渲染出用户看到的页面) 的 渐进式(循序渐进) 框架(一套完整的项目解决方法)
创建实例
核心步骤 4步:
1.准备容器
2.引包(官网)-开发版本/生产版本
起步 —> 安装 —> #CDN—> 通过script标签引入开发版本
3.创建 Vue 实例 new Vue()
一旦引入 VueJS核心包,在全局环境,就有了 Vue 构造函数
4.指定配置项→ 渲染数据
①el指定挂载点
②data提供数据
const app = new Vue({ // 通过 el 配置选择器,指定 Vue 管理的是哪个盒子 el: '#app', // 通过 data 提供数据 data: { msg: 'Hello World', count: 666 } })
完整代码如下:
<!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> <!-- 创建Vue实例,初始化渲染 1. 准备容器 (Vue所管理的范围) 2. 引包 (开发版本包 / 生产版本包) 官网 3. 创建实例 4. 添加配置项 => 完成渲染 --> <!-- 不是Vue管理的范围 --> <div class="box2"> box2 -- {{ count }} </div> <div class="box"> box -- {{ msg }} </div> ----------------------------------------------------- <!-- Vue所管理的范围 --> <div id="app"> <!-- 这里将来会编写一些用于渲染的代码逻辑 --> <h1>{{ msg }}</h1> <a href="#">{{ count }}</a> </div> <!-- 引入的是开发版本包 - 包含完整的注释和警告 --> <script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script> <script> // 一旦引入 VueJS核心包,在全局环境,就有了 Vue 构造函数 const app = new Vue({ // 通过 el 配置选择器,指定 Vue 管理的是哪个盒子 el: '#app', // 通过 data 提供数据 data: { msg: 'Hello World', count: 666 } }) </script> </body> </html>
效果:(可见,没有被Vue管理的盒子,没有渲染对应的数据)
插值表达式
插值表达式 {{ }}
插值表达式是一种 Vue 的模板语法
<!-- 不是Vue管理的范围 --> <div class="box2"> box2 -- {{ count }} </div> <div class="box"> box -- {{ msg }} </div> ----------------------------------------------------- <!-- Vue所管理的范围 --> <div id="app"> <!-- 这里将来会编写一些用于渲染的代码逻辑 --> <h1>{{ msg }}</h1> <a href="#">{{ count }}</a> </div>
1. 作用: 利用表达式进行插值,渲染到页面中
表达式:是可以被求值的代码,JS引擎会将其计算出一个结果
2. 语法:{{ 表达式 }}
3. 注意点:
(1)使用的数据必须存在 (data)
(2)支持的是表达式,而非语句,比如:if for ...
(3)不能在标签属性中使用 {{ }} 插值
实操代码:
<!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> <!-- 插值表达式:Vue的一种模板语法 作用:利用 表达式 进行插值渲染 语法:{{ 表达式 }} 注意点: 1. 使用的数据要存在 2. 支持的是表达式,不是语句 if for 3. 不能在标签属性中使用 {{ }} --> <div id="app"> <p>{{ nickname }}</p> <p>{{ nickname.toUpperCase() }}</p> <p>{{ nickname + '你好' }}</p> <p>{{ age >= 18 ? '成年' : '未成年' }}</p> <p>{{ friend.name }}</p> <p>{{ friend.desc }}</p> <!-- ----------------------- --> <!-- <p>{{ hobby }}</p> --> <!-- <p>{{ if }}</p> --> <!-- <p title="{{ nickname }}">我是p标签</p> --> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { nickname: 'tony', age: 18, friend: { name: 'jepson', desc: '热爱学习 Vue' } } }) </script> </body> </html>
效果:
响应式特性
Vue 核心特性:响应式
我们已经掌握了基础的模板渲染,其实除了基本的模板渲染,Vue背后还做了大量工作。
比如:数据的响应式处理 → 响应式:数据变化,视图自动更新
如何访问 or 修改?data中的数据, 最终会被添加到实例上
① 访问数据: "实例.属性名"
② 修改数据: "实例.属性名" = "值"
<!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"> {{ msg }} {{ count }} </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { // 响应式数据 → 数据变化了,视图自动更新 msg: '你好,世界', count: 100 } }) // data中的数据,是会被添加到实例上 // 1. 访问数据 实例.属性名 // 2. 修改数据 实例.属性名 = 新值 </script> </body> </html>
代码效果:
在浏览器控制台直接修改数据
页面自动更新
在浏览器控制台直接修改数据(count++)
页面自动更新
总结:
数据改变,视图会自动更新
聚焦于数据 → 数据驱动视图
使用 Vue 开发,关注业务的核心逻辑,根据业务修改数据即可
开发者工具
安装 Vue 开发者工具:装插件调试 Vue 应用
(1)通过谷歌应用商店安装 (国外网站)
(2)极简插件: 下载 → 开发者模式 → 拖拽安装 → 插件详情允许访问文件
重新打开浏览器,打开写的Vue应用:
可修改数据(不经过浏览器控制台console):
Vue指令
Vue 会根据不同的【指令】,针对标签实现不同的【功能】
指令:带有 v- 前缀 的 特殊 标签属性
v-html:
作用:设置元素的 innerHTML
语法:v-html = "表达式 "
注意:插值表达式并不能解析标签(仍把它当做字符串)
<!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 v-html="msg"></div> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { msg: ` <h3>前端~学起来!</h3> ` } }) </script> </body> </html>
效果:
还有哪些指令?
见于官网Vue.js
常用的:
总结:
不同指令的目的:解决不同业务场景需求
如果需要动态解析标签,可以用哪个指令?语法?
——v-html = "表达式 " → 动态设置元素 innerHTML
指令-v-show和v-if
v-show
1. 作用: 控制元素显示隐藏
2. 语法: v-show = "表达式" 表达式值 true 显示, false 隐藏
3. 原理: 切换 display:none 控制显示隐藏
4. 场景: 频繁切换显示隐藏的场景
v-if
1. 作用: 控制元素显示隐藏(条件渲染)
2. 语法: v-if = "表达式" 表达式值 true 显示, false 隐藏
3. 原理: 基于条件判断,是否 创建 或 移除 元素节点
4. 场景: 要么显示,要么隐藏,不频繁切换的场景
v-show底层原理:切换 css 的 display: none 来控制显示隐藏
v-if 底层原理:根据 判断条件 控制元素的 创建 和 移除(条件渲染)
实践代码:
<!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> <style> .box { width: 200px; height: 100px; line-height: 100px; margin: 10px; border: 3px solid #000; text-align: center; border-radius: 5px; box-shadow: 2px 2px 2px #ccc; } </style> </head> <body> <!-- v-show底层原理:切换 css 的 display: none 来控制显示隐藏 v-if 底层原理:根据 判断条件 控制元素的 创建 和 移除(条件渲染) --> <div id="app"> <div v-show="flag" class="box">我是v-show控制的盒子</div> <div v-if="flag" class="box">我是v-if控制的盒子</div> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { flag: true } }) </script> </body> </html>
效果(flag为true时):
当flag为false时,两个盒子均不显示(隐藏),
在浏览器中查看元素如下:
指令 v-else 和 v-else-if
1. 作用: 辅助 v-if 进行判断渲染
2. 语法: v-else v-else-if = "表达式"
3. 注意: 需要紧挨着 v-if 一起使用
实践代码:
<!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"> <p v-if="gender === 1">性别:♂ 男</p> <p v-else>性别:♀ 女</p> <hr> <p v-if="score >= 90">成绩评定A:奖励电脑一台</p> <p v-else-if="score >= 70">成绩评定B:奖励周末郊游</p> <p v-else-if="score >= 60">成绩评定C:奖励零食礼包</p> <p v-else>成绩评定D:惩罚一周不能玩手机</p> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { gender: 2, score: 95 } }) </script> </body> </html>
修改数据前:
修改gender数据为1:
页面变化:
指令-v-on-01-语法1-内联语句
Vue 指令 v-on
1. 作用: 注册事件 = 添加监听 + 提供处理逻辑
2. 语法:
① v-on:事件名 = "内联语句"
② v-on:事件名 = "methods中的函数名"
3. 简写:@事件名
实操代码:
<!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"> <button @click="count--">-</button> <span>{{ count }}</span> <button v-on:click="count++">+</button> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { count: 100 } }) </script> </body> </html>
效果:
指令-v-on-02-语法2-methods处理函数
Vue 指令 v-on
1. 作用: 注册事件 = 添加监听 + 提供处理逻辑
2. 语法:
① v-on:事件名 = "内联语句"
② v-on:事件名 = "methods中的函数名"
3. 简写:@事件名
4. 注意:methods函数内的 this 指向 Vue 实例
data的数据已经挂到Vue实例上
而methods中的函数,this都指向当前实例,
所以在methods内的函数中,我们可以通过this.的方式访问数据。
还可以通过app.的方式访问数据,但app作为变量,名字可能会被修改,如此访问代码可维护性不高,推荐使用this访问。
实操代码:
<!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"> <button @click="fn">切换显示隐藏</button> <h1 v-show="isShow">Capybara</h1> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> <script> const app1 = new Vue({ el: '#app', data: { isShow: true }, methods: { fn () { // 让提供的所有methods中的函数,this都指向当前实例 // console.log('执行了fn', app.isShow) // console.log(app1 === this) this.isShow = !this.isShow } } }) </script> </body> </html>
效果(点击按钮可控制文字显示和隐藏):
指令-v-on-调用传参
1.不传参
2.传参
实操代码:
<!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> <style> .box { border: 3px solid #000000; border-radius: 10px; padding: 20px; margin: 20px; width: 200px; } h3 { margin: 10px 0 20px 0; } p { margin: 20px; } </style> </head> <body> <div id="app"> <div class="box"> <h3>水豚自动售货机</h3> <button @click="buy(5)">可乐5元</button> <button @click="buy(10)">咖啡10元</button> <button @click="buy(8)">牛奶8元</button> </div> <p>银行卡余额:{{ money }}元</p> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { money: 100 }, methods: { buy (price) { this.money -= price } } }) </script> </body> </html>
效果:
点击可乐5元,银行卡余额减5
指令-v-bind
1. 作用: 动态地设置html的标签属性→ src url title ...
2. 语法: v-bind:属性名="表达式"
3. 注意: 简写形式 :属性名="表达式"
实操代码:
<!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"> <!-- v-bind:src => :src --> <img v-bind:src="imgUrl" v-bind:title="msg" alt=""> <img :src="imgUrl" :title="msg" alt=""> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { imgUrl: './imgs/10-02.png', msg: 'hello 波仔' } }) </script> </body> </html>
效果:
案例-波仔的学习之旅
实操代码:
<!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"> <button v-show="index > 0" @click="index--">上一页</button> <div> <img :src="list[index]" alt=""> </div> <button v-show="index < list.length - 1" @click="index++">下一页</button> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { index: 0, list: [ './imgs/11-00.gif', './imgs/11-01.gif', './imgs/11-02.gif', './imgs/11-03.gif', './imgs/11-04.png', './imgs/11-05.png', ] } }) </script> </body> </html>
效果:
指令-v-for
1. 作用: 基于数据循环, 多次渲染整个元素 → 数组、对象、数字...
2. 遍历数组语法:
v-for = "(item, index) in 数组"
- item 每一项, index 下标
- 省略 index: v-for = "item in 数组"
实操代码:
<!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"> <h3>水豚水果店</h3> <ul> <li v-for="(item, index) in list"> {{ item }} - {{ index }} </li> </ul> <ul> <li v-for="item in list"> {{ item }} </li> </ul> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { list: ['西瓜', '苹果', '鸭梨', '榴莲'] } }) </script> </body> </html>
效果:
案例-小黑的书架
实操代码:
<!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"> <h3>小卡的书架</h3> <ul> <li v-for="(item, index) in booksList" :key="item.id"> <span>{{ item.name }}</span> <span>{{ item.author }}</span> <!-- 注册点击事件 → 通过 id 进行删除数组中的 对应项 --> <button @click="del(item.id)">删除</button> </li> </ul> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { booksList: [ { id: 1, name: '《红楼梦》', author: '曹雪芹' }, { id: 2, name: '《西游记》', author: '吴承恩' }, { id: 3, name: '《水浒传》', author: '施耐庵' }, { id: 4, name: '《三国演义》', author: '罗贯中' } ] }, methods: { del (id) { // console.log('删除', id) // 通过 id 进行删除数组中的 对应项 → filter(不会改变原数组) // filter: 根据条件,保留满足条件的对应项,得到一个新数组。 // console.log(this.booksList.filter(item => item.id !== id)) this.booksList = this.booksList.filter(item => item.id !== id) } } }) </script> </body> </html>
效果:
点击删除红楼梦:
红楼梦被删除:
指令-v-for的key
语法:key属性 = "唯一标识"
作用:给列表项添加的唯一标识。便于Vue进行列表项的正确排序复用。
key作用:给元素添加的唯一标识。
(第一项红楼梦所在li元素,有自己的样式)
删除第一项后(加key):
如果v-for 中的 key - 不加 key
v-for 的默认行为会尝试 原地修改元素 (就地复用)
key作用:
给元素添加的唯一标识,便于Vue进行列表项的正确排序复用。
注意点:
1. key 的值只能是 字符串 或 数字类型
2. key 的值必须具有 唯一性
3. 推荐使用 id 作为 key(唯一),不推荐使用 index 作为 key(会变化,不对应)
实操代码(不加key):
<!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"> <h3>小卡的书架</h3> <ul> <li v-for="(item, index) in booksList"> <span>{{ item.name }}</span> <span>{{ item.author }}</span> <button @click="del(item.id)">删除</button> </li> </ul> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { booksList: [ { id: 1, name: '《红楼梦》', author: '曹雪芹' }, { id: 2, name: '《西游记》', author: '吴承恩' }, { id: 3, name: '《水浒传》', author: '施耐庵' }, { id: 4, name: '《三国演义》', author: '罗贯中' } ] }, methods: { del (id) { this.booksList = this.booksList.filter(item => item.id !== id) } } }) </script> </body> </html>
效果(给第一个li元素添加背景色):
页面更新
删除第一项红楼梦后:
指令-v-model
1. 作用: 给 表单元素 使用, 双向数据绑定 → 可以快速 获取 或 设置 表单元素内容
① 数据变化 → 视图自动更新
② 视图变化 → 数据自动更新
2. 语法: v-model = '变量'
实操代码:
<!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"> <!-- v-model 可以让数据和视图,形成双向数据绑定 (1) 数据变化,视图自动更新 (2) 视图变化,数据自动更新 可以快速[获取]或[设置]表单元素的内容 --> 账户:<input type="text" v-model="username"> <br><br> 密码:<input type="password" v-model="password"> <br><br> <button @click="login">登录</button> <button @click="reset">重置</button> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { username: '', password: '' }, methods: { login () { console.log(this.username, this.password) }, reset () { this.username = '' this.password = '' } } }) </script> </body> </html>
效果:
在开发工具中修改数据:
页面自动更新:
在页面修改数据(删除卡字):
数据跟着变化:
快速 获取 或 设置 表单元素内容
methods: { login () { console.log(this.username, this.password) }, reset () { this.username = '' this.password = '' } }
输入密码后点击登录:
login方法在控制台打印:
点击重置按钮(账号、密码清空):
综合案例-小黑记事本-渲染和删除
<!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" /> <link rel="stylesheet" href="./css/index.css" /> <title>记事本</title> </head> <body> <!-- 主体区域 --> <section id="app"> <!-- 输入框 --> <header class="header"> <h1>小卡记事本</h1> <input placeholder="请输入任务" class="new-todo" /> <button class="add">添加任务</button> </header> <!-- 列表区域 --> <section class="main"> <ul class="todo-list"> <li class="todo" v-for="(item, index) in list" :key="item.id"> <div class="view"> <span class="index">{{ index + 1 }}.</span> <label>{{ item.name }}</label> <button @click="del(item.id)" class="destroy"></button> </div> </li> </ul> </section> <!-- 统计和清空 --> <footer class="footer"> <!-- 统计 --> <span class="todo-count">合 计:<strong> 2 </strong></span> <!-- 清空 --> <button class="clear-completed"> 清空任务 </button> </footer> </section> <!-- 底部 --> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { list: [ { id: 1, name: '跑步一公里' }, { id: 3, name: '游泳100米' }, ] }, methods: { del (id) { // console.log(id) => filter 保留所有不等于该 id 的项 this.list = this.list.filter(item => item.id !== id) } } }) </script> </body> </html>
效果(点击×号可以删除):
综合案例-小黑记事本-添加
id: +new Date(), //一般id由后台生成,这里取临时性时间戳;
todoName通过v-model指令与input元素双向绑定,实现添加完成后清空输入框。
<!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" /> <link rel="stylesheet" href="./css/index.css" /> <title>记事本</title> </head> <body> <!-- 主体区域 --> <section id="app"> <!-- 输入框 --> <header class="header"> <h1>小卡记事本</h1> <input v-model="todoName" placeholder="请输入任务" class="new-todo" /> <button @click="add" class="add">添加任务</button> </header> <!-- 列表区域 --> <section class="main"> <ul class="todo-list"> <li class="todo" v-for="(item, index) in list" :key="item.id"> <div class="view"> <span class="index">{{ index + 1 }}.</span> <label>{{ item.name }}</label> <button @click="del(item.id)" class="destroy"></button> </div> </li> </ul> </section> <!-- 统计和清空 --> <footer class="footer"> <!-- 统计 --> <span class="todo-count">合 计:<strong> 2 </strong></span> <!-- 清空 --> <button class="clear-completed"> 清空任务 </button> </footer> </section> <!-- 底部 --> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> // 添加功能 // 1. 通过 v-model 绑定 输入框 → 实时获取表单元素的内容 // 2. 点击按钮,进行新增,往数组最前面加 unshift const app = new Vue({ el: '#app', data: { todoName: '', list: [ { id: 1, name: '跑步一公里' }, { id: 3, name: '游泳100米' }, ] }, methods: { del (id) { // console.log(id) => filter 保留所有不等于该 id 的项 this.list = this.list.filter(item => item.id !== id) }, add () { if (this.todoName.trim() === '') { alert('请输入任务名称') return } this.list.unshift({ id: +new Date(), name: this.todoName }) this.todoName = '' } } }) </script> </body> </html>
综合案例-小黑记事本-底部统计和清空
<!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" /> <link rel="stylesheet" href="./css/index.css" /> <title>记事本</title> </head> <body> <!-- 主体区域 --> <section id="app"> <!-- 输入框 --> <header class="header"> <h1>小卡记事本</h1> <input v-model="todoName" placeholder="请输入任务" class="new-todo" /> <button @click="add" class="add">添加任务</button> </header> <!-- 列表区域 --> <section class="main"> <ul class="todo-list"> <li class="todo" v-for="(item, index) in list" :key="item.id"> <div class="view"> <span class="index">{{ index + 1 }}.</span> <label>{{ item.name }}</label> <button @click="del(item.id)" class="destroy"></button> </div> </li> </ul> </section> <!-- 统计和清空 → 如果没有任务了,底部隐藏掉 → v-show --> <footer class="footer" v-show="list.length > 0"> <!-- 统计 --> <span class="todo-count">合 计:<strong> {{ list.length }} </strong></span> <!-- 清空 --> <button @click="clear" class="clear-completed"> 清空任务 </button> </footer> </section> <!-- 底部 --> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> // 添加功能 // 1. 通过 v-model 绑定 输入框 → 实时获取表单元素的内容 // 2. 点击按钮,进行新增,往数组最前面加 unshift const app = new Vue({ el: '#app', data: { todoName: '', list: [ { id: 1, name: '跑步一公里' }, { id: 2, name: '跳绳200个' }, { id: 3, name: '游泳100米' }, ] }, methods: { del (id) { // console.log(id) => filter 保留所有不等于该 id 的项 this.list = this.list.filter(item => item.id !== id) }, add () { if (this.todoName.trim() === '') { alert('请输入任务名称') return } this.list.unshift({ id: +new Date(), name: this.todoName }) this.todoName = '' }, clear () { this.list = [] } } }) </script> </body> </html>
效果(新增底部展示):
day02
指令补充
指令修饰符
通过 "." 指明一些指令 后缀,不同 后缀 封装了不同的处理操作 → 简化代码
① 按键修饰符
@keyup.enter → 键盘回车监听
记事本案例添加功能(按回车完成添加):
<!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" /> <link rel="stylesheet" href="./css/index.css" /> <title>记事本</title> </head> <body> <!-- 主体区域 --> <section id="app"> <!-- 输入框 --> <header class="header"> <h1>小卡记事本</h1> <input @keyup.enter="add" v-model="todoName" placeholder="请输入任务" class="new-todo" /> <button @click="add" class="add">添加任务</button> </header> <!-- 列表区域 --> <section class="main"> <ul class="todo-list"> <li class="todo" v-for="(item, index) in list" :key="item.id"> <div class="view"> <span class="index">{{ index + 1 }}.</span> <label>{{ item.name }}</label> <button @click="del(item.id)" class="destroy"></button> </div> </li> </ul> </section> <!-- 统计和清空 --> <footer class="footer" v-show="list.length > 0"> <!-- 统计 --> <span class="todo-count">合 计:<strong> {{ list.length }} </strong></span> <!-- 清空 --> <button @click="clear" class="clear-completed"> 清空任务 </button> </footer> </section> <!-- 底部 --> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> // 添加功能 // 1. 通过 v-model 绑定 输入框 → 实时获取表单元素的内容 // 2. 点击按钮,进行新增,往数组最前面加 unshift const app = new Vue({ el: '#app', data: { todoName: '', list: [ { id: 1, name: '跑步一公里' }, { id: 3, name: '游泳100米' }, ] }, methods: { del (id) { // console.log(id) => filter 保留所有不等于该 id 的项 this.list = this.list.filter(item => item.id !== id) }, add () { if (this.todoName.trim() === '') { alert('请输入任务名称') return } this.list.unshift({ id: +new Date(), name: this.todoName }) this.todoName = '' }, clear () { this.list = [] } } }) </script> </body> </html>
效果(输入内容后按下回车):
添加成功:
实操代码:
<!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"> <h3>@keyup.enter → 监听键盘回车事件</h3> <input @keyup.enter="fn" v-model="username" type="text"> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { username: '' }, methods: { fn (e) { // if (e.key === 'Enter') { // console.log('键盘回车的时候触发', this.username) // } console.log('键盘回车的时候触发', this.username) } } }) </script> </body> </html>
效果:
输入内容按下回车:
控制台输出:
底层封装的代码:
fn (e) { // if (e.key === 'Enter') { // console.log('键盘回车的时候触发', this.username) // } console.log('键盘回车的时候触发', this.username) }
② v-model修饰符
v-model.trim → 去除首尾空格
实操代码:
<!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> <style> .father { width: 200px; height: 200px; background-color: pink; margin-top: 20px; } .son { width: 100px; height: 100px; background-color: skyblue; } </style> </head> <body> <div id="app"> <h3>v-model修饰符 .trim .number</h3> 姓名:<input v-model.trim="username" type="text"><br> 年纪:<input v-model.number="age" type="text"><br> <h3>@事件名.stop → 阻止冒泡</h3> <div @click="fatherFn" class="father"> <div @click.stop="sonFn" class="son">儿子</div> </div> <h3>@事件名.prevent → 阻止默认行为</h3> <a @click.prevent href="http://www.baidu.com">阻止默认行为</a> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { username: '', age: '', }, methods: { fatherFn () { alert('老父亲被点击了') }, sonFn (e) { // e.stopPropagation() alert('儿子被点击了') } } }) </script> </body> </html>
效果:
修改usename数据(数据前后带空格):
页面展示数据带有空格:
提示:直接修改username数据,输入框显示始终带空格。
在姓名后的输入框输入内容(前面带空格):
username数据(经双向绑定)获取的是去掉空格后的内容:
v-model.number → 转数字
在年纪输入框输入18:
age数据获取的是数字类型的18:
但.number修饰符只是尝试帮你转换数字,如果输入非数字字符asd,则无法转换:
③ 事件修饰符
@事件名.stop → 阻止冒泡
如果没有阻止冒泡,当点击儿子元素(会因事件冒泡触发两次提示):
添加.stop修饰符,阻止子元素点击事件冒泡,则在点击儿子元素后仅提示一次。
@事件名.prevent → 阻止默认行为
该元素的默认点击事件,点击后跳转百度,添加.prevent修饰符,将拦截其默认跳转行为。
v-bind 对于样式控制的增强(v-bind操作class)
为了方便开发者进行样式控制, Vue 扩展了 v-bind 的语法,可以针对 class 类名 和 style 行内样式 进行控制 。
语法 :class = "对象/数组"
① 对象 → 键就是类名,值是布尔值。如果值为 true,有这个类,否则没有这个类
适用场景:一个类名,来回切换
② 数组 → 数组中所有的类,都会添加到盒子上,本质就是一个 class 列表
适用场景:批量添加或删除类
操作代码:
<!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> <style> .box { width: 200px; height: 200px; border: 3px solid #000; font-size: 30px; margin-top: 10px; } .pink { background-color: pink; } .big { width: 300px; height: 300px; } </style> </head> <body> <div id="app"> <div class="box" :class="{ pink: true, big: true }">卡皮巴拉</div> <div class="box" :class="['pink', 'big']">卡皮巴拉</div> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { } }) </script> </body> </html>
效果:
案例:京东秒杀 tab 导航高亮
实操代码:
关键代码::class="{ active: index === activeIndex }"
<!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> <style> * { margin: 0; padding: 0; } ul { display: flex; border-bottom: 2px solid #e01222; padding: 0 10px; } li { width: 100px; height: 50px; line-height: 50px; list-style: none; text-align: center; } li a { display: block; text-decoration: none; font-weight: bold; color: #333333; } li a.active { background-color: #e01222; color: #fff; } </style> </head> <body> <div id="app"> <ul> <li v-for="(item, index) in list" :key="item.id" @click="activeIndex = index"> <a :class="{ active: index === activeIndex }" href="#">{{ item.name }}</a> </li> </ul> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { activeIndex: 2, // 记录高亮 list: [ { id: 1, name: '京东秒杀' }, { id: 2, name: '每日特价' }, { id: 3, name: '品类秒杀' } ] } }) </script> </body> </html>
效果:
点击谁,谁高亮:
v-bind 对于样式控制的增强 - 操作style
语法 :style = "样式对象"
<div class="box" :style="{ CSS属性名1: CSS属性值, CSS属性名2: CSS属性值 }"></div>
适用场景:某个具体属性的动态设置
实操代码:
关于背景颜色,js对象属性名不支持“-”,background-color应写成backgroundColor
<!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> <style> .box { width: 200px; height: 200px; background-color: rgb(187, 150, 156); } </style> </head> <body> <div id="app"> <div class="box" :style="{ width: '400px', height: '400px', backgroundColor: 'green' }"></div> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { } }) </script> </body> </html>
效果:
案例-进度条效果
关键代码::style="{ width: percent + '%' }
<!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> <style> .progress { height: 25px; width: 400px; border-radius: 15px; background-color: #272425; border: 3px solid #272425; box-sizing: border-box; margin-bottom: 30px; } .inner { width: 50%; height: 20px; border-radius: 10px; text-align: right; position: relative; background-color: #409eff; background-size: 20px 20px; box-sizing: border-box; transition: all 1s; } .inner span { position: absolute; right: -20px; bottom: -25px; } </style> </head> <body> <div id="app"> <!-- 外层盒子底色 (黑色) --> <div class="progress"> <!-- 内层盒子 - 进度(蓝色) --> <div class="inner" :style="{ width: percent + '%' }"> <span>{{ percent }}%</span> </div> </div> <button @click="percent = 25">设置25%</button> <button @click="percent = 50">设置50%</button> <button @click="percent = 75">设置75%</button> <button @click="percent = 100">设置100%</button> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { percent: 30 } }) </script> </body> </html>
v-model 应用于其他表单元素
常见的表单元素都可以用 v-model 绑定关联 → 快速 获取 或 设置 表单元素的值
它会根据 控件类型 自动选取 正确的方法 来更新元素
输入框 input:text → value
文本域 textarea → value
复选框 input:checkbox → checked
单选框 input:radio → checked
下拉菜单 select → value
...
实操代码:
<!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> <style> textarea { display: block; width: 240px; height: 100px; margin: 10px 0; } </style> </head> <body> <div id="app"> <h3>小卡学习网</h3> 姓名: <input type="text" v-model="username"> <br><br> 是否单身: <input type="checkbox" v-model="isSingle"> <br><br> <!-- 前置理解: 1. name: 给单选框加上 name 属性 可以分组 → 同一组互相会互斥 2. value: 给单选框加上 value 属性,用于提交给后台的数据 结合 Vue 使用 → v-model --> 性别: <input v-model="gender" type="radio" name="gender" value="1">男 <input v-model="gender" type="radio" name="gender" value="2">女 <br><br> <!-- 前置理解: 1. option 需要设置 value 值,提交给后台 2. select 的 value 值,关联了选中的 option 的 value 值 结合 Vue 使用 → v-model --> 所在城市: <select v-model="cityId"> <option value="101">北京</option> <option value="102">上海</option> <option value="103">成都</option> <option value="104">南京</option> </select> <br><br> 自我描述: <textarea v-model="desc"></textarea> <button>立即注册</button> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { username: '', isSingle: false, gender: "2", cityId: '102', desc: "" } }) </script> </body> </html>
效果:
如下图输入数据:
绑定的数据跟着变化:
computed 计算属性
概念:基于现有的数据,计算出来的新属性。 依赖的数据变化,自动重新计算。
语法:
① 声明在 computed 配置项中,一个计算属性对应一个函数
② 使用起来和普通属性一样使用 {{ 计算属性名 }}
计算属性 → 可以将一段 求值的代码 进行封装
实操代码:
<!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> <style> table { border: 1px solid #000; text-align: center; width: 240px; } th,td { border: 1px solid #000; } h3 { position: relative; } </style> </head> <body> <div id="app"> <h3>小卡的礼物清单</h3> <table> <tr> <th>名字</th> <th>数量</th> </tr> <tr v-for="(item, index) in list" :key="item.id"> <td>{{ item.name }}</td> <td>{{ item.num }}个</td> </tr> </table> <!-- 目标:统计求和,求得礼物总数 --> <p>礼物总数:{{ totalCount }} 个</p> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { // 现有的数据 list: [ { id: 1, name: '篮球', num: 1 }, { id: 2, name: '玩具', num: 2 }, { id: 3, name: '铅笔', num: 5 }, ] }, computed: { totalCount () { // 基于现有的数据,编写求值逻辑 // 计算属性函数内部,可以直接通过 this 访问到 app 实例 // console.log(this.list) // 需求:对 this.list 数组里面的 num 进行求和 → reduce let total = this.list.reduce((sum, item) => sum + item.num, 0) return total } } }) </script> </body> </html>
效果:
computed计算属性vs方法methods
computed 计算属性:
作用:封装了一段对于数据的处理,求得一个结果。
语法:
① 写在 computed 配置项中
② 作为属性,直接使用 → this.计算属性 {{ 计算属性 }}
methods 方法:
作用:给实例提供一个方法,调用以处理业务逻辑。
语法:
① 写在 methods 配置项中
② 作为方法,需要调用 → this.方法名( ) {{ 方法名() }} @事件名="方法名"
缓存特性(提升性能):
计算属性会对计算出来的结果缓存,再次使用直接读取缓存,
依赖项变化了,会自动重新计算 → 并再次缓存
实操代码:
<!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> <style> table { border: 1px solid #000; text-align: center; width: 300px; } th,td { border: 1px solid #000; } h3 { position: relative; } span { position: absolute; left: 145px; top: -4px; width: 16px; height: 16px; color: white; font-size: 12px; text-align: center; border-radius: 50%; background-color: #e63f32; } </style> </head> <body> <div id="app"> <h3>小卡的礼物清单🛒<span>{{ totalCountFn() }}</span></h3> <h3>小卡的礼物清单🛒<span>{{ totalCountFn() }}</span></h3> <h3>小卡的礼物清单🛒<span>{{ totalCountFn() }}</span></h3> <h3>小卡的礼物清单🛒<span>{{ totalCountFn() }}</span></h3> <table> <tr> <th>名字</th> <th>数量</th> </tr> <tr v-for="(item, index) in list" :key="item.id"> <td>{{ item.name }}</td> <td>{{ item.num }}个</td> </tr> </table> <p>礼物总数:{{ totalCountFn() }} 个</p> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { // 现有的数据 list: [ { id: 1, name: '篮球', num: 3 }, { id: 2, name: '玩具', num: 2 }, { id: 3, name: '铅笔', num: 5 }, ] }, methods: { totalCountFn () { console.log('methods方法执行了') let total = this.list.reduce((sum, item) => sum + item.num, 0) return total } }, computed: { // 计算属性:有缓存的,一旦计算出来结果,就会立刻缓存 // 下一次读取 → 直接读缓存就行 → 性能特别高 // totalCount () { // console.log('计算属性执行了') // let total = this.list.reduce((sum, item) => sum + item.num, 0) // return total // } } }) </script> </body> </html>
效果:
控制台:
methods方法没有缓冲,会重复执行。
计算属性完整写法
计算属性默认的简写,只能读取访问,不能 "修改"。
如果要 "修改" → 需要写计算属性的完整写法
实操代码:
<!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> <style> input { width: 30px; } </style> </head> <body> <div id="app"> 姓:<input type="text" v-model="firstName"> + 名:<input type="text" v-model="lastName"> = <span>{{ fullName }}</span><br><br> <button @click="changeName">改名卡</button> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { firstName: '刘', lastName: '备', }, methods: { changeName () { this.fullName = '黄忠' } }, computed: { // 简写 → 获取,没有配置设置的逻辑 // fullName () { // return this.firstName + this.lastName // } // 完整写法 → 获取 + 设置 fullName: { // (1) 当fullName计算属性,被获取求值时,执行get(有缓存,优先读缓存) // 会将返回值作为,求值的结果 get () { return this.firstName + this.lastName }, // (2) 当fullName计算属性,被修改赋值时,执行set // 修改的值,传递给set方法的形参 set (value) { // console.log(value.slice(0, 1)) // console.log(value.slice(1)) this.firstName = value.slice(0, 1) this.lastName = value.slice(1) } } } }) </script> </body> </html>
计算属性简写时,调用changeName方法会报错:
效果:
点击改名卡:
// 完整写法 → 获取 + 设置 fullName: { // (1) 当fullName计算属性,被获取求值时,执行get(有缓存,优先读缓存) // 会将返回值作为,求值的结果 get () { return this.firstName + this.lastName }, // (2) 当fullName计算属性,被修改赋值时,执行set // 修改的值,传递给set方法的形参 set (value) { // console.log(value.slice(0, 1)) // console.log(value.slice(1)) this.firstName = value.slice(0, 1) this.lastName = value.slice(1) } }
成绩案例
实操代码:
<!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" /> <link rel="stylesheet" href="./styles/index.css" /> <title>Document</title> </head> <body> <div id="app" class="score-case"> <div class="table"> <table> <thead> <tr> <th>编号</th> <th>科目</th> <th>成绩</th> <th>操作</th> </tr> </thead> <tbody v-if="list.length > 0"> <tr v-for="(item, index) in list" :key="item.id"> <td>{{ index + 1 }}</td> <td>{{ item.subject }}</td> <!-- 需求:不及格的标红, < 60 分, 加上 red 类 --> <td :class="{ red: item.score < 60 }">{{ item.score }}</td> <td><a @click.prevent="del(item.id)" href="http://www.baidu.com">删除</a></td> </tr> </tbody> <tbody v-else> <tr> <td colspan="5"> <span class="none">暂无数据</span> </td> </tr> </tbody> <tfoot> <tr> <td colspan="5"> <span>总分:{{ totalScore }}</span> <span style="margin-left: 50px">平均分:{{ averageScore }}</span> </td> </tr> </tfoot> </table> </div> <div class="form"> <div class="form-item"> <div class="label">科目:</div> <div class="input"> <input type="text" placeholder="请输入科目" v-model.trim="subject" /> </div> </div> <div class="form-item"> <div class="label">分数:</div> <div class="input"> <input type="text" placeholder="请输入分数" v-model.number="score" /> </div> </div> <div class="form-item"> <div class="label"></div> <div class="input"> <button @click="add" class="submit" >添加</button> </div> </div> </div> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { list: [ { id: 1, subject: '语文', score: 62 }, { id: 7, subject: '数学', score: 89 }, { id: 12, subject: '英语', score: 70 }, ], subject: '', score: '' }, computed: { totalScore() { return this.list.reduce((sum, item) => sum + item.score, 0) }, averageScore () { if (this.list.length === 0) { return 0 } return (this.totalScore / this.list.length).toFixed(2) } }, methods: { del (id) { // console.log(id) this.list = this.list.filter(item => item.id !== id) }, add () { if (!this.subject) { alert('请输入科目') return } if (typeof this.score !== 'number') { alert('请输入正确的成绩') return } this.list.unshift({ id: +new Date(), subject: this.subject, score: this.score }) this.subject = '' this.score = '' } } }) </script> </body> </html>
技术总结:
效果:
watch 侦听器
watch 侦听器(监视器)
作用:监视数据变化,执行一些 业务逻辑 或 异步操作。
语法:
① 简单写法 → 简单类型数据,直接监视
② 完整写法 → 添加额外配置项
watch-简写-语法
实践代码:
如果监听的是某个对象中的属性:'obj.words' (newValue) { }
<!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> <style> * { margin: 0; padding: 0; box-sizing: border-box; font-size: 18px; } #app { padding: 10px 20px; } .query { margin: 10px 0; } .box { display: flex; } textarea { width: 300px; height: 160px; font-size: 18px; border: 1px solid #dedede; outline: none; resize: none; padding: 10px; } textarea:hover { border: 1px solid #1589f5; } .transbox { width: 300px; height: 160px; background-color: #f0f0f0; padding: 10px; border: none; } .tip-box { width: 300px; height: 25px; line-height: 25px; display: flex; } .tip-box span { flex: 1; text-align: center; } .query span { font-size: 18px; } .input-wrap { position: relative; } .input-wrap span { position: absolute; right: 15px; bottom: 15px; font-size: 12px; } .input-wrap i { font-size: 20px; font-style: normal; } </style> </head> <body> <div id="app"> <!-- 条件选择框 --> <div class="query"> <span>翻译成的语言:</span> <select> <option value="italy">意大利</option> <option value="english">英语</option> <option value="german">德语</option> </select> </div> <!-- 翻译框 --> <div class="box"> <div class="input-wrap"> <textarea v-model="obj.words"></textarea> <span><i>⌨️</i>文档翻译</span> </div> <div class="output-wrap"> <div class="transbox">mela</div> </div> </div> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> <script> // 接口地址:https://applet-base-api-t.itheima.net/api/translate // 请求方式:get // 请求参数: // (1)words:需要被翻译的文本(必传) // (2)lang: 需要被翻译成的语言(可选)默认值-意大利 // ----------------------------------------------- const app = new Vue({ el: '#app', data: { // words: '' obj: { words: '' } }, // 具体讲解:(1) watch语法 (2) 具体业务实现 watch: { // 该方法会在数据变化时调用执行 // newValue新值, oldValue老值(一般不用) // words (newValue) { // console.log('变化了', newValue) // } 'obj.words' (newValue) { console.log('变化了', newValue) } } }) </script> </body> </html>
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> <style> * { margin: 0; padding: 0; box-sizing: border-box; font-size: 18px; } #app { padding: 10px 20px; } .query { margin: 10px 0; } .box { display: flex; } textarea { width: 300px; height: 160px; font-size: 18px; border: 1px solid #dedede; outline: none; resize: none; padding: 10px; } textarea:hover { border: 1px solid #1589f5; } .transbox { width: 300px; height: 160px; background-color: #f0f0f0; padding: 10px; border: none; } .tip-box { width: 300px; height: 25px; line-height: 25px; display: flex; } .tip-box span { flex: 1; text-align: center; } .query span { font-size: 18px; } .input-wrap { position: relative; } .input-wrap span { position: absolute; right: 15px; bottom: 15px; font-size: 12px; } .input-wrap i { font-size: 20px; font-style: normal; } </style> </head> <body> <div id="app"> <!-- 条件选择框 --> <div class="query"> <span>翻译成的语言:</span> <select> <option value="italy">意大利</option> <option value="english">英语</option> <option value="german">德语</option> </select> </div> <!-- 翻译框 --> <div class="box"> <div class="input-wrap"> <textarea v-model="obj.words"></textarea> <span><i>⌨️</i>文档翻译</span> </div> <div class="output-wrap"> <div class="transbox">{{ result }}</div> </div> </div> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> <script> // 接口地址:https://applet-base-api-t.itheima.net/api/translate // 请求方式:get // 请求参数: // (1)words:需要被翻译的文本(必传) // (2)lang: 需要被翻译成的语言(可选)默认值-意大利 // ----------------------------------------------- const app = new Vue({ el: '#app', data: { // words: '' obj: { words: '' }, result: '', // 翻译结果 // timer: null // 延时器id }, // 具体讲解:(1) watch语法 (2) 具体业务实现 watch: { // 该方法会在数据变化时调用执行 // newValue新值, oldValue老值(一般不用) // words (newValue) { // console.log('变化了', newValue) // } 'obj.words' (newValue) { // console.log('变化了', newValue) // 防抖: 延迟执行 → 干啥事先等一等,延迟一会,一段时间内没有再次触发,才执行 clearTimeout(this.timer) this.timer = setTimeout(async () => { const res = await axios({ url: 'https://applet-base-api-t.itheima.net/api/translate', params: { words: newValue } }) this.result = res.data.data console.log(res.data.data) }, 300) } } }) </script> </body> </html>
此处提到,像timer这样不需要响应式的数据,并不需要写到data里面去,把Vue实例当做普通对象直接this.timer进行绑定。
watch-完整写法
② 完整写法 → 添加额外配置项
(1) deep: true 对复杂类型深度监视
(2) immediate: true 初始化立刻执行一次handler方法
实践代码:
<!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> <style> * { margin: 0; padding: 0; box-sizing: border-box; font-size: 18px; } #app { padding: 10px 20px; } .query { margin: 10px 0; } .box { display: flex; } textarea { width: 300px; height: 160px; font-size: 18px; border: 1px solid #dedede; outline: none; resize: none; padding: 10px; } textarea:hover { border: 1px solid #1589f5; } .transbox { width: 300px; height: 160px; background-color: #f0f0f0; padding: 10px; border: none; } .tip-box { width: 300px; height: 25px; line-height: 25px; display: flex; } .tip-box span { flex: 1; text-align: center; } .query span { font-size: 18px; } .input-wrap { position: relative; } .input-wrap span { position: absolute; right: 15px; bottom: 15px; font-size: 12px; } .input-wrap i { font-size: 20px; font-style: normal; } </style> </head> <body> <div id="app"> <!-- 条件选择框 --> <div class="query"> <span>翻译成的语言:</span> <select v-model="obj.lang"> <option value="italy">意大利</option> <option value="english">英语</option> <option value="german">德语</option> </select> </div> <!-- 翻译框 --> <div class="box"> <div class="input-wrap"> <textarea v-model="obj.words"></textarea> <span><i>⌨️</i>文档翻译</span> </div> <div class="output-wrap"> <div class="transbox">{{ result }}</div> </div> </div> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> <script> // 需求:输入内容,修改语言,都实时翻译 // 接口地址:https://applet-base-api-t.itheima.net/api/translate // 请求方式:get // 请求参数: // (1)words:需要被翻译的文本(必传) // (2)lang: 需要被翻译成的语言(可选)默认值-意大利 // ----------------------------------------------- const app = new Vue({ el: '#app', data: { obj: { words: '小黑', lang: 'italy' }, result: '', // 翻译结果 }, watch: { obj: { deep: true, // 深度监视 immediate: true, // 立刻执行,一进入页面handler就立刻执行一次 handler (newValue) { clearTimeout(this.timer) this.timer = setTimeout(async () => { const res = await axios({ url: 'https://applet-base-api-t.itheima.net/api/translate', params: newValue }) this.result = res.data.data console.log(res.data.data) }, 300) } } // 'obj.words' (newValue) { // clearTimeout(this.timer) // this.timer = setTimeout(async () => { // const res = await axios({ // url: 'https://applet-base-api-t.itheima.net/api/translate', // params: { // words: newValue // } // }) // this.result = res.data.data // console.log(res.data.data) // }, 300) // } } }) </script> </body> </html>
水果购物车-基本渲染
实操代码:
<!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" /> <link rel="stylesheet" href="./css/inputnumber.css" /> <link rel="stylesheet" href="./css/index.css" /> <title>购物车</title> </head> <body> <div class="app-container" id="app"> <!-- 顶部banner --> <div class="banner-box"><img src="http://autumnfish.cn/static/fruit.jpg" alt="" /></div> <!-- 面包屑 --> <div class="breadcrumb"> <span>🏠</span> / <span>购物车</span> </div> <!-- 购物车主体 --> <div class="main" v-if="fruitList.length > 0"> <div class="table"> <!-- 头部 --> <div class="thead"> <div class="tr"> <div class="th">选中</div> <div class="th th-pic">图片</div> <div class="th">单价</div> <div class="th num-th">个数</div> <div class="th">小计</div> <div class="th">操作</div> </div> </div> <!-- 身体 --> <div class="tbody"> <div v-for="(item, index) in fruitList" :key="item.id" class="tr" :class="{ active: item.isChecked }"> <div class="td"><input type="checkbox" v-model="item.isChecked" /></div> <div class="td"><img :src="item.icon" alt="" /></div> <div class="td">{{ item.price }}</div> <div class="td"> <div class="my-input-number"> <button :disabled="item.num <= 1" class="decrease" @click="sub(item.id)"> - </button> <span class="my-input__inner">{{ item.num }}</span> <button class="increase" @click="add(item.id)"> + </button> </div> </div> <div class="td">{{ item.num * item.price }}</div> <div class="td"><button @click="del(item.id)">删除</button></div> </div> </div> </div> <!-- 底部 --> <div class="bottom"> <!-- 全选 --> <label class="check-all"> <input type="checkbox" v-model="isAll"/> 全选 </label> <div class="right-box"> <!-- 所有商品总价 --> <span class="price-box">总价 : ¥ <span class="price">{{ totalPrice }}</span></span> <!-- 结算按钮 --> <button class="pay">结算( {{ totalCount }} )</button> </div> </div> </div> <!-- 空车 --> <div class="empty" v-else>🛒空空如也</div> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> <script> const defaultArr = [ { id: 1, icon: 'http://autumnfish.cn/static/火龙果.png', isChecked: true, num: 2, price: 6, }, { id: 2, icon: 'http://autumnfish.cn/static/荔枝.png', isChecked: false, num: 7, price: 20, }, { id: 3, icon: 'http://autumnfish.cn/static/榴莲.png', isChecked: false, num: 3, price: 40, }, { id: 4, icon: 'http://autumnfish.cn/static/鸭梨.png', isChecked: true, num: 10, price: 3, }, { id: 5, icon: 'http://autumnfish.cn/static/樱桃.png', isChecked: false, num: 20, price: 34, }, ] const app = new Vue({ el: '#app', data: { // 水果列表 fruitList: JSON.parse(localStorage.getItem('list')) || defaultArr, }, computed: { // 默认计算属性:只能获取不能设置,要设置需要写完整写法 // isAll () { // // 必须所有的小选框都选中,全选按钮才选中 → every // return this.fruitList.every(item => item.isChecked) // } // 完整写法 = get + set isAll: { get () { return this.fruitList.every(item => item.isChecked) }, set (value) { // 基于拿到的布尔值,要让所有的小选框 同步状态 this.fruitList.forEach(item => item.isChecked = value) } }, // 统计选中的总数 reduce totalCount () { return this.fruitList.reduce((sum, item) => { if (item.isChecked) { // 选中 → 需要累加 return sum + item.num } else { // 没选中 → 不需要累加 return sum } }, 0) }, // 总计选中的总价 num * price totalPrice () { return this.fruitList.reduce((sum, item) => { if (item.isChecked) { return sum + item.num * item.price } else { return sum } }, 0) } }, methods: { del (id) { this.fruitList = this.fruitList.filter(item => item.id !== id) }, add (id) { // 1. 根据 id 找到数组中的对应项 → find const fruit = this.fruitList.find(item => item.id === id) // 2. 操作 num 数量 fruit.num++ }, sub (id) { // 1. 根据 id 找到数组中的对应项 → find const fruit = this.fruitList.find(item => item.id === id) // 2. 操作 num 数量 fruit.num-- } }, watch: { fruitList: { deep: true, handler (newValue) { // 需要将变化后的 newValue 存入本地 (转JSON) localStorage.setItem('list', JSON.stringify(newValue)) } } } }) </script> </body> </html>
效果:
技术总结:
本次Vue学习系列(一)结束,
欢迎大家在评论区友善留言、讨论。