Vue 3 作为前端开发领域的热门框架,为构建用户界面提供了高效且灵活的解决方案。在上一篇博客:深度解析Vue3中,我们了解到了响应式数据、v-on、v-if、v-for、v-bind等Vue相关属性,这篇我们继续探讨Vue的其他属性,为你揭开Vue的魅力所在。
一、双向绑定指令
1、基本概念
在前端处理表单时,我们常常需要将表单输入框的内容同步给 JavaScript 中相应的变量。手动连接值绑定和更改事件监听器可能会很麻烦,而此时我们就可以使用v-model指令****。
v-model用于在表单元素(如<input>、<textarea>、<select>)和组件上创建双向绑定指令。这说明了该指令可以同时实现数据从数据层(JavaScript)到视图层(HTML)的渲染,可以从视图层获取用户输入并更新数据层。
v-model
会忽略任何表单元素上初始的value
、checked
或selected
attribute。它将始终将当前绑定的 JavaScript 状态视为数据的正确来源。所以应该在 JavaScript 中使用响应式系统的API来声明该初始值。
2、实际应用
(1)文本框(text)
①单向绑定
数据是单向流动的,通常是从数据层流向视图层。当数据发生改变时, 视图会自动更新. 但用户手动更改 input 的值, 数据不会自动更新。比如:
html
<head>
<style>
.textColor{
color: red;
}
</style>
</head>
<body>
<div id="app">
<h3>文本框 <ins>{{ data.text }}</ins></h3>
input元素的value属性动态化(单向数据绑定): <input type="text" :value="data.text"> <br>
</div>
<script type="module">
import { createApp, reactive } from './vue.esm-browser.js'
createApp({
setup() {
const data = reactive({
text: " ",
})
return {
data
}
}
}).mount("#app")
</script>
</body>
运行结果如下:
看代码,如果用户输入文本框中输入内容后,在"文本框"的后面没有实时更新相应内容,这便是单向绑定。
②双向绑定指令
数据不仅会从数据层流向视图,视图上的变化也会实时更新到数据层,二者相互影响。比如:
html
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>双向数据绑定指令v-model (实时渲染)</title>
<style>
.textColor{
color: red;
}
</style>
</head>
<body>
<div id="app">
<h3>文本框 <ins>{{ data.text }}</ins></h3>
双向数据绑定: <input type="text" v-model="data.text">
</div>
<script type="module">
import { createApp, reactive } from './vue.esm-browser.js'
createApp({
setup() {
const data = reactive({
text: "输入姓名",
})
return {
data
}
}
}).mount("#app")
</script>
</body>
运行结果如下:
双向绑定指令则会根据用户输入的内容实时更新到"文本框"后面。
(2)单选框(radio)
用于记录用户的单选选择,将用户选中的值绑定到数据中。比如:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>双向数据绑定指令v-model (实时渲染)</title>
<style>
.textColor{
color: red;
}
</style>
</head>
<body>
<div id="app">
<h3>您的性别(单选):<ins> {{ data.radio }}</ins></h3>
<!-- 对于多个<input type="radio">, v-model 绑定的是 input 单选元素组的选中的值value -->
<input type="radio" v-model="data.radio" value="男" name="radio">男
<input type="radio" v-model="data.radio" value="女" name="radio">女
<hr>
</div>
<script type="module">
import { createApp, reactive } from './vue.esm-browser.js'
createApp({
setup() {
const data = reactive({
radio: "",
})
return {
data
}
}
}).mount("#app")
</script>
</body>
</html>
运行结果如下:
用户选择"男"或"女"后,data.radio的数据会实时更新为所选单选框的value值,并在页面上显示。
(3)多选框(checkbox)
用于记录用户的多选选择,将用户选中的值绑定到数据中。比如:
html
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>双向数据绑定指令v-model (实时渲染)</title>
<style>
.textColor{
color: red;
}
</style>
</head>
<body>
<div id="app">
<h3>您的爱好(多选): {{ data.checkbox }}</h3>
<input type="checkbox" v-model="data.checkbox" value="足球">足球
<input type="checkbox" v-model="data.checkbox" value="蓝球">蓝球
<input type="checkbox" v-model="data.checkbox" value="羽毛球">羽毛球
<hr>
</div>
<script type="module">
import { createApp, reactive } from './vue.esm-browser.js'
createApp({
setup() {
const data = reactive({
checkbox: [],
})
return {
data
}
}
}).mount("#app")
</script>
</body>
运行结果如下:
当用户勾选或取消勾选选项时,checkbox数组会添加或删除相应选项的value值,页面上显示checkbox数组内容的部分也会更新。
(4)单个复选按钮的双向数据绑定
单个复选按钮的双向绑定把复选按钮的选中状态 (视图层面)和一个布尔类型的数据(模型层面)关联起来。用户点击复选按钮改变其选中状态,这个状态变化会被"传递"给绑定的数据,使该数据变为true(选中时)或false(未选中时)。常用于网页登录的"记住密码",如:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>双向数据绑定指令v-model (实时渲染)</title>
<style>
.textColor{
color: red;
}
</style>
</head>
<body>
<div id="app">
<h3>需要记住密码? {{ data.remember }} </h3>
<input type="checkbox" v-model="data.remember">记住密码
</div>
<script type="module">
import { createApp, reactive } from './vue.esm-browser.js'
createApp({
setup() {
const data = reactive({
remember: false,
})
return {
data
}
}
}).mount("#app")
</script>
</body>
</html>
运行结果如下:
当用户点击记住密码时,返回true的值
(5)下拉框(select)
绑定用户选择的下拉项的值。如:
html
<head>
<style>
.textColor{
color: red;
}
</style>
</head>
<body>
<div id="app">
<h3>下拉选择,您的专业是: {{ data.select }}</h3>
<select v-model="data.select">
<option value="计算机">计算机</option>
<option value="数字媒体">数字媒体</option>
<option value="中医养生">中医养生</option>
<option value="针灸推拿">针灸推拿</option>
</select>
</div>
<script type="module">
import { createApp, reactive } from './vue.esm-browser.js'
createApp({
setup() {
const data = reactive({
select: ""
})
return {
data
}
}
}).mount("#app")
</script>
</body>
运行结果如下:
当用户在下拉框中选择一个选项时,select数据会更新为所选选项的value值,页面上显示select的内容也会相应改变。
二、计算属性
1、基本概念
计算属性(computed)是基于响应式数据进行计算得到的值。计算属性会根据它所依赖的数据的变化而自动重新计算,并且计算结果会被缓存起来。
2、实际应用
平时我们在网上购物时,选择多个商品或选多数量时,在购物车下方会计算商品的总数或价格,我们用其中的总数举例。
html
<body>
<div id="app">
<h3>购买水果总数量为: {{ add() }}</h3>
苹果购买数量: <input type="text" v-model.number="data.x"> <br>
香蕉购买数量:<input type="text" v-model.number="data.y"> <br>
</div>
<script type="module">
import { createApp, ref, reactive, computed } from './vue.esm-browser.js'
createApp({
setup() {
const data = reactive({
x: "",
y: "",
})
//方法-不做缓存
const add = () => {
console.log("调用普通的方法add,不做缓存")
return data.x + data.y
}
const sub = computed(() => {
console.log("调用带计算属性的方法sub,并缓存起来")
return data.x + data.y
})
return {data,add,sub}
}
}).mount("#app")
</script>
</body>
运行结果如下:
当用户输入购买数量时,sub函数会实时增加数量,计算购买总数量。
代码分析:
①add方法是一个普通的函数,当被调用时会在控制台输出相应信息,并返回data.x与data.y相加的结果。这里每次调用add方法都会重新执行该函数内的逻辑,不会对结果进行缓存。
②sub是通过computed函数创建的计算属性。当它所依赖的data.x和data.y发生变化时,会自动重新计算结果,并在控制台输出相应信息。而且计算属性会对结果进行缓存,只有在其依赖的数据发生改变时才会重新计算,相比于普通方法在性能上可能更优(当多次获取结果且依赖数据未变时)。
3、优势
- 计算属性会缓存计算结果 。比如:当购物车内的商品只修改了名称,但其价格和数量没有被改变时,计算属性中计算总数量和总价便不会重新计算,因为他们所依赖的数据没有发生变化,这样也可以更好地避免进行过多的计算。
- 当计算机属性所依赖的数据发生变化时,计算的数据也会实时更新,这也能确保数据的有效性。
三、侦听器
1、基本概念
计算属性允许我们声明性地计算衍生值。然而在有些情况下,我们需要在状态变化时执行一些"副作用":例如更改 DOM,或是根据异步操作的结果去修改另一处的状态,而此时我们可以利用侦听器。
侦听器(watch)用于观察和响应数据的变化。他允许数据在发生变化时执行特定的操作。比如:异步操作等。
2、实际应用
当用户选择选项后在后台弹出提示,比如:
html
<body>
<div id="app">
<select v-model="date.year">
<option value="">请选择</option>
<option value="2023">2023</option>
<option value="2024">2024</option>
<option value="2025">2025</option>
</select>
年
<select v-model="date.month">
<option value="">请选择</option>
<option value="10">10</option>
<option value="11">11</option>
<option value="12">12</option>
</select>
月
</div>
<script type="module">
import { createApp, ref, reactive, watch } from './vue.esm-browser.js'
createApp({
setup() {
const date = reactive({ //日期
year: "2023",
month: "10"
})
watch( () => date.year,
(newValue, oldValue) => {
console.log("date.year的旧值:", oldValue, " --> date.year的新值:", newValue)
if (date.year == "2024") {
console.log("您选中的年份是:2024年")
}
}
)
return { date }
}
}).mount("#app")
</script>
</body>
在这个例子中,watch函数监听data对象。当data对象发生变化时,回调函数就会被执行,并且可以获取到变化后的新值(newValue)和变化前的旧值(oldValue)。即用户选择了2024后,控制台则会输出上一个年份和用户当前选择的年份,如下图:
注:代码中的初始年份为2023
3、与计算机属性的区别
(1)计算属性:是基于依赖数据计算得到一个值,并且有缓存机制。只要依赖数据不变,计算属性的值就不会重新计算。它主要用于根据已有数据计算出一个新的值用于显示等场景。
(2)侦听器:重点在于监听数据的变化,然后执行副作用操作 (如发送网络请求、修改其他数据等)。它没有计算属性那样的缓存机制,每次数据变化都会执行回调函数。
四、实践项目:实现购物车功能
通过Vue的学习,我们可以更好地利用他们的特性,制作一个购物车,实现以下从左图到右图的效果,用户通过点击"+"或"---"的按钮选择购买商品的数量,在文本框内输入商品单价后会自动计算所购买的商品总数和总价。
在实现该项目时,我们可以先制作一个简单的框架
html
<!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>
<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>
</body>
</html>
该代码包含了HTML和CSS样式, 这其中包含着之前的知识点,大家可以查看前面相关博客进行书写,也可以复制该代码,运用Vue的相关属性实现购物车的功能。
1、创建一个Vue应用程序
javascript
<script>
createApp({
setup() {
}
}).mount("#app")
</script>
接下来我们就在该代码内实现我们想要的效果
2、插入数组
使用reactive函数创建了一个名为cartItems的响应式数组,数组内包含了三个初始商品对象,每个对象有name(商品名称)、quantity(商品数量)、price(商品价格)三个属性,初始值分别设定了一些默认值(如数量和价格都为0),用于存储购物车中的商品信息。随着用户在页面上的操作(如调整数量、输入价格),这些数据会实时更新。
javascript
const cartItems = reactive([
{ name: '苹果', quantity: 0, price: 0 },
{ name: '香蕉', quantity: 0, price: 0 },
{ name: '菠萝', quantity: 0, price: 0 }
]);
增加数组后我们要将其遍历出来则可以使用v-for指令,假设有n个商品,则生成n个商品项( 为空格)
javascript
<div class="cart-item" v-for="(item, index) in cartItems">
<span>{{item.name}} </span>
<span class="quantity">{{cartItems[index].quantity}} </span>
</div>
</div>
3、增加或减少商品数量
首先在button元素内设置按键,用于用户增加或减少商品数量,通过v-on:click指令绑定按钮
javascript
<button v-on:click="decreaseQuantity(index)">-</button>
<button v-on:click="increaseQuantity(index)">+</button>
两行代码分别位于商品名称的左右,代码如下:
javascript
<button v-on:click="decreaseQuantity(index)">-</button>
<span class="quantity">{{cartItems[index].quantity}} </span>
<button v-on:click="increaseQuantity(index)">+</button>
接下来在script元素内接受一个index参数,用于指定要增加数量的商品在cartlItems数组中的索引位置,在内部将商品的数量加1,实现增加数量的功能。
javascript
const increaseQuantity = (index) => {
cartItems[index].quantity++;
};
与加法类似,在内部将商品的数量减1,实现减少数量的效果,但在减1之前应该先判断商品数量(quantity)是否大于0,是则减1,但要限制数量不能少于0
javascript
const decreaseQuantity = (index) => {
if (cartItems[index].quantity > 0) {
cartItems[index].quantity--;
}
};
其中decreaseQuantity(index)和increaseQuantity(index),其中index作为商品在cartItems数组内的索引。
4、输入价格
通过v-model双向绑定指令,使其能够实时更新商品的价格,同时在下面展示输入单价。
html
<p>
请输入价格:
<input type="text" v-model="cartItems[index].price" /> 元/斤 <br>
单价:{cartItems[index].price}} 元/斤
</p >
5、计算总价和总数量
通过computed函数创建一个totalItems属性,这样当购物时商品数量增加或减少可以实时更新。
①使用reduce方法,在该函数内有两个参数(total,item),其中total作为累加器,初始值为0(由reduce函数的第二个参数指定),它会累计每次回调函数执行后的结果。item是cartItems数组中的每个商品对象。在每次回调执行时,将当前商品对象的quantity属性值累加到total上,这样遍历完整个cartItems数组后,total就得到了所有商品数量的总和。
javascript
const totalItems = computed(() => {
return cartItems.reduce((total, item) => total + item.quantity, 0);
});
扩展
reduce函数:用于将数组中的元素通过某种方式累积为一个单一的值。它可以对数组中的每个元素执行一个由用户提供的回调函数,把这个函数的返回值不断累积,最后返回一个最终结果。
举个例子:
javascriptconst numbers = [1, 2, 3, 4, 5]; const sum = numbers.reduce((a, b) => { return a + b;}, 0);
在该代码中初始值0提供给了a,b的值为1,返回0+1=1作为新的a的值;第二次则a为1,b为2,返回1+2=3作为a的值,以此类推。
②通过reduce函数我们也可以实现计算商品总价的功能
javascript
const totalPrice = computed(() => {
return cartItems.reduce((total, item) =>
total + (item.quantity * item.price), 0);
});
6、return语句
最后通过return语句将对象和方法暴露出来,使得它们可以在HTML模板中进行数据绑定和方法调用,让Vue实例与页面元素关联起来,从而实现整个购物车功能在页面上的呈现和交互。
javascript
return {
cartItems,
totalItems,
totalPrice,
increaseQuantity,
decreaseQuantity
};
该示例可以清晰地看到如何运行Vue的各种属性,通过事件绑定、计算属性等来构建一个简单而使用的购物车应用。大家也来试试看吧!
完整代码如下;
html
<!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" v-for="(item, index) in cartItems">
<div class="buttons">
<span>{{item.name}} </span>
<button v-on:click="decreaseQuantity(index)">-</button>
<span class="quantity">{{cartItems[index].quantity}} </span>
<button v-on:click="increaseQuantity(index)">+</button>
<p>
请输入价格:
<input type="text" v-model="cartItems[index].price" /> 元/斤 <br>
单价:
{{cartItems[index].price}} 元/斤
</p >
</div>
</div>
<!-- 提示:可以用计算属性或数据变动侦听器,跟踪商品数和单价的变化,进而求出总数和总价-->
<h3>商品总数: <ins> {{totalItems}} </ins> 件</h3>
<h3>商品总价: <ins> {{totalPrice}} </ins> 元</h3>
</div>
<script type="module">
import { createApp, reactive, computed } from './vue.esm-browser.js';
createApp({
setup() {
const cartItems = reactive([
{ name: '苹果', quantity: 0, price: 0 },
{ name: '香蕉', quantity: 0, price: 0 },
{ name: '菠萝', quantity: 0, price: 0 }
]);
// 计算商品总数
const totalItems = computed(() => {
return cartItems.reduce((total, item) => total + item.quantity, 0);
});
// 计算商品总价
const totalPrice = computed(() => {
return cartItems.reduce((total, item) => total + (item.quantity * item.price), 0);
});
const increaseQuantity = (index) => {
cartItems[index].quantity++;
};
const decreaseQuantity = (index) => {
if (cartItems[index].quantity > 0) {
cartItems[index].quantity--;
}
};
return {
cartItems,
totalItems,
totalPrice,
increaseQuantity,
decreaseQuantity
};
}
}).mount('#app');
</script>
</body>
</html>