二.vue3基础
1.模板语法
1-1 第一个 vue 应用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<body>
<div id="app">
{{10 + 20}}
{{name}}
</div>
</body>
<script>
Vue.createApp(
{
data(){ // data:function(){}
return{
name: "phoenix"
}
}
}
).mount("#app")
</script>
</html>
- 推荐使用的 IDE 是 VSCode,配合 Vue 语言特性 (Volar) 插件。该插件提供了语法高亮、TypeScript 支持,以及模板内表达式与组件 props 的智能提示。
- Volar 取代了我们之前为 Vue 2 提供的官方 VSCode 扩展 Vetur。如果你之前已经安装了 Vetur,请确保在 Vue 3 的项目中禁用它。
1-2 应用动态绑定原理
Object.defineProperty(这种是 JS 中实现的方法,也是 Vue2 中使用的方法)
var obj = {}
var obox = document.getElementById("box")
Object.defineProperty(obj, "myname", {
get() {
console.log("有人访问了")
return obox.innerHTML
},
set(value) {
console.log("有人改变我了", value)
obox.innerHTML = value
}
})
/*
* 缺陷:无法监听数组的改变, 无法监听class改变, 无法监听Map Set结构。
*/
// ES6 proxy
var obj = {
}
var obox = document.getElementById("box")
var vm = new Proxy(obj, {
get(target, key) {
console.log("get")
return target[key]
},
set(target, key, value) {
console.log("set")
target[key] = value
obox.innerHTML = value
}
})
/*
vue3 基于Proxy ,ES6 Proxy,对不兼容ES6的浏览器,使用ES5的写法
if(支持proxy){
// proxy进行拦截处理, 实现功能
}else{
// object.defineProtery
}
浏览器插件 vue-tools
1-3 模板语法
-
最基本的数据绑定形式是文本插值,它使用的是“Mustache”语法 (即双大括号):
<span>Message: {{ msg }}</span>
-
双大括号标签会被替换为相应组件实例中 msg 属性的值。同时每次 msg 属性更改时它也会同步更新。
-
双大括号不能在 HTML attributes 中使用。想要响应式地绑定一个 attribute,应该使用 v-bind 指令:
<div v-bind:id="dynamicId"></div>
v-bind 指令指示 Vue 将元素的 id attribute 与组件的 dynamicId 属性保持一致。如果绑定的值是 null 或者 undefined ,那么该 attribute 将会从渲染的元素上移除。
当一个 html 标签上需要绑定多个值时候,可以使用 v-bind="xxx 对象"进行对象绑定,xxx 表示的是对象名称
-
表达式的支持
{{ number + 1 }} {{ ok ? 'YES' : 'NO' }} {{ message.split('').reverse().join('') }} <div :id="`list-${id}`"></div>
-
指令 v-on、@
<a v-on:click="doSomething"> ... </a> <!-- 简写 --> <a @click="doSomething"> ... </a>
1-4 Todolist-案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<body>
<div id="app">
<input type="text" v-model="mytext" @input="handleInput">
<button @click="handleAdd">add</button>
<ul>
<li v-for="data,index in datalist">{{data}}
<button @click="handleDel(index)">del</button>
</li>
</ul>
<div v-if="datalist.length === 0">暂无代办</div>
</div>
</body>
<script>
var obj = {
data() {
return {
mytext: "",
datalist: ["1", "2", "3"],
}
},
methods: {
handleAdd(e) {
this.datalist.push(this.mytext)
console.log(e)
},
handleDel(index) {
this.datalist.splice(index, 1)
},
handleInput(e){
console.log(e.target.value)
}
}
}
var app = Vue.createApp(obj).mount("#app")
</script>
</html>
1-5 点击变心案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<style>
.active{
background-color: red;
}
</style>
<body>
<div id="app">
<input type="text" v-model="mytext" @input="handleInput">
<button @click="handleAdd">add</button>
<ul>
<li v-for="data,index in datalist" :class="current === index ? 'active':''" @click = handleClick(index)>{{data}}
<button @click="handleDel(index)">del</button>
</li>
</ul>
<div v-if="datalist.length === 0">暂无代办</div>
</div>
</body>
<script>
var obj = {
data() {
return {
mytext: "",
datalist: ["1", "2", "3"],
current: 0,
}
},
methods: {
handleAdd(e) {
this.datalist.push(this.mytext)
console.log(e)
},
handleDel(index) {
this.datalist.splice(index, 1)
},
handleInput(e){
console.log(e.target.value)
},
handleClick(index){
this.current = index
}
}
}
var app = Vue.createApp(obj).mount("#app")
</script>
</html>
1-6 v-html- 模板的陷阱
双大括号会将数据解释为纯文本,而不是 HTML。若想插入 HTML,你需要使用 v-html 指令:
<p>Using text interpolation: {{ rawHtml }}</p>
<p>Using v-html directive: <span v-html="rawHtml"></span></p>
一般都是后台返回一段 HTML 片段,需要前端展示(一般存在于富文本编辑器,进行文章发布等),对于自己熟悉的后台,可以使用 v-html,但是对于不信任网站,可能会造成信息泄露
2.class 与 style
2-1 class 的绑定
对象写法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<style>
.active{
background-color: aquamarine;
}
.text-danger{
color: red;
}
</style>
<body>
<div id="app">
<h3 :class="classObject">mainIndex</h3>
<button @click="changeClass">changeClass</button>
</div>
</body>
<script>
var obj = {
data() {
return {
classObject:{
active: true,
'text-danger': false
}
}
},
methods: {
changeClass(){
this.classObject.active = !this.classObject.active;
this.classObject['text-danger'] = !this.classObject['text-danger']
}
}
}
var app = Vue.createApp(obj).mount("#app")
</script>
</html>
数组写法
data() {
return {
activeClass: 'active',
errorClass: 'text-danger'
}
}
<div :class="[activeClass, errorClass]"></div>
2-2 style 的绑定-同上
对象写法
data() {
return {
styleObject: {
color: 'red',
fontSize: '13px'
}
}
}
<div :style="styleObject"></div>
数组写法
data() {
return {
arr:[{
width:"200px",
height:"200px",
backgroundSize:"cover" // 可以采用驼峰命名或者‘background-size': 'cover'
}],
}
}
this.arr.push({
backgroundImage:"url(https://pic.maizuo.com/usr/movie/862ab6736237acd11599e5eecbbc83d7.jpg?x-ossprocess=image/quality,Q_70)"
})
数组就 push、pop,对象就直接写对象名称,删除对象属性就是为对象赋值为 undefined
3.条件渲染
3-1 条件渲染
v-if 是“真实的”按条件渲染,因为它确保了在切换时,条件区块内的事件监听器和子组件都会被销毁与重建。
v-if 也是惰性的:如果在初次渲染时条件值为 false,则不会做任何事。条件区块只有当条件首次变为 true 时才被渲染。
相比之下, v-show 简单许多,元素无论初始条件如何,始终会被渲染,只有 CSS display 属性会被切换。
总的来说, v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要频繁切换,则使用 v-show 较好;如果在运行时绑定条件很少改变,则 v-if 会更合适。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<style>
.active{
background-color: aquamarine;
}
.text-danger{
color: red;
}
.text-size{
font-size: 100px;
}
</style>
<body>
<div id="app">
<ul>
<li v-for="value,index in datalist">
{{value.title}}
<div v-if="value.state === 0">未付款</div>
<div v-else-if="value.state === 1">未发货</div>
<div v-else-if="value.state === 2">已发货</div>
<div v-else>已完成</div>
</li>
<!-- 也可以配合template模板语法渲染页面,渲染后不会在页面上出现template标签 -->
<template v-if="true">
<div>1111</div>
<div>2222</div>
<div>3333</div>
</template>
</ul>
</div>
</body>
<script>
var obj = {
data() {
return {
datalist:[
{
state: 0,
title: "111"
},
{
state: 1,
title: "222"
},
{
state: 2,
title: "333"
}
]
}
},
methods: {
}
}
var app = Vue.createApp(obj).mount("#app")
</script>
</html>
4.列表渲染
4-1 v-for 列表渲染
列表渲染中分为对象渲染、数组渲染、数字渲染
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<body>
<div id="app">
<!-- 列表渲染 -->
<ul>
<li v-for="value,key in list" :key="key">{{value}}-{{key}}</li>
</ul>
<!-- 对象渲染 -->
<ul>
<li v-for="(value,key,index) in objList" :key="key">{{key}}--{{value}}</li>
</ul>
<!-- 数字渲染 -->
<ul>
<li v-for="value,key in num" :key="key">{{value}}--{{key}}</li>
</ul>
</div>
</body>
<script>
var app = Vue.createApp(
{
data() {
return {
list: ["111","2222","3333"],
objList:{
"1":"a",
"2":"b",
"3":"c",
},
num:10
}
}
}
).mount("#app")
</script>
</html>
- 支持小括号
- 支持解构
- in === of (js 迭代器)
v-for 与 v-if
<ul >
<template v-for="{title,state},index in datalist" >
<li v-if="state===1">{{title}}</li>
</template>
</ul>
4-2 key 设置 - 性能的保障
Vue 默认按照“就地更新”的策略来更新通过 v-for 渲染的元素列表。当数据项的顺序改变时,Vue 不会随之移动 DOM 元素的顺序,而是就地更新每个元素,确保它们在原本指定的索引位置上渲染。
为了给 Vue 一个提示,以便它可以跟踪每个节点的标识,从而重用和重新排序现有的元素,你需要为每个元素对应的块提供一个唯一的 key attribute 来高效的对比新、旧虚拟 dom,从而改变真实 dom:
<div v-for="item in items" :key="item.id">
<!-- 内容 -->
</div>
虚拟 dom
{
type: 'div',
props: {
id: 'container'
},
children: [
{
type: 'span',
props: {
class: 'text1'
},
children: 'hello '
},
{
type: 'span',
props: {
class: 'text2'
},
children: 'kerwin'
},
]
}
真实 dom
<div id="container">
<span class="text1">hello </span>
<span class="text2">kerwin</span>
</div>
4-3 数组变动侦测
在 Vue2 中通过索引更新数组或对象,不会被侦听到,页面上不会发生变化,但是在 Vue3 因为使用的是 proxy,所以会被侦听到,从而改变页面渲染
Vue 能够侦听响应式数组的变更方法,并在它们被调用时触发相关的更新。这些变更方法包括:
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
对于一些不可变 (immutable) 方法,例如 filter() ,concat() 、slice()、map() ,这些都不会更改原数组,而总是返回一个新数组。当遇到的是非变更方法时,我们需要将旧的数组替换为新的:
this.items = this.items.filter((item) => item.message.match(/Foo/))
this.datalist = this.datalist.map(item => "phoenix")
4-4 模糊搜索案例
方案 1
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<style>
.active {
background-color: aquamarine;
}
.text-danger {
color: red;
}
.text-size {
font-size: 100px;
}
</style>
<body>
<div id="app">
<input type="text" v-model="mytext">
<ul>
<template v-for="value in datalist" :key="value">
<li v-if="value.includes(mytext)">{{value}}</li> <!-- includes:判断字符串是否包含,包含true/false-->
</template>
</ul>
</div>
</body>
<script>
var obj = {
data() {
return {
mytext: "",
datalist: ["aaa", "abb", "aab", "bcc", "abc", "bcd", "add", "acd"]
}
},
methods: {
}
}
var app = Vue.createApp(obj).mount("#app")
</script>
</html>
方案 2
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<style>
.active {
background-color: aquamarine;
}
.text-danger {
color: red;
}
.text-size {
font-size: 100px;
}
</style>
<body>
<div id="app">
<input type="text" v-model="mytext" />
<ul>
<li v-for="data in filterList()" :key="data">{{data}}</li>
</ul>
</div>
</body>
<script>
var obj = {
data() {
return {
mytext: "",
datalist: ["aaa", "abb", "aab", "bcc", "abc", "bcd", "add", "acd"]
}
},
methods: {
filterList(evt) {
return this.datalist.filter(item => item.includes(this.mytext))
}
}
}
var app = Vue.createApp(obj).mount("#app")
</script>
</html>
Vue3 支持在{{}}和指令中中使用表达式(函数)
5.事件处理器
5-1 事件处理器 - 告别原生事件
事件分类
@submit
@click
@
内联事件处理器
<button @click="count++">Add 1</button>
<button @click="test('hello')">test hello</button>
方法事件处理器
<button @click="test">test</button>
事件处理器默认会接收一个evt(用于事件的接受)
test(evt){
console.log(evt)
}
如果需要传入标签的事件对象,需要在函数最后传入 $event
<button @click="handleClick(1,2,3,$event)">test hello</button>
handleClick(a,b,c,evt){
console.log("111",a,b,c)
console.log(evt)
console.log(evt.target)
或者结合方法事件处理器
<li @click="(evt)=>handleClick(1,2,3,evt)">2222</li>
同样也会获得该标签的传递参数和事件
当子事件发生,默认父亲事件也会同样发生,这就是冒泡,可以通过阻止默认行为
evt.stopPropagation()
5-2 事件修饰符 - 事件偷懒符
Vue 为 v-on 提供了事件修饰符。修饰符是用 . 表示的指令后缀,包含以下这些:
名称 | 作用 | |
---|---|---|
.stop | 阻止事件冒泡 | |
.prevent | 阻止默认行为 | |
.self | 只有当事件是从事件目标本身触发时才触发处理程序 | |
.capture | 以捕获模式添加事件侦听器 | |
.once | 只触发一次 | |
.passive | 指示事件处理程序不会调用 preventDefault() |
其中,.stop
和 .prevent
是最常用的事件修饰符,用于阻止事件冒泡和默认行为。通常在高级组件开发中,.capture
也会比较常用。.self
则适用于当事件目标本身与父级元素都绑定相同的事件时,只需要处理目标元素的情况。.once
和 .passive
则针对特定场景使用,例如只需要执行单次操作和优化事件处理性能等。
常用的键盘事件
- @keyup:键盘松开时触发,可在后面加上修饰符来指定触发的键,例如 @keyup.enter 表示按下回车键时触发。(这里用到了按键别名)
- @keydown:键盘按下时触发,也可以加上修饰符来指定触发的键。
- @keypress:键盘按键被按下并松开时触发,同样可以加上修饰符。
Vue 中提供的按键别名如下:
名称 | 说明 |
---|---|
.enter:回车键 | |
.tab:制表键 | |
.delete / .del:删除键 | |
.esc:Esc 键 | |
.space:空格键 | |
.up:上箭头键 | |
.down:下箭头键 | |
.left:左箭头键 | |
.right:右箭头键 | |
.ctrl:Ctrl 键 | |
.alt:Alt 键 | |
.shift:Shift 键 | |
.meta:Meta 键 | 对于 Windows 键盘就是 Windows 键,而对于苹果键盘就是 Command 键 |
vue3 还提供了键盘编号对应的键盘名,只需要
<input type="text" @keyup.a="handleTest">
handleTest(evt){
console.log(1111)
}
当输入键盘a时候,就会触发键盘事件,调用事件函数
模态框案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<style>
#overlay {
background: rgba(0, 0, 0, 0.6);
width: 100%;
margin: auto;
position: fixed;
top: 0;
Left: 0;
right: 0;
bottom: 0;
}
#center {
background: #ffff;
border-radius: 5px;
/* 边框圆角 */
padding-top: 15px;
padding-Left: 30px;
padding-bottom: 15px;
width: 290px;
height: 160px;
position: fixed;
margin: auto;
Left: 0;
right: 0;
top: 0;
bottom: 0;
}
</style>
<body>
<div id="app">
<button @click="isShow=!isShow">显示模态框</button>
<div id="overlay" v-show="isShow" @click="isShow=!isShow">
<!-- <div id="overlay" v-show="isShow" @click.self="isShow=!isShow"> -->
<div id="center" @click.stop>
username: <input/>
<button>login in</button>
<button @click="isShow=!isShow">关闭</button>
</div>
</div>
</div>
</body>
<script>
var obj = {
data() {
return {
isShow:false,
}
},
methods: {
}
}
var app = Vue.createApp(obj).mount("#app")
</script>
</html>
6.表单控件绑定 v-model
6-1 表单输入绑定-一根绳上的蚂蚱
普通文本
<input v-model="message" placeholder="edit me" />
复选框
<input type="checkbox" id="checkbox" v-model="checked" />
checked:true
复选框就是给定一个数组,为每个复选框选项赋值一个value,
当点击以后,value值会加入到这个v-model绑定的数组中
<div>
你喜欢的运动:{{favList}}
<div>
<input type="checkbox" v-model="favList" value="篮球">篮球
<input type="checkbox" v-model="favList" value="足球">足球
<input type="checkbox" v-model="favList" value="乒乓球">乒乓球
<button>提交</button>
</div>
</div>
favList: []
v-model 绑定的是 Boolean 值,即 true 或者 false
单选框
<div>Picked: {{ picked }}</div>
<input type="radio" id="one" value="One" v-model="picked" />
<div>
你最喜欢的运动:{{picked}}
<div>
<input type="radio" v-model="picked" value="篮球">篮球
<input type="radio" v-model="picked" value="足球">足球
<input type="radio" v-model="picked" value="乒乓球">乒乓球
<button>提交</button>
</div>
</div>
picked:"足球"
选择器
<div>Selected: {{ selected }}</div>
<select v-model="selected">
<option disabled value="">Please select one</option>
<option value="a">A</option>
<option value="b">B</option>
<option value="c">C</option>
</select>
6-2 购物车案例 🌟
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<style>
li{
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
border: 1px solid lightgray;
}
li img{
width: 100px;
}
</style>
<body>
<div id="app">
<ul>
<li>
<div>
<input type="checkbox" v-model="isAllChecked" @change="handleAllChange">
<span>全选/全不选</span>
</div>
</li>
<template v-if="datalist.length !== 0">
<li v-for="(value,index) in datalist" :key="value.id">
<div>
<input type="checkbox" v-model="checkList" :value="value" @change="handleItemChange">
</div>
<div>
<img :src="value.poster" :alt="value.title">
</div>
<div>
<div>{{value.title}}</div>
<div style="color: red;">{{value.price}}</div>
</div>
<div>
<button @click="value.number--" :disabled="value.number === 1">-</button>
{{value.number}}
<button @click="value.number++" :disabled="value.number === value.limit">+</button>
</div>
<div>
<button @click="handleDel(index,value.id)">删除</button>
</div>
</li>
<li>
<div>
总金额:{{sum(checkList)}}
</div>
</li>
</template>
<template v-else>
<li>购物车为空</li>
</template>
</ul>
<!-- {{checkList}}
-----------------------
{{datalist}} -->
</div>
</body>
<script>
var obj = {
data() {
return {
isAllChecked:false,
checkList:[], // 勾选的商品列表
datalist: [
{
id: 1,
title: "商品1",
price: 10,
number: 1,
poster: "https://fc.sinaimg.cn/large/008w3CKjgy1hfk6lcrud0j307i0alwfq.jpg",
limit: 5,
},
{
id: 2,
title: "商品2",
price: 20,
number: 2,
poster: "https://fc.sinaimg.cn/large/008w3CKjgy1hfoyxf73s5j307i0arwfo.jpg",
limit: 6,
},
{
id: 3,
title: "商品3",
price: 30,
number: 3,
poster: "https://fc.sinaimg.cn/large/008w3CKjgy1hfnacjnf46j307i0al3zy.jpg",
limit: 8,
}
]
}
},
watch:{
},
methods: {
handleAllChange(){
this.checkList = this.isAllChecked? this.datalist:[]
},
handleItemChange(){
if(this.datalist.length === 0){
this.isAllChecked =false
return
}
this.isAllChecked = this.checkList.length === this.datalist.length
},
sum(checkList){
var sum = 0;
checkList.forEach(element => {
sum += element.price * element.number
});
return sum;
},
handleDel(index,id){
this.datalist.splice(index,1)
// 同步更新总价格
this.checkList = this.checkList.filter(element => element.id !== id)
// 同步全选
this.handleItemChange()
}
}
}
var app = Vue.createApp(obj).mount("#app")
</script>
</html>
6-3 表单修饰符
.lazy(当页面失去焦点并且内容发生改变时候才会进行触发,这个是 @change 语法糖)
<!-- 在 "change" 事件后同步更新而不是 "input" -->
<input v-model.lazy="msg" />
.number
用户输入自动转换为数字,你可以在 v-model 后添加 .number 修饰符来管理输入:
number 修饰符会在输入框有 type=“number” 时自动启用。
<input v-model.number="age" />
.trim
默认自动去除用户输入内容中两端的空格,你可以在 v-model 后添加 .trim 修饰符:
<input v-model.trim="msg" />
7.计算属性
7-1 计算属性 - 会缓存
模板中的表达式虽然方便,但也只能用来做简单的操作。如果在模板中写太多逻辑,会让模板变得臃肿,难以维护。因此我们推荐使用计算属性来描述依赖响应式状态的复杂逻辑。
{
data() {
return {
author: {
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
}
}
},
// 结果会被缓存
computed: {
// 一个计算属性的 getter
publishedBooksMessage() {
// `this` 指向当前组件实例
return this.author.books.length > 0 ? 'Yes' : 'No'
}
}
{ { publishedBooksMessage } }
}
若我们将同样的函数定义为一个方法而不是计算属性,两种方式在结果上确实是完全相同的,然而,不同之处在于计算属性值会基于其响应式依赖被缓存。一个计算属性仅会在其响应式依赖更新时才重新计算。
7-2 之前案例的小改造
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<style>
li{
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
border: 1px solid lightgray;
}
li img{
width: 100px;
}
</style>
<body>
<div id="app">
<ul>
<li>
<div>
<input type="checkbox" v-model="isAllChecked">
<span>全选/全不选</span>
</div>
</li>
<template v-if="datalist.length !== 0">
<li v-for="(value,index) in datalist" :key="value.id">
<div>
<input type="checkbox" v-model="checkList" :value="value">
</div>
<div>
<img :src="value.poster" :alt="value.title">
</div>
<div>
<div>{{value.title}}</div>
<div style="color: red;">{{value.price}}</div>
</div>
<div>
<button @click="value.number--" :disabled="value.number === 1">-</button>
{{value.number}}
<button @click="value.number++" :disabled="value.number === value.limit">+</button>
</div>
<div>
<button @click="handleDel(index,value.id)">删除</button>
</div>
</li>
<li>
<div>
总金额:{{sum}}
</div>
</li>
</template>
<template v-else>
<li>购物车为空</li>
</template>
</ul>
</div>
</body>
<script>
var obj = {
data() {
return {
// isAllChecked:false,
checkList:[], // 勾选的商品列表
datalist: [
{
id: 1,
title: "商品1",
price: 10,
number: 1,
poster: "https://fc.sinaimg.cn/large/008w3CKjgy1hfk6lcrud0j307i0alwfq.jpg",
limit: 5,
},
{
id: 2,
title: "商品2",
price: 20,
number: 2,
poster: "https://fc.sinaimg.cn/large/008w3CKjgy1hfoyxf73s5j307i0arwfo.jpg",
limit: 6,
},
{
id: 3,
title: "商品3",
price: 30,
number: 3,
poster: "https://fc.sinaimg.cn/large/008w3CKjgy1hfnacjnf46j307i0al3zy.jpg",
limit: 8,
}
]
}
},
watch:{
},
computed:{
sum(){
var sum = 0;
this.checkList.forEach(element => sum += element.price * element.number
);
return sum;
},
isAllChecked:{
get(){
return this.checkList.length === this.datalist.length
},
set(checked){
this.checkList = checked?this.datalist:[]
}
}
},
methods: {
handleDel(index,id){
this.datalist.splice(index,1)
// 同步更新总价格
this.checkList = this.checkList.filter(element => element.id !== id)
}
}
}
var app = Vue.createApp(obj).mount("#app")
</script>
</html>
注意:
-
Getter 不应有副作用
计算属性的 getter 应只做计算而没有任何其他的副作用,这一点非常重要,请务必牢记。举例来说,不要在 getter 中做异步请求或者更改 DOM!
-
避免直接修改计算属性值
从计算属性返回的值是派生状态。可以把它看作是一个“临时快照”,每当源状态发生变化时,就会创建一个新的快照。更改快照是没有意义的,因此计算属性的返回值应该被视为只读的,并且永远不应该被更改——应该更新它所依赖的源状态以触发新的计算。
7-3 侦听器 watch 的对比
watch 选项期望接受一个对象,其中键是需要侦听的响应式组件实例属性 (例如,通过 data 或 computed 声明的属性)——值是相应的回调函数。该回调函数接受被侦听源的新值和旧值。
监听属性,需要具有深度绑定,当对象内部某一个发生变化时候,就会触发监听属性
watch: {
// 侦听根级属性
a(val, oldVal) {
console.log(`new: ${val}, old: ${oldVal}`)
},
// 字符串方法名称
b: 'someMethod',
// 该回调将会在被侦听的对象的属性改变时调动,无论其被嵌套多深
c: {
handler(val, oldVal) {
console.log('c changed')
},
deep: true
},
// 侦听单个嵌套属性:
'c.d': function (val, oldVal) {
// do something
},
// 该回调将会在侦听开始之后立即调用
e: {
handler(val, oldVal) {
console.log('e changed')
},
immediate: true
}
}
8.数据请求
8-1 Fetch
XMLHttpRequest 是一个设计粗糙的 API,配置和调用方式非常混乱, 而且基于事件的异步模型写起来不友好。
兼容性不好 polyfill: GitHub - camsong/fetch-ie8: A window.fetch JavaScript polyfill supporting IE8
fetch("http://localhost:3000/users")
.then(res=>res.json())
.then(res=>{
console.log(res)
})
fetch("http://localhost:3000/users",{
method:"POST",
headers:{
"content-type":"application/json"
},
body:JSON.stringify({
username:"kerwin",
password:"123"
})
})
.then(res=>res.json())
.then(res=>{
console.log(res)
})
fetch("http://localhost:3000/users/5",{
method:"PUT",
headers:{
"content-type":"application/json"
},
body:JSON.stringify({
username:"kerwin",
password:"456"
})
})
.then(res=>res.json())
.then(res=>{
console.log(res)
})
fetch("http://localhost:3000/users/5",{
method:"DELETE"
})
.then(res=>res.json())
.then(res=>{
console.log(res)
})
8-2 axios🌟🌟
Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
-
get 请求
axios.get("http://localhost:3000/users",{ params:{ name:"kerwin" } }).then(res=>{ console.log(res.data) })
-
post 请求
axios.post("http://localhost:3000/users",{ name:"kerwin", age:100 }).then(res=>{ console.log(res.data) })
-
put 请求
axios.put("http://localhost:3000/users/12",{ name:"kerwin111", age:200 }).then(res=>{ console.log(res.data) })
-
delete 请求
axios.delete("http://localhost:3000/users/11").then(res=>{ console.log(res.data) })
-
axios(config)配置
axios({ method: 'post', url: 'http://localhost:3000/users', data: { name: 'kerwin', age: 100 } }) .then(res => { console.log(res.data) }).catch(err=>{ console.log(err) })
Axios 会将获取的 json 数据包装起来,我们需要通过 result.data 来获取后端返回的请求值
9.过滤器
9-1 vue3 过滤器不支持了 - 怎么办?
在 2.x 中,开发者可以使用过滤器来处理通用文本格式。
<p>{{ accountBalance | currencyUSD }}</p>
filters: {
currencyUSD(value) {
return '$' + value
}
}
虽然这看起来很方便,但它需要一个自定义语法,打破了大括号内的表达式“只是 JavaScript”的假设,这不仅有学习成本,而且有实现成本。
在 3.x 中,过滤器已移除,且不再支持。取而代之的是,我们建议用方法调用或计算属性来替换它们。