vue——v-model,computed,watch(内含项目实战)
目录
双向数据绑定指令v-model
文本输入框的双向数据绑定
单选框的双向数据绑定
复选框的双向数据绑定
单个复选按钮的双向数据绑定
下拉单选按钮的双向数据绑定
计算属性 computed
方法调用
调用计算属性
响应式数据更新
数据变动侦听器watch
侦听 基本类型 的数据变动
侦听对象类型的数据变动
知识扩展
解决方法
实战项目:购物车
v-for 遍历商品项
增加商品数
减少商品数
计算商品总数
计算商品总价
完整代码参考
双向数据绑定指令v-model
将表单元素的值与Vue实例中的数据进行绑定,当vue内的数据发生改变时, 网页视图会自动更新;当用户手动更改 input 的值, 数据也会自动更新。 (v-model只能用于表单元素)
文本输入框的双向数据绑定
对于单个文本输入框, v-model 绑定的是 input 输入框元素的 value 属性(即默认填充的文本值)
<p>文本框:{{ test.word }}</p>
<input type="text" v-model="test.word">
const test=reactive({
word:"",
});
单选框的双向数据绑定
对于多个单选框, v-model 绑定的是 input 单选元素组的选中的值value
你的爱好是: <ins>{{ test.radio }}</ins><br>
<input type="radio" value="健身" v-model="test.radio" name="hobby">健身
<input type="radio" value="阅读" v-model="test.radio" name="hobby">阅读
<input type="radio" value="旅游" v-model="test.radio" name="hobby">旅游
const test=reactive({
radio:"",
});
网页勾选选项后 的内部数据更新
复选框的双向数据绑定
对于多个复选框, v-model 绑定的是 input 复选元素组的选中的值value
你的爱好是: <ins>{{ test.checkbox }}</ins><br>
<input type="checkbox" value="健身" v-model="test.checkbox">健身
<input type="checkbox" value="阅读" v-model="test.checkbox">阅读
<input type="checkbox" value="旅游" v-model="test.checkbox">旅游
const test=reactive({
checkbox:[],
});
网页勾选选项后 的内部数据更新
单个复选按钮的双向数据绑定
对于单个复选框, v-model 绑定的是单个 input 复选按钮的是否被选中的布尔值
需要记住密码? <ins>{{ test.checkbox }}</ins><br>
<input type="checkbox"v-model="test.checkbox">需要记住密码
const test=reactive({
checkbox:false,
});
下拉单选按钮的双向数据绑定
对于 <select>, v-model 绑定的是 select 元素众多选项中被选中的值value
<p>您的专业是:{{test.select}}</p>
<select v-model=" test.select">
<option value="计算机">计算机</option>
<option value="数字媒体">数字媒体</option>
<option value="护理">护理</option>
<option value="中医">中医</option>
</select>
const test=reactive({
select:[],
});
网页勾选选项后 的内部数据更新
计算属性 computed
计算属性通过对已有属性进行计算而得到新的属性值,其基于它的响应式依赖进行缓存的,只有当计算属性的相关响应式依赖发生改变时才会更新值
方法调用
使用方法调用时,程序会不厌其烦的重复执行 getter 函数来获取计算结果,这就意味着需要不止一次这样的计算,随着次数的增多非常耗性能,网页的负担也越大。
解析:此代码中执行了两次 getter 函数
<p>{{ updateTest() }}</p>
<p>{{ updateTest() }}</p>
const test=reactive({
x:20,
y:30
})
const updateTest=()=>{
console.log("调用普通的方法updateTest,不做缓存")
return test.x+test.y
}
方法调用
调用计算属性
使用计算属性可以避免像调用方法调用一样的问题,两种方式在结果上确实是相同的,然而,不同之处在于计算属性值会基于其响应式依赖被缓存(也就是说程序会帮你将这个被调用函数的计算结果进行缓存,当你需要时,程序会立即返回先前缓存的计算结果,就不用 重复执行 getter 函数);只有当响应式依赖的值更新时才重新计算。
解析:此代码中执行了一次 getter 函数,之后计算结果被缓存,直到响应式数据被更新,否则不会执行getter 函数
//计算属性为属性,所以后面无需像调用方法那样跟上括弧
<p>{{ updateTest_1}}</p>
<p>{{ updateTest_1 }}</p>
const test=reactive({
x:20,
y:30
})
const updateTest_1=computed(()=>{
console.log("调用带计算属性的方法updateTest_1,并缓存起来")
return test.x-test.y
})
调用计算属性 结果
响应式数据更新
不同于方法调用的是,计算属性是基于它的依赖来进行缓存的,只有依赖的属性发生变化时才会重新计算,否则会直接返回缓存的值
方法调用——响应式数据更新
解析:由于响应式被数据更新,所以再次执行两次getter 函数;此代码中共执行了四次getter 函数,
<p>{{ updateTest() }}</p>
<p>{{ updateTest() }}</p>
x <input type="text" v-model.number="test.x"> <br>
<p>您输入的x值为 {{test.x}}, 类型为 {{typeof test.x}}</p>
const test=reactive({
x:20,
y:30
})
const updateTest=()=>{
console.log("调用普通的方法updateTest,不做缓存")
return test.x+test.y
}
方法调用- 响应式数据发生变动
计算属性——响应式数据更新
由于响应式数据被更新,计算属性也随之更新,这里执行了一次getter 函数;此代码中执行了两次getter 函数,其余为缓存
<p>{{ updateTest_1}}</p>
<p>{{ updateTest_1 }}</p>
y <input type="text" v-model.number="test.y"> <br>
<p>您输入的y值为 {{test.y}}, 类型为 {{typeof test.y}}</p>
const test=reactive({
x:20,
y:30
})
const updateTest_1=computed(()=>{
console.log("调用带计算属性的方法updateTest_1,并缓存起来")
return test.x-test.y
})
计算属性 数据发生变动
数据变动侦听器watch
用于监测页面上的数据变化,当数据发生改变时,可以更新页面内容、触发其他操作或发送请求等
侦听 基本类型 的数据变动
watch侦听hobby属性的变化。当hobby发生改变时,会执行回调函数(newValue,oldValue),打印出变化的内容;这里嵌套了 if条件,如果条件满足则往下执行,反之则停止。
爱好
<br>
<select v-model="hobby">
<option value="请选择">请选择</option>
<option value="健身">健身</option>
<option value="旅游">旅游</option>
<option value="阅读">阅读</option>
</select>
const hobby = ref("请选择")
watch(hobby,function(newValue,oldValue){
console.log("hobby的旧值:", oldValue, " --> hobby的新值:", newValue);
if(newValue=="健身"){
console.log("你的爱好是健身");
}
}
)
打印出 watch监听 变化的内容
侦听对象类型的数据变动
血型信息收集:
<br>
<select v-model="test.blood">
<option value="请选择">请选择</option>
<option value="A型">A型</option>
<option value="B型">B型</option>
<option value="AB型">AB型</option>
</select>
<select v-model="test.sex">
<option value="请选择">请选择</option>
<option value="男">男</option>
<option value="女">女</option>
</select>
const test= reactive({
blood:"请选择",
sex:"请选择",
});
watch(test,
function(newValue,oldValue){
console.log("test的旧值:", oldValue, " --> test的新值:", newValue);
}
)
侦听对象类型的数据变动
注意:在这里`newValue` 此处和 `oldValue` 是相同的,因为它们是同一个对象`test`
以上实验失败原因:在JS中,对象及数组内的值是靠引用来获取的,当修改对象或数组的值时, 实际上修改的并不是值而是对象或数组的引用;如果修改了对象或数组的值,那么打印出来的结果则是修改后的值。
知识扩展
watch 的第一个参数可以是不同形式的“数据源”:它可以是一个 ref (包括计算属性)、一个响应式对象、一个 getter 函数、或多个数据源组成的数组:
单个ref
const x=ref("0")
//单个ref
watch (x,(newValue,oldValue)=>{
console.log("新值:"+newValue,"旧值:"+oldValue)
})
响应式对象
注:不能直接侦听响应式对象的属性值(如下)
血型
<br>
<input type="text" v-model="test.blood">
const test= reactive({
blood:"A型",
});
watch(test.blood,
function(newValue,oldValue){
console.log("test的旧值:", oldValue, " --> test的新值:", newValue);
}
)
直接侦听响应式对象的属性值 结果
那么如何 侦听响应式对象?
需要用一个返回该属性的 getter 函数( ()=>test.blood)
watch( ()=>test.blood,
function(newValue,oldValue){
console.log("test的旧值:", oldValue, " --> test的新值:", newValue);
}
)
添加getter 函数 侦听响应式对象
解决方法
我们可以添加一个返回该属性的 getter 函数来获取 对象“test” 内 “blood” 的值,这样就可以监听到对象类型的数据变动
watch( ()=>test.blood,
function(newValue,oldValue){
console.log("test的旧值:", oldValue, " --> test的新值:", newValue);
}
)
实战项目:购物车
用以上知识做一个购物车(如下)
屏幕录制 2024-11-26 184424
以下为项目初始代码,快来做做看吧!
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>实战小项目:购物车</title>
<style>
body {
font-family: Arial, sans-serif;
}
.cart-item {
width: 50%;
margin-bottom: 15px;
padding: 10px;
border: 2px solid gray;
border-radius: 10px;
background-color: #ddd;
}
.buttons {
margin-top: 5px;
}
.buttons button {
padding: 5px 10px;
margin-right: 5px;
font-size: 16px;
cursor: pointer;
border: none;
border-radius: 3px;
background-color: pink;
}
.buttons input {
width: 25px;
}
.buttons button:hover {
background-color: yellow;
}
.quantity {
font-size: 18px;
font-weight: bold;
margin-left: 10px;
}
h1, h2 {
color: #333;
}
</style>
</head>
<body>
<div id="app">
<h1>实战小项目:购物车</h1>
<!-- 提示:可以使用v-for指令,假设有n个品类,则生成n个商品项-->
<div class="cart-item">
<div class="buttons" >
<span>苹果 </span>
<button>-</button>
<span class="quantity">1 </span>
<button>+</button>
<p>
请输入价格:
<input type="text"/> 元/斤 <br>
单价:
1 元/斤
</p>
</div>
</div>
<!-- 提示:可以用计算属性或数据变动侦听器,跟踪商品数和单价的变化,进而求出总数和总价-->
<h3>商品总数: <ins > 1 </ins> 件</h3>
<h3>商品总价: <ins > 1 </ins> 元</h3>
</div>
<script type="module">
import { createApp, reactive, computed } from './vue.esm-browser.js'
createApp({
setup() {
// 1.定义属性:存储商品的(响应式)数组
const cartItems = reactive([
{ name: '苹果', quantity: 1, unit_price: 1 },
{ name: '香蕉', quantity: 1, unit_price: 1 },
{ name: '菠萝', quantity: 1, unit_price: 1 },
]);
// 2.定义方法:增加商品数
// 3.定义方法:减少商品数
// 4.定义方法:计算商品总数
// 5.定义方法:计算商品总价
// 6.暴露属性和方法
},
}).mount('#app');
</script>
</body>
</html>
v-for 遍历商品项
利用v-for 来获取对象内的值,即使对象内的值更新,v-for也会随之更新。这里用v-for 遍历数组cartItems 内的值和索引。
<div class="cart-item" v-for="(value,index) in cartItems">
<div class="buttons">
//此代码中根据索引将数组内的商品名单拎出来,根据索引对应到各自的商品项,
//即当v-for遍历到索引为0的数组值时,此代码内的索引也为0,
//那么单拎出来的商品名为“苹果”,以此类推
<span>{{ cartItems[index].name }} </span>
<button>-</button>
<span class="quantity">1 </span>
<button>+</button>
<p>
请输入价格:
<input type="text"/> 元/斤 <br>
单价:
1 元/斤
</p>
</div>
//遍历属性内的值
const cartItems = reactive([
{ name: '苹果', quantity: 1, unit_price: 1 },
{ name: '香蕉', quantity: 1, unit_price: 1 },
{ name: '菠萝', quantity: 1, unit_price: 1 },
]);
当属性内的值更新时:
const cartItems = reactive([
{ name: '苹果', quantity: 1, unit_price: 1 },
{ name: '香蕉', quantity: 1, unit_price: 1 },
{ name: '菠萝', quantity: 1, unit_price: 1 },
{ name: '榴莲', quantity: 1, unit_price: 1 },
]);
增加商品数
先定义add来实现商品数的增加,然后用v-on监听鼠标事件,事件被触发时,会执行指定的方法。
由于有多个商品项增加按钮,所以我们可以通过调用v-for 的索引来区分,增加的商品数为哪个商品。
(即当用户点击增加苹果商品数时,此时鼠标监听到的索引(index)为“ 0 ”,那么对应的增加的商品数量为 cartItems[0].quantity 的)
<button @click="add(index)">+</button>
// 2.定义方法:增加商品数
const add=(index)=>{
cartItems[index].quantity+=1
}
减少商品数
和增加商品数一样,先定义nagtive来实现商品数的增加,然后用v-on监听鼠标事件,事件被触发时,会执行指定的方法。
<button @click="nagtive(index)">-</button>
// 3.定义方法:减少商品数
const nagtive=(index)=>{
cartItems[index].quantity-=1
}
当商品数减到0,商品数无法再往下减,我们可以使其在一个停止不减的状态。当商品数<=0时,使商品数等于0,这样商品数的最低值为0(也可以弄一个弹窗,提示用户)
// 3.定义方法:减少商品数
const nagtive=(index)=>{
cartItems[index].quantity-=1
if(cartItems[index].quantity<=0){
cartItems[index].quantity=0
//alert("你的购物车已为空!!")
}
}
计算商品总数
解析:用计算属性或数据变动侦听器,跟踪商品数的变化,进而求出总数
先定义count来实现计算商品总数,先命名一个常量total_count 为0,便于计算商品总数;计算前我们需要得到各商品的商品数变化,利用 for 将商品项(cartItems)遍历一遍,得到各个商品项的商品数,计入常量total_count,再返回total_count计算结果
//将计算结果渲染出来
<h3>商品总数: <ins > {{count}} </ins> 件</h3>
// 4.定义方法:计算商品总数
const count=computed(()=>{
新常量,用于承载 各个商品项数量
let total_count=0;
item_count 为新常量,用于装for所遍历的值
for(const item_count of cartItems){
每一次遍历的商品项数量都计入 常量total_count
total_count +=item_count.quantity;
}
返回计算结果
return total_count
})
计算商品总价
解析:用计算属性或数据变动侦听器,跟踪商品数和单价的变化,进而求出总价
先定义money来实现计算商品总数,先命名一个常量total_money为0,便于计算商品总价;计算前我们需要得到各商品的商品数变化以及商品单价变化,首先用v-model 来得到输入的商品单价;其次,利用 for 将商品项(cartItems)遍历一遍,得到每个商品项变化后的商品数量以及商品单价;最后将每个商品项的单价和商品数相乘,最后计入常量total_money,再返回total_money计算结果
v-mode 获取数据
请输入价格:
<input type="text" v-model="cartItems[index].unit_price"/> 元/斤 <br>
单价:
1 元/斤
渲染计算结果
<h3>商品总价: <ins > {{money}} </ins> 元</h3>
// 5.定义方法:计算商品总价
const money=computed(()=>{
let total_money=0;
for(const item_money of cartItems){
total_money+=item_money.quantity*item_money.unit_price;
}
return total_money
})
完整代码参考
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>实战小项目:购物车</title>
<style>
body {
font-family: Arial, sans-serif;
}
.cart-item {
width: 50%;
margin-bottom: 15px;
padding: 10px;
border: 2px solid gray;
border-radius: 10px;
background-color: #ddd;
}
.buttons {
margin-top: 5px;
}
.buttons button {
padding: 5px 10px;
margin-right: 5px;
font-size: 16px;
cursor: pointer;
border: none;
border-radius: 3px;
background-color: pink;
}
.buttons input {
width: 25px;
}
.buttons button:hover {
background-color: yellow;
}
.quantity {
font-size: 18px;
font-weight: bold;
margin-left: 10px;
}
h1, h2 {
color: #333;
}
</style>
</head>
<body>
<div id="app">
<h1>实战小项目:购物车</h1>
v-for 遍历商品项的 索引和值
<div class="cart-item" v-for="(value,index) in cartItems">
<div class="buttons">
渲染商品名
<span>{{ cartItems[index].name }} </span>
v-on 鼠标监听触发 增加商品数
<button @click="nagtive(index)">-</button>
渲染 商品项的数量
<span class="quantity">{{ cartItems[index].quantity }} </span>
v-on 鼠标监听触发 减少商品数
<button @click="add(index)">+</button>
<p>
v-model 获取数据更改
请输入价格:
<input type="text" v-model="cartItems[index].unit_price"/> 元/斤 <br>
单价:
1 元/斤
</p>
</div>
</div>
渲染 计算属性计算结果
<h3>商品总数: <ins > {{count}} </ins> 件</h3>
<h3>商品总价: <ins > {{money}} </ins> 元</h3>
</div>
<script type="module">
import { createApp, reactive, computed } from './vue.esm-browser.js'
createApp({
setup() {
// 1.定义属性:存储商品的(响应式)数组
const cartItems = reactive([
{ name: '苹果', quantity: 1, unit_price: 1 },
{ name: '香蕉', quantity: 1, unit_price: 1 },
{ name: '菠萝', quantity: 1, unit_price: 1 },
]);
// 2.定义方法:增加商品数
const add=(index)=>{
cartItems[index].quantity+=1
}
// 3.定义方法:减少商品数
const nagtive=(index)=>{
cartItems[index].quantity-=1
if(cartItems[index].quantity<=0){
cartItems[index].quantity=0
}
}
// 4.定义方法:计算商品总数
const count=computed(()=>{
let total_count=0;
for(const item_count of cartItems){
total_count +=item_count.quantity;
}
return total_count
})
// 5.定义方法:计算商品总价
const money=computed(()=>{
let total_money=0;
for(const item_money of cartItems){
total_money+=item_money.quantity*item_money.unit_price;
}
return total_money
})
// 6.暴露属性和方法
return{
cartItems,
add,
nagtive,
count,
money,
}
},
}).mount('#app');
</script>
</body>
</html>