v-for更新监测
<template>
<div>
<ul>
<li v-for="(val, index) in arr" :key="index">
{{ val }}
</li>
</ul>
<button @click="revBtn">数组翻转</button>
<button @click="sliceBtn">截取前3个</button>
<button @click="updateBtn">更新第一个元素值</button>
</div>
</template>
<script>
// 总结: 哪些方式会导致v-for更新
// 1. 翻转数组 可以
// 2. 截取数组 不可以
// 3. 单独字面量修改 不可以, 数组方法修改 可以
// 4. 数组整个被重新赋值, 可以
// 总结:
// 除了字面量[下标]方法以外的, "只要改变原数组"都会导致v-for更新
export default {
data() {
return {
arr: [5, 3, 9, 2, 1],
};
},
methods: {
revBtn() {
// 情况1. 数组翻转 arr
this.arr.reverse();
},
sliceBtn() {
// 情况2: 截取前3个
let newArr = this.arr.slice(0, 3);
this.arr = newArr; // 会导致原数组更新
},
updateBtn() {
// 情况3: 更新第一个元素值
// this.arr[0] = 100
this.arr.splice(0, 1, 100); // 替换原数组导致更新
// Vue还提供了内置 $set方法
// this.$set(this.arr, 0, 100) // 了解
},
},
};
</script>
<style>
</style>
v-for如何更新的
<template>
<div>
<ul>
<li v-for="(val, ind) in arr" :key="ind">
{{ val }}
</li>
</ul>
<button @click="btn">下标1位置插入新来的</button>
</div>
</template>
<script>
// 总结: v-for 到底如何更新的?
// 1. 第一次v-for, 准备一套DOM结构, 渲染到页面上
// 2. v-for 检测到目标结构变化, 再生成新的DOM结构, 和原来的DOM结构做对比, 只改变有差异的部分
// 3. 尽可能原地复用现有的标签
export default {
data(){
return {
arr: ['老大', "老二", '老三']
}
},
methods: {
btn(){
this.arr.splice(1, 0, '新来的')
}
}
}
</script>
<style>
</style>
key作用
<template>
<div>
<ul>
<li v-for="obj in arr" :key="obj.id">
{{ obj.name }}
</li>
</ul>
<button @click="btn">下标1位置插入新来的</button>
</div>
</template>
<script>
// 目标: key作用
// 无key属性, 和有key(值为索引), v-for更新时, (就地复用标签, 对比差异, 只更新变化的)
// 2. 有key(值为id), v-for更新时, 用key来对比新旧DOM, 会提高'更新'的性能
// key值要求: 唯一不重复, 数字或字符串
// 结论:
// (1): key属性提高更新时, 性能, dom变化后用key来做对比找出差异更新真实DOM
// (2): key的值要么用id, 没有id用索引值
export default {
data() {
return {
arr: [
{
name: '老大',
id: 50
},
{
name: '老二',
id: 31
},
{
name: '老三',
id: 10
}
],
};
},
methods: {
btn(){
this.arr.splice(1, 0, {
id: 19,
name: '新来的'
})
}
}
};
</script>
<style>
</style>
动态class
<template>
<div>
<!-- 语法: :class="{类名: 布尔值}" -->
<p :class="{redStr: true}">我是p标签, 尝试动态class的样式</p>
</div>
</template>
<script>
// 目标: 动态class
// 语法: :class="{类名: 布尔值}"
// 效果: 布尔值为true, 类名原地生效, 为false,不生效
export default {
}
</script>
<style>
.redStr{
color: skyblue;
}
</style>
动态style
<template>
<div>
<!-- 语法: :style="{css属性名: 值}" -->
<p style="color: red; backgroundcolor: pink">111</p>
<p :style="{ color: 'yellow', backgroundColor: 'skyblue' }">
我是p标签, 尝试动态style的样式
</p>
</div>
</template>
<script>
// 目标: 动态class
// 场景: 样式值会变, 需要采用动态style
// 语法: :style="{样式名: 样式值}"
export default {};
</script>
<style>
</style>
vue计算属性-computed
<template>
<div>
<p>和为: {{ num }}</p>
</div>
</template>
<script>
// 目标: 计算属性
// 作用(场景): 当一个变量的值, 通过其他变量计算而得来的
// 语法:
/**
* computed: {
* 计算属性名(){ // 它也是一个变量(所以不能和data里重名)
* // 必须return 值, 给这个计算属性使用
* }
* }
*/
// 效果: 计算属性里引用的变量发生值后, 重新执行计算属性函数里代码
export default {
data() {
return {
a: 10,
b: 8,
};
},
computed: {
num() {
return this.a + this.b;
},
},
};
</script>
<style>
</style>
vue计算属性-缓存
<template>
<div>
<p>{{ reverseMessage }}</p>
<p>{{ reverseMessage }}</p>
<p>{{ reverseMessage }}</p>
<p>{{ getMessage() }}</p>
<p>{{ getMessage() }}</p>
<p>{{ getMessage() }}</p>
</div>
</template>
<script>
// 目标: 计算属性'缓存'好处
// 好处: 计算属性有'缓存', 第一次计算结果后保存在内存当中, 当第二次使用时(值未发生过变化), 直接取值使用(不会调用函数)
// 当计算属性内使用变量值发生改变, 计算属性会从新算一次并缓存
export default {
data() {
return {
message: "Hello World",
};
},
computed: {
reverseMessage() {
console.log("计算属性 - 函数执行了");
return this.message.split("").reverse().join("");
},
},
methods: {
getMessage() {
console.log("methods - 函数执行了");
return this.message.split("").reverse().join("");
},
},
};
</script>
<style>
</style>
vue计算属性-完整写法
<template>
<div>
<!-- 表单value属性的值 和 vue变量的值 双向绑定 -->
<input type="text" v-model="full" />
</div>
</template>
<script>
// 目标: 计算属性完整写法
// 前提: 你要给计算属性, 赋值, 才使用'完整写法'
// 语法:
/**
* 只要返回值
computed: {
计算属性名(){
return 值
}
}
完整写法
computed: {
计算属性名:{
set(){}, // 有人给计算属性变量'赋值'的时候,自动触发此函数
get(){} // 有人要'使用'计算属性变量值得时候, 自动触发此函数并必须return值
}
}
*/
export default {
computed: {
// full(){
// return '你好啊'
// }
full: {
set(val) {
// 页面改变 -> set触发
console.log(val);
},
get() {
return "翟潇闻";
},
},
},
};
</script>
<style>
</style>
案例-全选和反选
<template>
<div>
<span>全选:</span>
<input type="checkbox" v-model="isAll" />
<button @click="fanFn">反选</button>
<ul>
<li v-for="(obj, index) in arr" :key="index">
<input type="checkbox" v-model="obj.c" />
<span>{{ obj.name }}</span>
</li>
</ul>
</div>
</template>
<script>
// 需求1: 小选 -> 全选
// 1.0 准备标签和样式
// 1.1 铺设li了, '小选框选中状态, 和对象的c属性双向绑定'
// 1.2 全选框, 选中状态, 也需要通过小选框们统计而得来呀
// 1.3 全选框, 定义计算属性叫 isAll
// 1.4 用eveny方法统计小选框关联的数组里数据, 返回给isAll显示到页面上, 全选用v-model="isAll"
// 需求1: 全选 -> 小选
// 2.0 思考页面全选后, 同步给v-model的isAll变量
// 2.1 isAll改写成了完整写法(带set/get)
// 2.2 在set中, 页面全选选中状态, 触发set方法传入true/false
// 2.3 遍历arr数组里每个对象, 给每个小选框同步状态
// 需求3: 点击反选
// 3.0 反选 - 点击事件
// 3.1 遍历数组里每个对象, 把对象的c属性取出, 取反, 在赋予回去
// 核心思想: 操作数据, 驱动视图(v-model关键选中状态)
export default {
data() {
return {
arr: [
{
name: "猪八戒",
c: false,
},
{
name: "孙悟空",
c: false,
},
{
name: "唐僧",
c: false,
},
{
name: "白龙马",
c: false,
},
],
};
},
methods: {
fanFn() {
this.arr.forEach((obj) => {
obj.c = !obj.c;
});
},
},
computed: {
isAll: {
set(val) {
// val变量值:(true/false)
// 全选框选中状态值, 赋予给每个小选框选中状态
this.arr.forEach((obj) => {
obj.c = val;
});
},
get() {
// 所有符合条件的, 返回的是true
// 有一个不符合条件的, 返回的是false
return this.arr.every((obj) => obj.c === true);
},
},
},
};
</script>
vue侦听器-watch
<template>
<div>
<input type="text" v-model="userName" />
</div>
</template>
<script>
// 目标: 侦听某个变量'值'的改变
// 语法:
/**
watch: {
被侦听的变量名(newVal,oldVal){
只要侦听的变量名值改变, 函数就会自动触发
}
}
*/
export default {
data() {
return {
userName: "",
};
},
watch: {
userName(newVal, oldVal) {
console.log(newVal, oldVal);
},
},
};
</script>
<style>
</style>
vue侦听器-深度侦听
<template>
<div>
<input type="text" v-model="user.name" />
<input type="text" v-model="user.age" />
</div>
</template>
<script>
// 目标: 侦听对象值得变化
// 语法:
/**
watch: {
被侦听的变量名{
handler(newVal,oldVal){},
deep: true // 深度侦听
}
}
*/
// 原因: 变量里存的是对象(内存地址), 绑定失败, 改的是对象里面的属性值得改变, user本身并为发生变化
// 解决: 深度侦听
export default {
data() {
return {
user: {
name: "",
age: 0,
},
};
},
watch: {
// 如果非要拿到某个属性值新旧(了解)
// ["user.age"]
user: {
handler(newVal, oldVal) {
// newVal,oldVal 监听的user变量的值(本身就是个对象)
// 改变数据, 改的对象里的属性值, 对象本身并未修改(还是那个对象)
console.log(newVal, oldVal);
},
deep: true, // 深度侦听
},
},
};
</script>
<style>
</style>
案例-品牌管理
<template>
<div id="app">
<div class="container">
<!-- 顶部框模块 -->
<div class="form-group">
<div class="input-group">
<h4>品牌管理</h4>
</div>
</div>
<!-- 数据表格 -->
<table class="table table-bordered table-hover mt-2">
<thead>
<tr>
<th>编号</th>
<th>资产名称</th>
<th>价格</th>
<th>创建时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="obj in list" :key="obj.id">
<td>{{ obj.id }}</td>
<td>{{ obj.name }}</td>
<!-- 如果价格超过100,就有red这个类 -->
<!-- :class="{类名: 布尔值}" -->
<td :class="{ red: obj.price >= 100 }">{{ obj.price }}</td>
<td>{{ formatDate(obj.time) }}</td>
<td><a href="#" @click="delFn(obj.id)">删除</a></td>
</tr>
<tr style="background-color: #eee">
<td>统计:</td>
<td colspan="2">总价钱为: {{ allPrice }}</td>
<td colspan="2">平均价: {{ svgPrice }}</td>
</tr>
</tbody>
<tfoot v-if="list.length === 0">
<tr>
<td colspan="5" style="text-align: center">暂无数据</td>
</tr>
</tfoot>
</table>
<!-- 添加资产 -->
<form class="form-inline">
<div class="form-group">
<div class="input-group">
<input
type="text"
class="form-control"
placeholder="资产名称"
v-model="name"
/>
</div>
</div>
<div class="form-group">
<div class="input-group">
<input
type="number"
class="form-control"
placeholder="价格"
v-model.number="price"
/>
</div>
</div>
<!-- 阻止表单提交 -->
<!-- form 里按钮阻止默认提交行为.prevent -->
<button class="btn btn-primary" @click.prevent="addFn">添加资产</button>
</form>
</div>
</div>
</template>
<script>
// 需求1: 铺设表格数据
// 1.0 md里复制标签和数据
// 1.1 下载bootstrap并main.js中引入
// 1.2 v-for把数据渲染到表格里
// 1.3 价格加动态 class, 价格>100才有red类名
// 需求2: 添加资产
// 2.0 按钮 - 点击事件 事件方法
// 2.1 v-model和变量, 收集输入框内容
// 2.2 判断是否为空, 给提示
// 2.3 数据添加到数组里(形成一个新的对象)
// 2.4 细节: form 里按钮阻止默认提交行为.prevent
// 需求3: 删除功能
// 3.0 删除a - 点击事件和方法
// 3.1 事件触发传递对应id值
// 3.2 通过id找对应索引
// 3.3 数组splice方法, 传入索引删除数组元素 -> 标签里使用list的地方就会重新计算
// 3.4 tfoot加v-if判断,无值在显示
// 3.5 删除光了以后, 在新增bug解决(因为数组里无值, 所以要判断)
// 需求4: 时间格式处理
// 4.0 (第一种方法自己写/第二种方法moment包处理时间格式化)
// 4.1 下载moment, 引入moment
// 4.2 methods里定义formatDate函数
// 4.3 上面时间处, 调用methods里方法, 传递要被处理时间
// 4.4 formatDate函数里处理后, 把结果返回到标签显示
// 需求5: 完成总价和均价的计算
// 5.0 准备一行tr标签
// 5.1 准备2个计算属性变量, 并在标签内使用
// 5.2 在计算属性内, 计算总价和均价并返回
// 需求6: 把数组数据, 缓存到浏览器本地(localStorage)
// 6.0 添加资产/删除资产, 侦听list数组改变, 把数组保存到本地
import moment from "moment";
export default {
data() {
return {
name: "", // 名称
price: 0, // 价格
// list: [
// { id: 100, name: "外套", price: 199, time: new Date("2010-08-12") },
// { id: 101, name: "裤子", price: 34, time: new Date("2013-09-01") },
// { id: 102, name: "鞋", price: 25.4, time: new Date("2018-11-22") },
// { id: 103, name: "头发", price: 19900, time: new Date("2020-12-12") },
// ],
list: JSON.parse(localStorage.getItem("brandList")) || [], // 本地无值, 就给一个空数组(因为调用push方法)
};
},
methods: {
// 添加资产 - 点击事件
addFn() {
// 判断内容
if (this.name.trim().length === 0 || this.price === 0) {
alert("请输入内容");
return; // 阻止代码往下继续执行
}
// 新增
let theId =
this.list.length === 0 ? 100 : this.list[this.list.length - 1].id + 1;
this.list.push({
id: theId,
name: this.name,
price: this.price,
time: new Date(),
});
this.name = "";
this.price = 0;
},
delFn(id) {
// 通过要删除id, 找到对应下标, 才能用数组删除方法
let index = this.list.findIndex((obj) => obj.id === id);
this.list.splice(index, 1);
},
// 定义时间处理函数
formatDate(dateObj) {
return moment(dateObj).format("YYYY-MM-DD");
},
},
computed: {
allPrice() {
// 遍历数组每个对象, 进行累加
// 外面return 是把累加好的结果返回给allPrice使用
// 里面return是把每次累加的sum值, 返回给下一次函数调用sum的初始值
return this.list.reduce((sum, obj) => {
sum += obj.price;
return sum;
}, 0);
},
svgPrice() {
// toFixed保留两位小数
return (this.allPrice / this.list.length).toFixed(2);
},
},
watch: {
list: {
deep: true,
handler() {
// 只要list内发生改变, 此处就侦听到自动执行
// 存到本地
localStorage.setItem("brandList", JSON.stringify(this.list));
},
},
},
};
</script>
<style >
.red {
color: red;
}
</style>
作业1_买点书练习
<template>
<div>
<p>请选择你要购买的书籍</p>
<ul>
<li v-for="(obj, index) in arr" :key="index">
<span>{{ obj.name }}</span>
<button @click="buyFn(index)">买书</button>
</li>
</ul>
<table border="1" width="500" cellspacing="0">
<tr>
<th>序号</th>
<th>书名</th>
<th>单价</th>
<th>数量</th>
<th>合计</th>
</tr>
<tbody>
<tr v-for="(obj, index) in arr" :key="index">
<td>{{ index + 1 }}</td>
<td>{{ obj.name }}</td>
<td>{{ obj.price }}</td>
<td>{{ obj.count }}</td>
<td>{{ obj.price * obj.count }}</td>
</tr>
</tbody>
</table>
<p>总价格为: {{ allPrice }}</p>
</div>
</template>
<script>
// 目标: 目标: 把数据铺设到页面上, 当用户点击买书按钮, 书籍数量增加1, 并且要计算累计的和
// 1. 标签和数据
// 2. v-for循环li
// 3. v-for循环表格里数据
export default {
data() {
return {
arr: [
{
name: "水浒传",
price: 107,
count: 0,
},
{
name: "西游记",
price: 192,
count: 0,
},
{
name: "三国演义",
price: 219,
count: 0,
},
{
name: "红楼梦",
price: 178,
count: 0,
},
],
};
},
computed: {
allPrice(){
// 5. 统计总价
return this.arr.reduce((sum, obj) => {
sum += obj.price * obj.count
return sum;
}, 0)
}
},
methods: {
// 4. 修改索引对应 - 对象的count值
buyFn(ind){
this.arr[ind].count++
}
}
};
</script>
作业2_选你爱我求和
<template>
<div>
<!-- 无id时, 可以使用index(反正也是就地更新) -->
<div style="display: inline-block" v-for="(num, index) in arr" :key="index">
<!-- 4.用v-model收集用户选中的复选框对应的value值 -->
<input type="checkbox" :value="num" v-model="listArr" />
<span>{{ num }}</span>
</div>
<p>你选中的元素, 累加的值和为:{{ allCount }}</p>
</div>
</template>
<script>
// 目标: 选择数字, 求和
// 1. 标签和数据准备好
// 2. v-for生成标签解构, 复选框绑定value值
export default {
data() {
return {
arr: [9, 15, 19, 25, 29, 31, 48, 57, 62, 79, 87],
listArr: [],
};
},
// 3.计算属性
computed: {
allCount() {
// 5. 统计listArr和
return this.listArr.reduce((sum, num) => (sum += num), 0);
},
},
};
</script>
作用3_导航切换效果
<template>
<div class="wrap">
<div class="nav_left" id="navLeft">
<div class="nav_content">
<span
:class="{ active: index === selIndex }"
v-for="(obj, index) in arr"
:key="obj.first_id"
@click="btn(index)"
>{{ obj.first_name }}</span
>
</div>
</div>
<div class="down">
<i class="iconfont icon-xiajiantoubeifen gray"></i>
</div>
</div>
</template>
<script>
// 目标: 切换到移动端画面, 点击导航, 高亮
// 提示: 索引 / 高亮的class的样式
// 绑定点击事件, 传入对应索引
export default {
data() {
return {
// 保存用户点击的频道的索引
selIndex: 0,
arr: [
{
first_id: "0",
first_name: "热门",
},
{
first_id: "621",
first_name: "\u5496\u5561",
},
{
first_id: "627",
first_name: "\u996e\u98df",
},
{
first_id: "279",
first_name: "\u7537\u88c5",
},
{
first_id: "294",
first_name: "\u5973\u88c5",
},
{
first_id: "122",
first_name: "\u773c\u955c",
},
{
first_id: "339",
first_name: "\u5185\u8863\u914d\u9970",
},
{
first_id: "391",
first_name: "\u6bcd\u5a74",
},
{
first_id: "35",
first_name: "\u978b\u9774",
},
{
first_id: "39",
first_name: "\u8fd0\u52a8",
},
{
first_id: "153",
first_name: "\u7bb1\u5305",
},
{
first_id: "119",
first_name: "\u7f8e\u5986\u4e2a\u62a4",
},
{
first_id: "355",
first_name: "\u5bb6\u7eba",
},
{
first_id: "51",
first_name: "\u9910\u53a8",
},
{
first_id: "334",
first_name: "\u7535\u5668",
},
{
first_id: "369",
first_name: "\u5bb6\u88c5",
},
{
first_id: "10",
first_name: "\u5bb6\u5177",
},
{
first_id: "223",
first_name: "\u6570\u7801",
},
{
first_id: "429",
first_name: "\u6c7d\u914d",
},
{
first_id: "546",
first_name: "\u5065\u5eb7\u4fdd\u5065",
},
{
first_id: "433",
first_name: "\u5b9a\u5236",
},
],
};
},
methods: {
btn(theIndex) {
// 用户点的索引保存一下
this.selIndex = theIndex;
},
},
};
</script>
<style>
.wrap {
width: 100%;
display: flex;
margin: 0.2rem 0 0 0;
position: relative;
}
/*左侧的导航样式*/
.nav_left {
width: 21.1875rem;
overflow: scroll;
}
.nav_left::-webkit-scrollbar {
display: none;
}
.nav_content {
white-space: nowrap;
padding: 0 0.7rem;
}
.nav_content span {
display: inline-block;
padding: 0.4rem 0.6rem;
font-size: 0.875rem;
}
.nav_content .active {
border-bottom: 2px solid #7f4395;
color: #7f4395;
}
.nav_left,
.down {
float: left;
}
/*右侧导航部分*/
.down {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
}
.gray {
color: gray;
display: inline-block;
vertical-align: middle;
}
</style>
作业4_学生信息管理
<template>
<div id="app">
<div>
<span>姓名:</span>
<input type="text" v-model.trim="user.name" />
</div>
<div>
<span>年龄:</span>
<input type="number" v-model.number="user.age" />
</div>
<div>
<span>性别:</span>
<select v-model="user.sex">
<option value="男">男</option>
<option value="女">女</option>
</select>
</div>
<div>
<button @click="addOrEditFn">添加/修改</button>
</div>
<div>
<table
border="1"
cellpadding="10"
cellspacing="0"
v-show="arr.length > 0"
>
<tr>
<th>序号</th>
<th>姓名</th>
<th>年龄</th>
<th>性别</th>
<th>操作</th>
</tr>
<tr v-for="(obj, index) in arr" :key="index">
<td>{{ index + 1 }}</td>
<td>{{ obj.name }}</td>
<td>{{ obj.age }}</td>
<td>{{ obj.sex }}</td>
<td>
<button @click="delFn(index)">删除</button>
<button @click="editFn(obj, index)">编辑</button>
</td>
</tr>
</table>
</div>
</div>
</template>
<script>
// 需求1: 铺设页面, 准备初始的数据(自己手写数据结构) - 前面是数组索引+1 *作为序号
// 需求2: 当输入框没有值, 要给用户一个提示, 必须都有值才能增加新数据 (数据驱动页面哦)
// 需求3: 添加功能 - 想好数据结构统一对象的key
// 需求4: 点击编辑功能, 把值赋予到输入框上(不要操作dom, 数据驱动页面)
// 需求5: 用户修改后, 点击相同按钮 - 想想怎么判断怎么是添加还是修改的功能 (提示: 准备一个全局变量, 点过编辑按钮可以让它为true) - 实现编辑后更新页面效果
// 需求6: 点击删除, 删除这行数据
export default {
data() {
return {
user: {
// 表单里绑定对象
name: "",
age: 0,
sex: "",
},
editIndex: -1, // 正在编辑的索引默认打开网页是没有的
arr: [
// 数据源
{
name: "Tom",
age: 19,
sex: "男",
},
{
name: "Jone",
age: 21,
sex: "女",
},
],
};
},
methods: {
addOrEditFn() {
// 添加/修改方法
if (
this.user.name.length === 0 ||
this.user.age === 0 ||
this.user.sex.length === 0
) {
alert("请输入内容");
return;
}
if (this.editIndex === -1) {
// 新增
this.arr.push({ ...this.user });
} else {
// 编辑
this.$set(this.arr, this.editIndex, { ...this.user }); // 修改数组里某个值导致v-for更新
}
// 清空输入框
this.user.name = "";
this.user.age = 0;
this.user.sex = "";
},
editFn(theObj, index) {
// 编辑-点击事件
this.user = { ...theObj }; // 回显
this.editIndex = index; // 正在编辑的索引
},
delFn(index) {
// 删除方法
this.arr.splice(index, 1);
},
},
};
</script>
|