vue第三天
侦听器
可以侦听data/computed属性值改变
语法:
-
watch: { "被侦听的属性名" (newVal, oldVal){ } }
注:写在data同级
深度侦听和立即执行
侦听复杂类型, 或者立即执行侦听函数
immediate 可以再页面进来时马上触发一次当前监听回调
deep 可以监听到复杂数据内部的改变
示例:
<template>
<div>
<input type="text" v-model="msg" />
<br />
user.name
<input type="text" v-model="user.name" />
<br />
user.age
<input type="text" v-model="user.age" />
<br />
<input type="text" v-model="num1" />
总数: {{ num2 }}
</div>
</template>
<script>
export default {
data() {
return {
msg: "jack",
user: {
name: "rose",
age: 23,
},
num1: 2,
num2: 3,
};
},
watch: {
// 以键值对的形式指定 被监听字段: 触发后回调函数
msg(newVal, oldVal) {
console.log(newVal, oldVal);
},
// 复杂类型无法直接监听, 需要多加额外配置
user: {
//deep 可以监听到复杂数据内部的改变
deep: true,
handler(newVal, oldVal) {
console.log(newVal, oldVal);
},
},
num1: {
// immediate 可以再页面进来时马上触发一次当前监听回调
immediate: true,
handler(newVal) {
this.num2 += newVal;
},
},
},
};
</script>
<style>
</style>
案例:
品牌管理案例使用侦听器添加本地存储功能
<template>
<div class="container">
<div class="row">
<div class="col-12 pt-3">
<table class="table table-bordered">
<thead>
<tr>
<th scope="col">编号</th>
<th scope="col">资产名称</th>
<th scope="col">价格</th>
<th scope="col">创建时间</th>
<th scope="col">操作</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in list" :key="item.id">
<th scope="row">{{ item.id }}</th>
<td>{{ item.name }}</td>
<td :class="{ expensive: item.price > 100 }">{{ item.price }}</td>
<td>{{ item.time | dataFilter }}</td>
<td>
<!-- 删除1.删除按钮,绑定事件 -->
<!-- 删除2.绑定事件获取要删除的元素索引 -->
<button
type="button"
class="btn btn-link"
@click="deleteProduct(index)"
>
删除
</button>
</td>
</tr>
<tr class="bg-light">
<th scope="row">统计</th>
<td colspan="2">总价:{{ totalPrices }}</td>
<td colspan="2">均价:{{ averagePrice }}</td>
</tr>
</tbody>
<!-- 删除4.控制空状态显示 -->
<tfoot v-show="list.length === 0">
<tr>
<td class="text-center" colspan="5">暂无数据</td>
</tr>
</tfoot>
</table>
</div>
</div>
<form class="row align-items-center">
<div class="col-3">
<input
type="text"
class="form-control"
placeholder="资产名称"
v-model="productName"
/>
</div>
<div class="col-3">
<input
type="text"
class="form-control"
placeholder="价格"
v-model.number="productPrice"
/>
</div>
<div class="col-3">
<button
type="submit"
class="btn btn-primary"
@click.prevent="addProperty"
>
添加资产
</button>
</div>
</form>
</div>
</template>
<script>
import moment from "moment";
export default {
name: "App",
data() {
return {
// 运用短路运算 如果有本地存储则取用本地存储渲染页面 没有则使用空数组渲染页面
list: JSON.parse(localStorage.getItem("list")) || [],
productName: "",
productPrice: 0,
};
},
watch: {
// 添加侦听器
list(newVal) {
// 通过侦听器添加本地存储功能
localStorage.setItem("list", JSON.stringify(newVal));
},
},
computed: {
//计算总价业务相关代码
totalPrices() {
let total = 0;
this.list.forEach((item) => {
total += item.price;
});
return total;
},
// 计算均价业务相关代码
averagePrice() {
let average = this.totalPrices / this.list.length;
return average;
},
},
filters: {
dataFilter(time) {
return moment(time).format("YYYY-MM-DD");
},
},
methods: {
addProperty() {
if (!this.productName || !this.productPrice) {
alert("资产名称或价格不能为空");
return;
}
//删除5.处理空数据情况下新增逻辑异常
let id;
if (this.list.length > 0) {
id = this.list[this.list.length - 1].id + 1;
} else {
id = 100;
}
this.list.push({
id,
name: this.productName,
price: this.productPrice,
time: new Date(),
});
(this.productName = ""), (this.productPrice = 0);
},
deleteProduct(index) {
//删除3.删除元素
// console.log(index);
this.list.splice(index, 1);
},
},
};
</script>
<style scoped>
.expensive {
color: red;
}
</style>
vue组件
组件概念
组件是可复用的 Vue 实例, 封装标签, 样式和JS代码
组件化 :封装的思想,把页面上 可重用的部分 封装为 组件,从而方便项目的 开发 和 维护
一个页面, 可以拆分成一个个组件,一个组件就是一个整体, 每个组件可以有自己独立的 结构 样式 和 行为(html, css和js)
组件基础使用-注册和使用
每个组件都是一个独立的个体, 代码里体现为一个独立的.vue文件
口诀: 哪部分标签复用, 就把哪部分封装到组件内
==(重要): 组件内template只能有一个根标签==
==(重要): 组件内data必须是一个函数, 独立作用域==
创建组件
封装标签+样式+js - 组件都是独立的, 为了复用
在app.vue同级目录文件下新建文件夹components(文件夹名可自定义---建议命名为components)
在该文件夹内创建组件
注册组件
全局注册使用
全局入口在main.js, 在new Vue之上注册
语法:
-
import Vue from 'vue' import 组件对象 from 'vue文件路径' Vue.component("组件名", 组件对象)
// @/ 表示在src文件下开始查找
import ProdItem from "@/components/ProdItem"
Vue.component("ProdItem", ProdItem)
全局注册ProdItem组件名后, 就可以当做标签在任意Vue文件中template里用
单双标签都可以或者小写加-形式, 运行后, 会把这个自定义标签当做组件解析, 使用==组件里封装的标签替换到这个位置==
组件名要两个或两个以上单词 采用驼峰命名或者-分隔
局部注册使用
语法:在script内注册
-
import 组件对象 from 'vue文件路径' export default { components: { "组件名": 组件对象 } }
组件注册使用总结:
-
(创建)封装html+css+vue到独立的.vue文件中
-
(引入注册)组件文件 => 得到组件配置对象
-
(使用)当前页面当做标签使用
组件-scoped的作用
目的: 解决多个组件样式名相同, 冲突问题
在style上加入scoped属性, 就会在此组件的标签上加上一个随机生成的data-v开头的属性
而且必须是当前组件的元素, 才会有这个自定义属性, 才会被这个样式作用到
总结: style上加scoped, 组件内的样式只在当前vue组件生效
scoped 可以将当前样式限定在这个文件内, 对多可以穿透到下一层的根元素,其他不受影响(即:只会影响到组件内最外层的同类名元素,组件内同类名的子元素则不受影响 ---组件内多套一层父元素,则不会影响到组件内同类名的元素---)
vue组件通信--传递方式
从一个组件内, 给另外一个组件传值
父传子
目标:父组件 -> 子组件 传值
首先明确父和子是谁, 在父引入子 (被引入的是子)
-
父: App.vue
-
子: MyProduct.vue
例如: App.vue(父) ProdItem.vue(子)
父:父组件作为属性将数据传交给子组件标签
子:
- 子组件内props, 定义变量, 准备接收, 然后使用变量
- 数据接收后直接可以使用了
总结: 组件封装复用的标签和样式, 而具体数据要靠外面传入
父传子--配合循环
把数据循环分别传入给组件内显示
例:
父:
<template>
<div>
<div class="item">app.vue文件里面的 item</div>
<!-- 通过变量传递参数 -->
<ProdItem
v-for="item in list"
:key="item.id"
:title="item.proname"
:price="item.proprice"
:info="item.info"
/>
</div>
</template>
<script>
//局部注册组件演示
import ProdItem from "@/components/ProdItem.vue";
export default {
data() {
return {
list: [
{
id: 1,
proname: "超级好吃的棒棒糖",
proprice: 18.8,
info: "开业大酬宾, 全场8折",
},
{
id: 2,
proname: "超级好吃的大鸡腿",
proprice: 34.2,
info: "好吃不腻, 快来买啊",
},
{
id: 3,
proname: "超级无敌的冰激凌",
proprice: 14.2,
info: "炎热的夏天, 来个冰激凌了",
},
],
};
},
components: {
ProdItem,
},
};
</script>
<style scoped>
.item {
color: orange;
}
div {
margin-top: 15px;
}
</style>
子:
<template>
<div>
<!-- 组件命名要以两个以上单词取名,否则报错 -->
<!-- 要封装的东西放入到组件内(html/css/js) -->
<div>
<!-- 父传子3.数据直接可以使用了 -->
<!-- 被声明过 props 接收的数据, 就能当做 data 一样使用 -->
<div class="item" @click="handleClick">
<h2>{{ title }}</h2>
<div>售价:{{ price }}</div>
<div>{{ info }}</div>
</div>
</div>
</div>
</template>
<script>
export default {
// 父传子2.子元素声明一个接收的配置 props
// 父组件可以传数据, 但是没有权限控制子组件怎么渲染
// 如果子组件愿意接收什么数据, 就以字符串数组的形式声明 props
// 比如这里我愿意接收 title price, info 三个属性
// props 可以声明接收任意数据, 但是父组件有没有传, 就不是子组件控制的了
props: ["title", "price", "info", "index"],
methods: {
handleClick() {
// console.log("被点击");
// 子传父1.子组件促发自定义事件,传递参数(可选)
this.$emit("kanjia", this.index, 3);
},
},
};
</script>
<style>
.item {
color: salmon;
border: 1px solid #ccc;
}
</style>
单向数据流
在vue中需要遵循单向数据流原则
1. 父组件的数据发生了改变,子组件会自动跟着变
2. 子组件不能直接修改父组件传递过来的props props是只读的
==父组件传给子组件的是一个对象,子组件修改对象的属性,是不会报错的,对象是引用类型, 互相更新==
总结: props的值不能重新赋值, 对象引用关系属性值改变, 互相影响
子传父
目标:子组件向父组件传递数据
- 子组件自定义事件方法,用户触发该事件时把数据传递出去
- 对应标签绑定监听:@时间名="函数"、
- 在methods声明函数:
methods:{函数() {this.$emit('自定义事件',参数1,参数2)}:}
例:
- 父组件监听子组件自定义的事件方法,绑定响应处理函数
- 监听子组件自定义事件: @自定义事件="处理函数"
- 在methods中声明处理函数:
组件是一个vue实例, 封装标签, 样式和JS代码
便于复用, 易于扩展
一方主动传递, 另一方表明愿意接受, 最后用起来
- 父传子
- 父组件以属性的形式传入数据
- 子组件 props 定义需要接受的数据
- 直接当成 data 使用即可
- 子传父
- 子组件主动触发自定义事件 this.$emit('自定义事件名')
- 父组件 监听 v-on 就是 @自定义事件名
- 父组件给个对应的处理函数