coderwhy前端系统课 - 阶段六VUE (整理版)(更新中2023.7.16)
1. 前言
本文以coderwhy前端系统课 - 阶段六VUE为主。
一刷版本的笔记有些乱,目前还在二刷整理,同时参考了一部分其他的资料,并加上个人使用总结
建议使用资源绑定里的html文件进行阅读
资源说明:
- 资源内有自定义的样式更便于阅读,这里的样式不做额外编写
- 资源内点击侧边栏开关时文章阅读位置会偏移,点击目录定位即可
- 资源内的图片无法像csdn的可以点击放大,但能看清的
- 该文章有些定位点链接,资源内可点击,文章这点了没效果,不做额外修改
- 该文章出现一些看不懂的符号拼接啥的,略过
(一些自定义语法,太多了,这回应该删完了,后面更新估计懒得删)
附上一张效果图:
2. vue2 vs vue3
放在开头为了方便对比,后面的内容以vue3为主
2.1. vue文件结构
vue2
<template><div><h1>{{ title }}</h1><button @click="increment">{{ count }}</button></div>
</template><script>
export default {data() {return {title: 'Hello, Vue2!',count: 0}},methods: {increment() {this.count++}}
}
</script>
vue3
<template><div><h1>{{ title }}</h1><button @click="increment">{{ count }}</button></div>
</template><script>
import { ref } from 'vue'export default {data() {return {// 也可以在这定义data数据}},setup() {const title = 'Hello, Vue3!'const count = ref(0)function increment() {count.value++}return {title,count,increment}}
}
</script>
2.2. 路由
– 来自chatgpt,两者都使用Vue Router来实现路由功能
vue2
- 引入Vue Router插件及其依赖
import Vue from 'vue'
import Router from 'vue-router'Vue.use(Router)
- 配置路由映射关系
const router = new Router({routes: [{path: '/',name: 'home',component: Home},{path: '/about',name: 'about',component: About}]
})
其中,path
表示URL路径,name
为路由名称,component
指定该路由对应的组件。
- 将路由挂载到Vue实例上
new Vue({router,render: h => h(App),
}).$mount('#app')
在 Vue3 中,render 函数的写法有所不同,使用了新的 createApp API。
vue3
提供了一个基于函数式API的新特性:createRouter()
- 引入Vue Router插件及其依赖
import { createRouter, createWebHistory } from 'vue-router'
- 创建Router实例并配置路由映射关系
const routes = [{path: '/',name: 'home',component: Home},{path: '/about',name: 'about',component: About}
]const router = createRouter({history: createWebHistory(),routes
})
与Vue2不同的是,在Vue3中,需要将history
传递给 createWebHistory
来指定路由模式。
routes
数组中的每个对象包含path
、name
和component
属性。
- 将路由挂载到Vue实例上
createApp(App).use(router).mount('#app')
这里使用createApp()方法来创建Vue实例,并通过use()方法将router实例添加到Vue实例中。
备注
vue2 中的 render: h => h(App)
2.3. JS函数
vue2
3. 基础语法
3.1. 插值语法 {{}}
html
<!-- 1.基本使用 -->
<h2>{{ message }}</h2>
<h2>当前计数: {{ counter }} </h2><!-- 2.表达式 -->
<h2>计数双倍: {{ counter * 2 }}</h2>
<h2>展示的信息: {{ info.split(" ") }}</h2><!-- 3.三元运算符 -->
<h2>{{ age >= 18? "成年人": "未成年人" }}</h2><!-- 4.调用methods中函数 -->
<h2>{{ formatDate(time) }}</h2>
javascript
//datacounter: 100,info: "my name is why",age: 22,time: 123//methodformatDate: function(date) {return "2022-10-10-" + date}
3.2. 内置指令
v-for(item in list)
遍历,内容:{{item}}
v-text
文本 ,等于直接用{{}}
v-html
HTML插入v-pre
无需编译,如{{m}}
,显示在浏览器还是{{m}}
v-once
只传入一次v-cloak
遮罩斗篷,先隐藏没有传入值的{{m}}
,有传入值后显示,css加上[v-cloak]{display:none}
(1) v-memo 某值改变才更新
某值改变时才更新内容,可查看《vue3.2新增指令v-memo的使用 - 南风晚来晚相识》
html
<div id="app"><div v-memo="[name]"><h2>姓名: {{ name }}</h2><h2>年龄: {{ age }}</h2><h2>身高: {{ height }}</h2></div><button @click="updateInfo">改变信息</button>
</div>
javascript
data: function() {return {name: "why",age: 18,height: 1.88}
},methods: {updateInfo: function() {this.name = "kobe"this.age = 20}
}
(2) v-bind 动态绑定 :
原语句 | 语法糖 |
---|---|
v-bind:src="" | :src |
v-bind:href="" | :href |
v-bind:class="" | :class |
绑定方法参考
html
<!-- 1.绑定img的src属性 -->
<img v-bind:src="showImgUrl" alt="">
<!-- 语法糖: v-bind -> : -->
<img :src="showImgUrl" alt=""><!-- 2.绑定a的href属性 -->
<a :href="href">百度一下</a>
javascript
data: function() {return {imgUrl1: "http://p1.music.126.net/agGc1qkogHtJQzjjyS-kAA==/109951167643767467.jpg",imgUrl2: "http://p1.music.126.net/_Q2zGH5wNR9xmY1aY7VmUw==/109951167643791745.jpg",showImgUrl: "http://p1.music.126.net/_Q2zGH5wNR9xmY1aY7VmUw==/109951167643791745.jpg",href: "http://www.baidu.com"}
},methods: {switchImage: function() {// 图片地址 = 现在的图片地址 === 是图片1吗 ? 是:展示图片2 不是:显示图片1this.showImgUrl = this.showImgUrl === this.imgUrl1 ? this.imgUrl2: this.imgUrl1}
}
❥ 绑定class
前提:
// css.active {color: red;}//dataisActive: false,// methodsbtnClick: function() {this.isActive = !this.isActive},
- 三元:
<!-- =isActive吗? 是:添加active样式 否:啥也不加 --><button :class="isActive ? 'active': ''" @click="btnClick">我是按钮</button>
- 单值 ⭐
<!-- 不会影响原有class active样式加不加呢? =false 不加 =true 加 -->
<button class="haha" :class="{ active: isActive }" @click="btnClick">我是按钮</button>
- 对象语法的多个键值对
<!-- 直接添加 why -->
<button :class="{ active: isActive, why: true, kobe: false }" @click="btnClick">我是按钮</button>
- 把 :class 的内容抽取出去
<!-- 抽取到方法里 -->
<button class="abc cba" :class="getDynamicClasses()" @click="btnClick">我是按钮</button>
//methodgetDynamicClasses: function() {return { active: this.isActive, why: true, kobe: false }}
- 数组
<!-- 3.动态class可以写数组语法(了解) --><h2 :class="['abc', 'cba']">Hello Array</h2><h2 :class="['abc', className]">Hello Array</h2><h2 :class="['abc', className, isActive? 'active': '']">Hello Array</h2><h2 :class="['abc', className, { active: isActive }]">Hello Array</h2>
❥ 绑定style
- 绑定属性为对象,分隔符为
,
<!-- 普通的html写法 -->
<h2 style="color: red; font-size: 30px;">哈哈哈哈</h2><!-- 对象类型 -->
<!-- 调用data的fontColor 调用data的fontSize与px拼接 88px要连在一起,所以加'' -->
<h2 v-bind:style="{ color: fontColor, fontSize: fontSize + 'px', height: '88px' }">哈哈哈哈</h2>
//data
fontColor: "blue",
- 绑定属性为数组 (很少用)
<h2 :style="objStyle">呵呵呵呵</h2>
<h2 :style="[objStyle, { backgroundColor: 'purple' }]">嘿嘿嘿嘿</h2>
//data
objStyle: {fontSize: '50px',color: "green"
}
❥ 绑定属性名
<h2 :[name]="'aaaa'">Hello World</h2>
//data
name: "class"
❥ 绑定对象
<h2 v-bind="infos">Hello Bind</h2>
// data
infos: { name: "why", age: 18, height: 1.88, address: "广州市" },
(3) v-on 事件绑定(事件监听) @
原语句 | 语法糖 |
---|---|
v-on:click=" " | @click=" " |
绑定方法参考
<!-- 1.基本的写法 -->
<div class="box" v-on:click="divClick"></div><!-- 2.语法糖写法(重点掌握) -->
<div class="box" @click="divClick"></div><!-- 4.绑定其他方法(掌握) -->
<div class="box" @mousemove="divMousemove"></div><!-- 5.元素绑定多个事件(掌握) -->
<div class="box" @click="divClick" @mousemove="divMousemove"></div>
methods: {divClick() {console.log("divClick")},divMousemove() {console.log("divMousemove")}
}
❥ 参数传递
<!-- 1.默认传递event对象 -->
<button @click="btn1Click">按钮1</button><!-- 2.只有自己的参数 -->
<button @click="btn2Click('pyy', age)">按钮2</button><!-- 3.自己的参数和event对象在模板中想要明确的获取event对象: $event -->
<button @click="btn3Click('pyy', age, $event)">按钮3</button>
//data
age: 18
//方法
methods: {// 1.默认参数: event对象// 总结: 如果在绑定事件的时候, 没有传递任何的参数// 那么event对象会被默认传递进来btn1Click(event) {console.log("btn1Click:", event)},// 2.明确参数:btn2Click(name, age) {console.log("btn2Click:", name, age) // pyy 18},// 3.明确参数+event对象btn3Click(name, age, event) {console.log("btn3Click:", name, age, event)}
}
❥ 添加修饰符
<button @click.stop="btnClick">按钮</button>
使用过
回车自动触发:@keyup.enter.native
阻止默认事件:@submit.native.prevent
解决问题:
其他修饰符
.stop
- 调用 event.stopPropagation(),这是阻止事件的冒泡方法,不让事件向document上蔓延,但是默认事件任然会执行,当你掉用这个方法的时候,如果点击一个连接,这个连接仍然会被打开,解释来源.prevent
- 调用event.preventDefault(),这是阻止默认事件的方法,调用此方法是,连接不会被打开,但是会发生冒泡,冒泡会传递到上一层的父元素;.capture
- 添加事件侦听器时使用 capture 模式,事件冒泡.self
- 只当事件是从侦听器绑定的元素本身触发时才触发回调.{keyAlias}
- 仅当事件是从特定键触发时才触发回调.once
- 只触发一次回调.left
- 只当点击鼠标左键时触发.right
- 只当点击鼠标右键时触发.middle
- 只当点击鼠标中键时触发passive -{ passive: true}模式添加侦听器
(4) v-if 是否(条件渲染)
v-if="条件"
条件成立,执行该段
v-else
条件不成立,执行该段
❥ 数组
<ul v-if="names.length > 0"><li v-for="item in names">{{item}}</li></ul><h2 v-else>当前names没有数据, 请求获取数据后展示</h2>
v-for(元素 in 列表)
遍历
//datanames:[] // 无数据names:[ab, ad, ae] // 有数据
❥ 对象
<!-- v-if="条件" 无值 > false 有值 > true -->
<div class="info" v-if="Object.keys(info).length"> <h2>个人信息</h2><ul><li>姓名: {{info.name}}</li><li>年龄: {{info.age}}</li></ul>
</div><!-- v-else -->
<div v-else><h2>没有输入个人信息</h2><p>请输入个人信息后, 再进行展示~</p>
</div>
info: {name:"aa", age:11}
❥ if else if
<div id="app"><h1 v-if="score > 90">优秀</h1><h2 v-else-if="score > 80">良好</h2><h3 v-else-if="score >= 60">及格</h3><h4 v-else>不及格</h4></div>
(5) v-show 显示/隐藏 (条件渲染)
<div><button @click="toggle">切换</button>
</div><div v-show="isShowCode"><img src="https://game.gtimg.cn/images/yxzj/web201706/images/comm/floatwindow/wzry_qrcode.jpg" alt="">
</div>
// data
isShowCode: true
//method
toggle() {this.isShowCode = !this.isShowCode
}
注意:
v-if=true/false
也可以控制元素的隐藏显示,区别在于:
- v-show 不支持 template (见template详解)
- v-show 不可以和 v-else 一起使用
- v-show 不管是 true 还是 false ,内容的 dom 都是存在的,只是通过 css 的 display 属性来切换
- v-if=flase 时,对应的内容不会渲染在 dom 中(是不存在的!)
- 频繁的切换隐藏显示用v-show
(6) v-for 遍历 (列表渲染)
v-for="(item,index) in 数组"
也支持 v-for="(item,index) of 数组"
,但平时一般直接用in
《v-for 循环中 in 与 of 区别,以及 ES5 for in 与 ES6 for of 区别 - 雁 南飞》:
❥ 数组和对象
/–遍历对象 v-for="(value, key, index) in info"
/–遍历字符串 v-for="item in message"
/–遍历数字 v-for="item in 100"
写在 li 标签,会产生多个 li,默认 item 为 value 值
html
<!-- 遍历 value -->
<li v-for="movie in movies">{{ movie }}</li><!-- 遍历 value, key, index -->
<li v-for="(value, key, index) in info">{{value}}-{{key}}-{{index}}
</li><!-- 有索引 0, 1, 2, 3... -->
<li v-for="(movie, index) in movies">{{index + 1}} - {{ movie }}
</li><!-- 3.遍历数组复杂数据 -->
<h2>商品列表</h2>
<div class="item" v-for="item in products"><h3 class="title">商品: {{item.name}}</h3><span>价格: {{item.price}}</span><p>秒杀: {{item.desc}}</p>
</div>
</div>
javascript
// data// 1.moviesmovies: ["星际穿越", "少年派", "大话西游", "哆啦A梦"],// 2.数组: 存放的是对象products: [{ id: 110, name: "Macbook", price: 9.9, desc: "9.9秒杀, 快来抢购!" },{ id: 111, name: "iPhone", price: 8.8, desc: "9.9秒杀, 快来抢购!" },{ id: 112, name: "小米电脑", price: 9.9, desc: "9.9秒杀, 快来抢购!" },]
❥ 数组更新检测
changeArray() {// 1.直接将数组修改为一个新的数组// this.names = ["why", "kobe"]// 2.通过一些数组的方法, 修改数组中的元素this.names.push("why") // 加this.names.pop() // 删除后面一个this.names.splice(2, 1, "why")this.names.sort()this.names.reverse()// 3.不修改原数组的方法是不能侦听(watch)// 因为this.names.map() 会产生一个新数组,而不是修改原数组,所以需要把值存入一个变量const newNames = this.names.map(item => item + "why") //每个value后面拼接why this.names = newNames}
⭐ splice!可以添加、删除、替换
names.splice(3,1,"pyy","pyyyy") // 在位置3 删除1个 添加pyy和pyyyy
❥ key的作用
官方解释key::
有key和没有key时执行的方法不一样
没key时,也会执行diff算法,对比遇到不一样的内容时,后面的全部替换:
有key时,也使用diff算法,但会尽量的复用原有节点:
<!-- key要求是唯一: id -->
<li v-for="item in letters" :key="item">{{item}}</li>
总结
key的面试问答
(7) template (列表渲染)
当div没有意义,又使用了v-xxx
,那就把 div 换成 template
可以放id <template id=" ">
以下来自解释来自于《vue v-if与v-show的区别,template的使用 - 键盘上的那抹灰》
- 当两个以上的div被同一元素控制时,用
template
替换该元素 - 控制台template改良版将不会出现一个新的div,也不会出现template,减少空间
- template是没有实际东西的dom,所以v-show与template联合使用将失效
3.3. v-model
原理和使用方法
原理:
写法:
✗ 手动绑定,先显示message值在input里,input输入其他值时,message同步改变
<input type="text" :value="message" @input="inputChange">
<h2>{{message}}</h2>
//datamessage: "Hello Model",// methodsinputChange(event) {this.message = event.target.value},
⭐ v-model,不用添加方法
<input type="text" v-model="message">
<h2>{{message}}</h2>
案例:登录
<label for="account">账号:<input id="account" type="text" v-model="account"></label><label for="password">密码:<input id="password" type="password" v-model="password"></label><button @click="loginClick">登录</button>
// data
account: "",
password: ""
// methods
loginClick() { // 获取值,发送出去const account = this.accountconst password = this.password// url发送网络请求console.log(account, password)
}
(1) 绑定到 textarea
(2) 绑定到 checkbox
lable 里的 for 为 html 内的知识点,点击文字也可以关联到 input
(3) 绑定到 redio
(4) 绑定到 select
(5) 值绑定
选项也来自服务器时。
(6) 修饰符
v-model-lazy
会将绑定的事件切换为 change 事件,只有在提交时(比如回车)才会触发
v-model-number
转为数字类型
v-model-trim
动过滤用户输入的空白字符
使用多个
v-model.lazy.trim=""
可以组合使用
4. Options API(vue2)
Options API 包含
export default {data() { return { } }, // 数据 props: [ ] // 接收父组件传递过来的属性methods:{ }, // 方法watch: { }, // 监听(数据变化)computed: { }, // 复杂数据处理minxins: { }, // 混入(合并),vue2用的多components: { }, // 局部组件created() { }, // 监听(创建后)
+// lifecycle hooks 生命周期钩子函数,如// created、mounted、updated、destroyed 等// 以下不确定 provide: [ ] // 提供数据给injectinject: [ ] // 使用props这些数据}
4.1. 复杂数据处理 ⭐computed
(1) 方案对比
- 插值语法 ✗
<!-- 插值语法表达式直接进行拼接 --><!-- 1.拼接名字 --><h2>{{ firstName + " " + lastName }}</h2><!-- 2.显示分数等级 --><h2>{{ score >= 60 ? '及格': '不及格' }}</h2><!-- 3.反转单词显示文本 --><h2>{{ message.split(" ").reverse().join(" ") }}</h2>
/–split 将字符串转化为数组
/–reverse 反转
/–join 用xx拼接
- method 方法 ✗
函数调用
<!-- 1.拼接名字 方便多次调用--><h2>{{ getFullname() }}</h2><h2>{{ getFullname() }}</h2><h2>{{ getFullname() }}</h2><!-- 2.显示分数等级 --><h2>{{ getScoreLevel() }}</h2><!-- 3.反转单词显示文本 --><h2>{{ reverseMessage() }}</h2>
// methodsgetFullname() {return this.firstName + " " + this.lastName},getScoreLevel() {return this.score >= 60 ? "及格": "不及格"},reverseMessage() {return this.message.split(" ").reverse().join(" ")}
弊端:所有的data使用过程都变成了方法的调用
- ⭐ computed 计算属性 ⭐
官方:任何包含响应式数据的复杂逻辑,都应该使用计算属性(案例里都算相应式数据的复杂逻辑)
数据更新时会自动处理
computed 位置
const app = Vue.createApp({data() { return { } },methods:{ },computed: { }}).mount("#app")
处理案例:
<!-- 1.拼接名字 --><h2>{{ fullname }}</h2><h2>{{ fullname }}</h2><h2>{{ fullname }}</h2><!-- 2.显示分数等级 --><h2>{{ scoreLevel }}</h2><!-- 3.反转单词显示文本 --><h2>{{ reverseMessage }}</h2>
// 1.创建appconst app = Vue.createApp({// data: option apidata() {return {// 1.姓名firstName: "kobe",lastName: "bryant",// 2.分数: 及格/不及格score: 80,// 3.一串文本: 对文本中的单词进行反转显示message: "my name is why"}},computed: {// 1.计算属性默认对应的是一个函数fullname() {return this.firstName + " " + this.lastName},scoreLevel() {return this.score >= 60 ? "及格": "不及格"},reverseMessage() {return this.message.split(" ").reverse().join(" ")}}})// 2.挂载appapp.mount("#app")
(2) method 与 computed 区别
-
表现形式:computed 稍微简洁一些
-
computed 有缓存
解释:第一次变化时,数据未发生变化,computed方法仅调用了一次,缓存下来直接用,而method方法重复调用(执行)了三次
(3) 计算属性的 set get
4.2. 监听器 watch
位置
const app = Vue.createApp({data() { return { } },methods:{ },computed: { },watch:{ }}).mount("#app")
(1) 监听新旧值
dataName(newValue, oldValue) {}
const app = Vue.createApp({// data: option apidata() {return {message: "Hello Vue",info: { name: "why", age: 18 }}},methods: {changeMessage() {this.message = "你好啊, 李银河!"this.info = { name: "kobe" }}},watch: {// 1.默认有两个参数: newValue/oldValuemessage(newValue, oldValue) {console.log("message数据发生了变化:", newValue, oldValue)},info(newValue, oldValue) {// 2.如果是对象类型, 那么拿到的是代理对象console.log("info数据发生了变化:", newValue, oldValue) // 两个(proxy)对象 // newValue proxy对象 {name:"kobe"}// oldValue proxy对象 { name: "why", age: 18 }console.log(newValue.name, oldValue.name) // 两个值// 3.获取原生对象 (不想获取proxy对象,想要获取的原生对象方法)console.log(...newValue) // 原生的方法console.log(Vue.toRaw(newValue)) //vue专门提供的方法,{name:kobe}}}}).mount("#app")
如果原来是对象类,那么监听时获取到的也是proxy对象
对象类型
proxy对象 👉 Vue.toRaw(newValue)
(2) 深度监听
methods方法修改内容时,虽然内容会有变化,但是默认的watch不会进行深度监听!,所以在watch里默认没有监听到
修改info.name
,监听变化
- ⭐
handler(){}
相当于info(newValue,oldValue){}
的语法糖 - ⭐
deep: true
启动深度监听 - 但!点击改变
info.name:kobe
后改深度对象返回的newValue,oldValue是相同的
因为改变的不是info对象是info里的属性, (所以也没能监听到啊,deep:true到底有什么用) - ⭐
immediate
,第一次渲染时执行一次 - 这里的
info.name function
是vue2的知识点,能监听到name的变化
(3) 声明周期的监听
监听message
const app = Vue.createApp({// data: option apidata() {return {message: "Hello Vue"}},methods: {changeMessage() {this.message = "你好啊, 李银河!"}},// 生命周期回调函数: 当前的组件被创建时自动执行// 一般在该函数中, 会进行网络请求created() {// ajax/fetch/axiosconsole.log("created")this.$watch("message", (newValue, oldValue) => {console.log("message数据变化:", newValue, oldValue)}, { deep: true })}})
4.3. Options API的弊端
vue2编写组件的方式是Options API
- Options API一大特点是在对应的属性中编写对应的功能模块
- 如data定义数据、methods定义方法、computed定义计算属性、watch监听属性变化,也包括生命周期钩子
但是这种代码有很大的弊端
- 当实现某一个功能时,这个功能对应的代码逻辑会被拆分到各个属性中
- 当组件变得更大、更复杂时,逻辑关注点的列表就会增长,那么同一个功能的逻辑就会被拆分的很分散
- 对于一开始没有编写这些组件的人(包括阅读组件的其他人)来说,这个组件的代码是难以阅读和理解的
当组件非常大时,这种碎片化的代码使用、理解和维护这个复杂的组件变得异常困难,并且隐藏了潜在的逻辑问题。并且处理单个逻辑关注点时,需要不断地跳转到相应的代码中
5. Composition API (vue3)
5.1. 含义和区别
Composition API是什么,跟options API有什么区别 —— chatgpt
Composition API是Vue 3中提供的一种新的API风格,用于组织和重用组件逻辑。与Options API不同,Composition API将相关的代码组合在一起,以便更好地组织和重用代码。它基于函数而不是对象的形式,使得代码更容易拆分成可组合的逻辑块。此外,使用Composition API可以更好地封装和隐藏信息,并在组件之间共享逻辑,从而提高代码的可维护性和可重用性。总之,Composition API是Vue 3中一个非常实用且令人期待的改进,可以帮助我们更轻松地构建复杂的Web应用程序。
5.2. 包含内容
Composition API包含什么 —— chatgpt
-
reactive
:用于将一个普通对象转换为响应式对象,可以监听该对象属性的变化。 -
ref
:用于将基本类型数据转换为响应式对象,可以监听该值的变化。 -
computed
:用于创建计算属性,依赖于其他响应式对象和计算属性。 -
watchEffect
:用于监听响应式对象的变化,并在变化时执行回调函数。 -
生命周期钩子:包括
onMounted
(挂载到DOM后)、onUpdated
(更新后)、onUnmounted
(卸载后)等,用于在组件生命周期的不同阶段执行相应的操作。 -
provide
和inject
:用于跨层级传递数据。 -
封装复用逻辑:通过使用函数封装可复用的逻辑,实现组件之间逻辑共享。
5.3. setup函数
(1) 参数
props 和 context
props 从父组件传递过来的属性会被放在props对象中,setup中需要使用时直接通过props参数获取:
- 定义props的类型和之前的规则是一样的,再props选项中定义
- 再template中依然是可以正常使用props中的属性
- 再setup函数中想要使用props,不可以通过this去获取
- 因为props有直接作为参数传递到setup函数中,可以直接通过参数来使用即可
context 也称之为SetupContext,包含三个属性:
attrs
:所有的非props的attributeslots
:父组件传递过来的插槽emit
:组件内部需要发出事件时会用到emit(因为我们不能访问this,所以不可以通过this.$emit发出事件)
案例
计数器
设:计数器代码需要复用,要将相关代码提取出来
总结:使用setup方法,函数的复用性、简洁性更强
(2) 返回值 return
作用
- 可以在模板template中被使用
- 可以通过setup的返回值来替代data选项
- 可以返回一个执行函数来替代在methods中定义的方法👇
注意: 此时counter并不是响应式数据,因为对于一个定义的变量来说,默认情况下,Vue并不会跟踪它的变化,来引起界面的响应式操作
(3) 数据响应式
❥ Reactive API
-
对传入类型有限制,必须是一个对象或者数组
-
一般用在复杂类型的数据,如账号密码
-
传入基本数据类型(String、Number、Boolean)时会有警告
<template>{{state.name}}{{state.counter}}
</template>
import {reactive} from 'vue'
export default {setup() {const state = reactive({name:'pyy',counter: 100})return {name,counter}}
}
原理:
- 这是因为当我们使用reactive函数处理我们的数据之后,数据再次被使用时就会进行依赖收集;
- 当数据发生改变时,所有收集到的依赖都是进行对应的响应式操作(比如更新界面);
- 事实上,我们编写的data选项,也是在内部交给了reactive函数将其编程响应式对象的;
❥ Ref API
-
ref会返回一个可变的响应式对象
-
该对象作为一个响应式的引用,维护着它内部的值,这就是ref名称的来源
-
它内部的值是在ref的value属性中被维护着
-
在模板(template)引入ref时,vue会自动解包(浅层解包),所以在模板中不需要写ref.value
<template><div class='app'>{{counter}} <!-- 会自动解包 --></div>
</template><script>
import { ref } from 'vue'export default {setup() {let counter = ref(100)const increment = () => {counter.value++ // 表示ref.value}const decrement = () => {counter.value--}return { counter, increment, decrement }}
}</script>
一点小瑕疵
“ref是浅层解包”说法里有点小瑕疵,估计是
相关js👇
const info = {counter // 语法糖写法,全称为 counter: counter,表示counter的值为上方的counter = ref(0)
}
❥ 用哪个
/–ref 可以定义简单的数据,也可以定义复杂的数据
方法 | 定义的数据 | 应用场景 |
---|---|---|
ref | 简单与复杂数据都可以 | 1、其他的场景基本都用ref 2、定义从网络中获取的数据(案例👇) |
reactive | 复杂数据 | 1、本地(生成的)数据,如:账号密码 2、多个数据之间是有关系的,组合在一起有特定联系(表单) |
案例
// 2.定义从网络中获取的数据也是使用ref
// const musics = reactive([]) 但是一般用ref
const musics = ref([])
onMounted(() => {const serverMusics = ["晴天", "屋顶", "听妈妈的话"]musics.value = serverMusics
})
(4) reactive 知识补充 —— readonly
readonly
:一般我们通过reactive
或ref
可以获取到一个响应式的对象。但某些情况下,我们传给其他组件的这个响应式对象希望在另外一个组件被使用,但不能被修改。(了解即可)
(👆总结:响应式对象在其他组件可以被使用,但不能修改)
readonly会返回原始对象的只读代理(也就是它依然是一个Proxy)
单项数据流:组件数据传到另一项组件时为只读状态
react:react的使用是非常灵活的,但是它有一个重要的原则 —— 任何一个组件都应该像函数一样,不能修改传入的props
(2023.7.16 更新到这)
6. 组件(一)
概念及思想
6.1. 注册 调用
全局组件
在任何其他的组件中都可以使用的组件,app.component( 组件名称, 对象 )
方法
局部组件
只有在注册的组件中才能使用的组件,components( 组件名称, 对象 )
属性
(1) 全局组件
app.component( 组件名称, 对象 )
方法,注册全局组件,html 里直接用组件名<组件名称></组件名称>
⭐ vue全局注册案例 —— chatgpt
1.假设你有一个名为"my-component"的组件
<template><div>{{ message }}</div>
</template><script>
export default {data() {return {message: 'Hello World!'}}
}
</script>
2.要在整个应用程序中使用该组件,你需要先调用Vue全局方法"Vue.component()"进行注册,如下所示:
// main.js
import Vue from 'vue'
import MyComponent from './MyComponent.vue'Vue.component('my-component', MyComponent)new Vue({el: '#app',// ...
})
现在,可以在整个应用程序中使用"my-component"组件,如下所示:
// App.vue
<template><div id="app"><my-component></my-component></div>
</template><script>
import MyComponent from './MyComponent.vue'export default {components: {MyComponent}
}
</script>
在这个例子中,我们已经在"main.js"文件中全局注册了"my-component"组件,因此我们不再需要在"App.vue"文件中再次导入和注册该组件。相反,我们可以直接在模板中使用该组件。
总结一下,Vue的全局组件注册使得我们可以在整个应用程序中使用该组件,而不需要重复导入或注册该组件。
课程内容
框架
案例
案例
全局组件的特点: 一旦注册成功后, 可以在任意其他组件的template中使用
以前写法对比图:
案例:
(2) 局部组件
components( 组件名称, 对象 )
属性
const app = Vue.createApp({components: { }, // 局部组件data() { return { } }, // 数据methods:{ }, // 方法computed: { }, // 复杂数据处理watch:{ } // 监听}).mount("#app")
⭐ vue局部注册案例 —— chatgpt
以下是一个简单的 Vue 局部组件注册案例:
<template><div><!-- 使用局部组件 --><my-component></my-component></div>
</template><script>
import MyComponent from './MyComponent.vue'export default {components: {// 注册局部组件'my-component': MyComponent}
}
</script>
在上面的代码中,我们首先引入了 MyComponent 组件,然后在 Vue 实例的 components 选项中注册了该组件。这个组件现在就可以在当前 Vue 组件中使用,并且可以通过 标签来调用。
需要注意的是,在进行局部组件注册时,我们需要指定组件的名称,这个名称将作为组件的标签名在模板中使用。在上面的例子中,我们注册了名为 my-component 的组件,并在模板中使用了它。
值得一提的是,Vue 还支持使用字符串数组来一次性注册多个局部组件,例如:
<script>
import MyComponent1 from './MyComponent1.vue'
import MyComponent2 from './MyComponent2.vue'
import MyComponent3 from './MyComponent3.vue'export default {components: {'my-component-1': MyComponent1,'my-component-2': MyComponent2,'my-component-3': MyComponent3}
}
</script>
在上述代码中,我们同时注册了三个局部组件,并分别指定了它们的名称。这样,在模板中就可以使用 < my-component-1 >、< my-component-2 > 和 < my-component-3 > 这三个标签来调用这三个组件了。
课程
案例:
<div id="app"><home-nav></home-nav><product-item></product-item></div><template id="product"><div class="product"><h2>{{title}}</h2><p>商品描述, 限时折扣, 赶紧抢购</p><p>价格: {{price}}</p><button>收藏</button></div></template><template id="nav"><div>-------------------- nav start ---------------</div><product-item></product-item><div>-------------------- nav end ---------------</div></template>
// 1.创建appconst ProductItem = {template: "#product",data() {return {title: "我是product的title",price: 9.9}}}// 1.1.组件打算在哪里被使用const app = Vue.createApp({// components: option apicomponents: {ProductItem,HomeNav: {template: "#nav",components: {ProductItem}}},// data: option apidata() {return {message: "Hello Vue"}}})// 2.挂载appapp.mount("#app")
图解:
案例:
组件间的嵌套
(3) 组件名字
两种方式
-
连接- 使用驼峰标识符
组件名称使用驼峰式时,components:{ MyItem }
,在 <template></template>
内使用时,以下两种方法都可以调用
<MyItem></MyItem>
<my-item></my-item>
6.2. 通信
父组件传递给子组件: 通过 props
属性
子组件传递给父组件: 通过 $emit
触发事件
(1) 父传子 子用 props 接收 ⭐
子组件放props:[ ]
,props 位置
// vue3export default{components: { }, // 局部组件data() { return { } }, // 数据methods:{ }, // 方法computed: { }, // 复杂数据处理watch:{ }, // 监听props:[] // 或{} //接收父组件传递过来的属性}
根(父)组件
(app.vue)
<!-- 没加:的,传的是字符串类型 加了:的,会变为js代码,自动变成数字类型 -->
①<show-info name="why" :age="18" :height="1.88" address="广州市" abc="cba" class="active" />
②<show-info name="kobe" :age="30" :height="1.87" />
子vue
script
用下面的语法接收,接收后template
用{{}}
调用
-
props 数组语法
props: ["name", "age", "height"]
弊端: 1、不能对类型进行验证;2、没有默认值的
-
props 对象语法
export default {props: {name: {type: String,default: "我是默认name"},age: {type: Number,required: true,default: 0},height: {type: Number,default: 2},// 重要的原则: 对象类型写默认值时, 需要编写default的函数, 函数返回默认值friend: {type: Object,default() { // 或 dafault: () => ({ name: "james" })return { name: "james" }}},hobbies: {type: Array,default: () => ["篮球", "rap", "唱跳"]},showMessage: {type: String,default: "我是showMessage"}}}
(2) 子传父 子用 $emit 发送⭐
添加emits
属性,方便查看发送的参数名,且父组件编写时会自动提示
export default {// 1.emits数组语法emits: ["add"],methods: { 略 }}
全貌:
关于emits验证语法,(先执行函数,再验证,即使为false也是先执行函数,仅为提醒作用)
(3) 非Prop的Attribute
官方概念
(未传递的属性)
解释
用props演示来解释
父组件有传 address="广州市" abc="cba" class="active"
(👆①)
子组件没有{{address}}
接收语句,但vue会自动帮我们接收,添加到子组件的根元素上(在浏览器页面代码上)
inheritAttrs
属性决定要不要自动接收
export default {inheritAttrs: false, // 不接收props: { 略 }}
第一种情况
设置了inheritAttrs: false
,但又想在某元素上调用,则设置$attrs
如:class="$attrs.class"
第二种情况
不设置inheritAttrs
(就是要接收),又有多个根(如下图同级的div)的情况下,使用v-bind="$attrs"
,告诉浏览器把属性传到哪个根(div)上
(4) 通信案例(一)
chatgpt提供案例,简单的 父传子+子传父
(5) 通信案例(二)
课程提供的综合案例
代码:
代码分析:
default:()=>[]
默认值:返回空数组
(6) 非父子通信 事件总线 ⭐
provide Inject
最上层父组件有一个 provide 选项来提供数据
(孙)子组件有一个 inject 选项来开始使用这些数据
provide提供数据
import { computed } from 'vue' // 有@click= 事件// provide一般都是写成函数provide() {return {name: "why",age: 18,message: computed(() => this.message) // computed 复杂数据处理,数据发生变化时自动更新} // this.message 数据来自于data}
Inject 使用数据
inject: ["name", "age", "message"]
全局事件总线
跨层级较多时采用事件总线方法
vue2 有事件总线,vue3移除了,官方推荐了mitt和tiny-emitter库,这里我们使用hy-event-store
安装库npm install hy-event-store
,里面有提供HYEventBus
和HYEventStore
创建全局总线,utils > event-bus.js
import { HYEventBus } from 'hy-event-store'const eventBus = new HYEventBus()export default eventBus
A 发出事件eventBus.emit("名称", 数据, 数据)
B 监听事件created() { eventBus.on("名称", (数据名, 数据名) => { } ) }
created() { eventBus.on("名称", (数据名, 数据名) => { })
}
A.vue
import eventBus from './utils/event-bus'export default {methods: {bannerBtnClick() {console.log("bannerBtnClick")eventBus.emit("whyEvent", "why", 18, 1.88)}}}
B.vue
import eventBus from './utils/event-bus'export default {created() { //声明周期函数eventBus.on("whyEvent",(name, age, height)=>{console.log("whyEvent事件在app中监听", name, age, height)this.message = `name:${name}, age:${age}, height:${height}` // 更改data的message数据})}}
监听后一般要做移除工作,以C.vue文件,监听总线为例
import eventBus from './utils/event-bus'export default {methods: {whyEventHandler() {console.log("whyEvent在category中监听")}},created() { // 声明周期函数eventBus.on("whyEvent", this.whyEventHandler) // 监听的是whyEvent,变化时执行方法},unmounted() {console.log("category unmounted") eventBus.off("whyEvent", this.whyEventHandler) // 销毁监听}}
6.3. 插槽
定义
-
抽取共性,预留不同
-
将共同元素、内容依然在组件内进行封装
-
不同的元素使用slot作为占位,让外部决定显示什么元素
(1) 基础用法(单插槽)
<slot></slot>
(2) 具名插槽(多插槽)
子组件传递:<slot name="插槽名称">默认值</slot>"
父组件接收,方式一:v-slot:插槽名称
方式二:#插槽名称
(父组件未指定接收名字时,名称为default 。v-slot:default
)
子组件
<div class="right"><slot name="right">right</slot> </div>
里面的right为默认内容,当父组件调用又无实际内容时,显示right
父组件
<template #center><span>内容</span></template><template v-slot:right><a href="#">登录</a></template>
(3) 动态插槽
(4) 作用域插槽
即使有通讯,vue - template - {{ }}
也只是获取自己的 vue-script-data
,这称之为渲染作用域
改良目的:不想制作文字使用,也可以是button
更多:
ppt内的解释:
6.4. 生命周期
速览
创建 -> 加载(挂载) -> 更新 -> 销毁(卸载)
/–创建前 beforeCreate
/–创建后 created
/–加载前 beforeMount
/–加载后 mounted DOM渲染在此周期中已经完成
/–更新前 beforeUpdate
/–更新后 updated
/–销毁(卸载)前 beforeDestroy / beforeUnmount
/–销毁(卸载)后 destroyed / Unmounted
销毁和卸载的区别
-
vue版本
vue2 -> 销毁 destroyed
vue3 -> 卸载 Unmounted -
处理工作上 —— chatgpt
destroyed适合处理一些清理工作,如清除计时器、取消网络请求、销毁第三方库等
Unmounted适合做一些操作DOM的工作,如获取元素高度、保存滚动位置等 -
执行时机 —— chatgpt
destroyed:组件实例完全销毁之后调用,此时所有的指令以及事件监听器都已经被移除,数据绑定也被解绑。
Unmounted:组件从DOM中卸载之前调用,此时可以访问到组件实例、指令、事件以及DOM元素,但是该组件的实例上的所有指令和事件监听器都已经被移除。
生命周期详解
生命周期函数是一些钩子函数(回调函数)。P1027
export default {// 1.组件被创建之前beforeCreate() {console.log("beforeCreate - 组件被创建之前");},// 2.组件被创建完成 ⭐created() {console.log("created - 组件被创建完成")console.log("1.发送网络请求, 请求数据")console.log("2.监听eventbus事件")console.log("3.监听watch数据")},// 3.组件template准备被挂载beforeMount() {console.log("beforeMount - 组件template准备被挂载")},// 4.组件template被挂载: 虚拟DOM -> 真实DOM ⭐mounted() {console.log("mounted - 组件template被挂载")console.log("1.获取DOM")console.log("2.使用DOM")},// 5.数据发生改变// 5.1. 准备更新DOMbeforeUpdate() {console.log("数据发生改变");console.log("beforeUpdate - 准备更新DOM")},// 5.2. 更新DOMupdated() {console.log("updated - 更新DOM")},// 6.卸载VNode -> DOM元素// 6.1.卸载之前beforeUnmount() {console.log("beforeUnmount - 卸载之前")},// 6.2.DOM元素被卸载完成 ⭐unmounted() {console.log("unmounted - DOM元素被卸载完成")}}
移除案例
6.5. ref引用 $refs
ref是什么?
在Vue中,ref是一种特殊的属性,用于给元素或组件指定一个唯一的标识符,以便可以在JavaScript代码中访问该元素或组件。通过this.$refs对象,我们可以访问所有具有 ref 属性的元素或组件,并且可以执行操作,例如访问DOM元素的属性或调用组件的方法。
作用
帮助获取DOM
在Vue开发中我们是不推荐进行原生DOM操作的;
这个时候,我们可以给元素或者组件绑定一个ref的attribute属性
使用方法
html元素加上ref="名称"
,方法 methods 中通过 this.$名称
获取
<h2 ref="title" class="title" :style="{ color: titleColor }">{{ message }}</h2>
<button ref="btn" @click="changeTitle">修改title</button><banner ref="banner"/>
export default {components: { Banner }, data() { return { message: "Hello World", titleColor: "red" } },methods: {changeTitle() {// 2.获取h2/button元素console.log(this.$refs.title) // 打印整个h2代码console.log(this.$refs.btn) // 打印整个button代码// 3.获取banner组件: 组件实例console.log(this.$refs.banner) // 打印代理对象// 3.1.在父组件中可以主动的调用子组件的对象方法this.$refs.banner.bannerClick() // 执行方法// 3.2.获取banner组件实例, 获取banner中的元素console.log(this.$refs.banner.$el) // 打印banner的<div>...</div>// 3.3.如果banner template是多个根, 拿到的是第一个node节点// 注意: 开发中不推荐一个组件的template中有多个根元素// console.log(this.$refs.banner.$el.nextElementSibling)// 4.组件实例还有两个属性(了解):console.log(this.$parent) // 获取父组件 打印代理对象console.log(this.$root) // 获取根组件 打印代理对象}}}
效果
6.6. 动态组件 :is="组件名称"
通过 <component :is="组件名称"> </component>
绑定
效果
解释
组件名Home和home的大小写没关系
里边的props + $emit 知识点
6.7. keep-alive 保持存活
如:tabA有计数器,选择了10。此时切换到tabB,再切换回tabA,我的计数器是多少?
- 用了
<keep-alive></keep-alive>
,计数器仍保持在10的状态 - 没有
<keep-alive></keep-alive>
,计数器还原(原因:切换到tabB时,tabA已经被卸载,使用unmounted
可看到提示已被卸载)
属性:
include
属性决定哪个要保持存活,不被销毁,(字符串、正则、数组)exclude
属性决定哪个不被缓存,要销毁,(字符串、正则、数组)max
属性决定最多可以缓存多少组件实例,一旦达到这个数字,那么最近没有被访问的实例会被销毁,(数字、字符串)- 注意组件name中间
,
后不加空格,直接写组件名称
<keep-alive include="组件A定义的name,组件B定义的name"><component :is="组件名称"></component>
</keep-alive>
created 创建后、unmounted 卸载后(卸载成功)
当使用了keep-alive
保持存活后,该组件就不会执行unmounted函数
缓存组件的生命周期
// 对于保持keep-alive组件, 监听有没有进行切换
// keep-alive组件进入活跃状态
activated() {console.log("home activated") // 进入该组件
},
deactivated() {console.log("home deactivated") // 退出该组件
}
7. 组件(二)
7.1. 异步组件 (了解)
异步组件不常用,一般使用懒加载的方式
学习异步组件前,先了解一下webpack 代码分包
(1) webpack 代码分包
当自己编写的页面代码过多时,首屏渲染速度就会延长。
(2) 异步组件使用
打包某个js文件
打包某个组件
方式一:
// 引入方法
import { defineAsyncComponent } from 'vue'
// 引入组件
const AsyncCategory = defineAsyncComponent(() => import("./views/Category.vue"))
// 在components中调用
export default {components: {Category: AsyncCategory},
}// 注意:
// 是把 import Category from './views/Category.vue' 改为上面defineAsyncComponent方法
方式二:
7.2. 组件的v-model
普通v-model
<!-- 1.input v-model -->
<input v-model="message">
<input :value="message" @input="message = $event.target.value">
概念
顾名思义,在组件中使用v-model,它默认完成了两件事情:
v-bind:value
的数据绑定,其中modelValue 是默认名@input
的事件绑定,@update:model-value 也是事件默认名,v-on:update:model-value
=@update:model-value
基本用法
上下两行是等价的,(modelValue 等同于 model-value)
<my-input v-model="message"/>
<my-input :model-value="message" @update:model-value="message = $event"></my-input>
APP.vue
<template><div class="app"><counter v-model="appCounter"></counter><counter :modelValue="appCounter" @update:modelValue="appCounter = $event"></counter></div>
</template><script>import Counter from './Counter.vue'export default {components: {Counter},data() {return {appCounter: 100,}}}
</script><style scoped>
</style>
- 上下两个counter是相等的
- 绑定更新事件,$event数据等于自己绑定的值
Counter.vue
<template><div><h2>Counter: {{ modelValue }}</h2><button @click="changeCounter">修改counter</button></div>
</template><script>export default {// 接收props: {modelValue: {type: Number,default: 0}},// 发送emits: ["update:modelValue"],methods: {changeCounter() {this.$emit("update:modelValue", 999)} }}
</script><style scoped>
</style>
- 接收modelValue为数字类型,如无内容默认为0
效果:点击前100,点击后为999
组件自定义名称
<!-- 3.组件的v-model: 自定义名称counter -->
<counter2 v-model:counter="appCounter" v-model:why="appWhy"></counter2>
App.vue
<template><div class="app"><counter2 v-model:counter="appCounter"v-model:why="appWhy"></counter2></div>
</template><script>import Counter2 from './Counter2.vue'export default {components: {Counter2},data() {return {appCounter: 100,appWhy: "coderwhy"}}}
</script><style scoped>
</style>
counter2.vue
<template>
<div><h2>Counter: {{ counter }}</h2><button @click="changeCounter">修改counter</button><!-- why绑定 --><hr><h2>why: {{ why }}</h2><button @click="changeWhy">修改why的值</button>
</div>
</template><script>
export default {props: {// 接收counter组件v-model,默认值为数字0,有接收值,为100counter: {type: Number,default: 0},// 接收why组件v-model,默认值为空字符串,有接收值,为coderwhywhy: {type: String,default: ""}},// 发送emits: ["update:counter", "update:why"],methods: {changeCounter() {this.$emit("update:counter", 999)},changeWhy() {this.$emit("update:why", "kobe")}}
}
</script><style scoped>
</style>
7.3. 混入Mixin
组件和组件之间有时候会存在相同的代码逻辑,我们希望对相同的代码逻辑进行抽取,vue2和vue都支持使用Mixin来完成(vue2使用较多,vue3已经不怎么用了)
作用
- 分发Vue组件中的可复用功能
- 一个min对象可以包含任何组件选项
- 当组件使用Mixin对象时,所有Mixin对象的选项将被 混合 进入该组件本身的选项中
使用方法
views
文件夹同级新建一个mixins
文件夹
- 新建
message-mixin.js
文件
export default {data() {return {message: "Hello World"}},created() {console.log("message:", this.message)}
}
About.vue
引用文件
<template><h2>About组件</h2>
</template><script>// 引入方法import messageMixin from '../mixins/message-mixin'export default {// 调用mixins: [messageMixin]}
</script><style scoped>
</style>
混入的含义
message-mixin.js
的data数据会自动合并(混入)到About.vue
的data里,包括生命周期(created)等其他函数都会自动合并(混入)并执行
合并规则
Mixin对象中的选项和组件对象中的选项发生冲突,分成不同情况来处理
情况一:如果是data函数的返回值对象
- 返回值对象默认情况下会进行合并
- data返回值对象的属性发生冲突时会保留组件自身的数据
(如message-mixin.js
和About.vue
都有message,则以About.vue
的message数据为准)
情况二:如果有相同的生命周期钩子函数
- 生命周期的钩子函数会被合并到数组中,都会被调用
情况三:值为对象的选项,例如methods
、components
和directives
,将被合并为同一个对象
- 比如都有methods选项,并且都定义的方法,那么它们都会生效
- 但如果对象的key相同(key:value),那么会取组件自身的对象键值(如上案例,假设
message-mixin.js
和About.vue
都有massage,则优先取About.vue
的值)
全局混入
app.mixin({})
8. 路 由
9. 搭建项目
主要有以下几种方法
-
CDN引入方式:这种方法适用于简单的页面,只需要在HTML文件中引入Vue的CDN链接即可。这种方式在Vue 2和Vue 3中都适用。
-
Vue CLI 脚手架:Vue CLI是Vue官方提供的脚手架工具,可以快速搭建起一个Vue项目,并且提供了一些便捷的功能,如自动生成代码、打包压缩等。在Vue 2中,通过命令行输入“vue create 项目名”即可创建项目;而在Vue 3中,使用Vue CLI需要先全局安装Vue CLI 4.x或以上版本,然后通过命令行输入“vue create 项目名”创建项目。
-
使用Webpack手动配置:如果想要更加灵活地配置项目,可以选择手动配置Webpack。在Vue 2中,需要先安装Vue Loader和相关插件,然后通过Webpack配置文件进行相关配置;而在Vue 3中,Vue Loader已经与Vue CLI集成,只需要在Webpack配置文件中引入Vue即可开始开发。
-
使用Vite:Vite是Vue3官方推荐的构建工具,可以快速搭建Vue项目,并且具备高效的开发体验。Vite使用ES模块化机制来加载代码,能够极大地提升项目的启动速度和开发效率。使用Vite搭建Vue项目非常简单,只需要全局安装Vite并执行“vite create 项目名”即可创建项目。
9.1. Vue CLI 脚手架
(1) 安装
- 安装
npm install @vue-cli -g
不行时用npm install -g @vue/cli
- 查看版本
vue --version
(2) 创建项目 —— vue create 项目名称
-
vue create 项目名称
,(底层打包工具是 webpack) -
是否使用淘宝源 >> no
-
选择预设 >> Manually select features 手动选择新特性
-
后期做项目会选择:
-
选择vue版本 >> 3.x
-
babel放独立文件还是package.json
-
是否生成预设 >> yes >> 名称
-
使用工具 >> NPM 或者 PNPM
-
项目创建成功
-
启动服务
npm run server
,更改了配置文件得重新启动服务 -
打开项目
http://localhost:8080/
(3) 创建项目 —— npm init vue@latest
(底层打包工具是vite,越来越流行,打包效率高?待核实)
- 是否安装create-vue,y
- 项目名称
- typeScript,n
- jsx,n
- 暂时都n
- 创建成功👇
(4) 区别
(来自chatgdp)
npm init vue@latest
和vue create
项目名称都是用于创建Vue.js项目的命令,但它们有以下不同点:
-
npm init vue@latest
是通过npm包管理器在当前目录下初始化Vue.js项目,并生成一个package.json文件。
而vue create 项目名称
则是通过Vue CLI工具在指定目录下创建新的Vue.js项目。 -
npm init vue@latest
只会安装Vue.js框架本身,你需要手动安装其他依赖项,如Vue Router、Vuex等。
vue create 项目名称
可以快速构建带有预设置集成的Vue.js应用程序,包括路由、状态管理、Linter、测试等等。它还提供了各种选项和预设模板,方便你根据需求进行选择。
因此
如果你想要更加自定义化地创建Vue.js项目,则可以使用npm init vue@latest
;
如果你想要更快捷地创建符合标准规范的Vue.js应用程序,则可以选择vue create 项目名称
。
9.2. 项目结构
(1) .browserlistrc 浏览器视配文件
/–>1%
市场占有率大于1%
last 2 versions
支持最后两个版本
not dead
还在维护的
not ie 11
非ie11
(2) 配置别名
vue.config.js
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({transpileDependencies: true,configureWebpack: {resolve: {// 配置路径别名// @是已经配置好的路径别名: 对应的是src路径alias: {"utils": "@/utils" }}}})
使用 utils/文件夹/文件夹/math.js
时,可以直接写 utils/math
,(此时没有文件路径提示)
添加文件路径提示:jsconfig.json
"paths": {"utils/*": ["src/utils/*"]
},
解释 jsconfig.json
(3) vscode 关于 vue 的插件
vetur:推荐在 vue2 使用 ✗
volar:推荐在vue3使用,目前比较好用 ⭐
(4) style 作用域
- 在 app.vue 内设置的常见样式
.title { }
,会使底下其他组件的.title { }
生效,为不影响他们,添加<style scoped></style>
生成自己的作用域
10. 概念
10.1. VNode
- 全程Virtual Node,虚拟节点
- 无论是组件还是元素,它们最终在Vue中表示出来的都是一个个VNode
- VNode的本质是一个JavaScript的对象
- template 👉 VNode 👉 真实DOM
10.2. 虚拟DOM
- 由多个虚拟节点(VNode)组成一颗树(虚拟Dom)
- 为什么要生成虚拟DOM?
- 便于跨平台,写一份代码渲染到多个平台上
- 便于跨平台,写一份代码渲染到多个平台上
10.3. proxy
proxy 代理对象,解释:《一篇彻底理解Proxy - LBJ》
10.4. el:#app
意思是把前面html页面中id='app进行了染
10.5. $event
10.6. 解构
(待补充)
10.7. 单项数据流
数据传递给另一个组件时,只允许阅读,不允许修改,称为单向数据流
11. 案例
11.1. 购物车
效果
代码截图
分析
- books,数据来源:本地和服务器写法
created(){}
生命周期回调函数:
当前的组件被创建时自动执行一般在该函数中, 会进行网络请求
- 总价的两种计算方法,totalPrice() {} ,有高阶函数写法 reduce 累加器
该高阶仅有一句话,可省略为
- 有多个价格在不同的位置,但前面都要添加¥,可以通过设置一个方法的形式添加
- 监听点击了哪一个商品的
+
/-
- 小于等于1时,按钮禁用
- 移除商品
- 购物车移除全部商品后,文字提示
- 点击效果,点击行变色,其他行还原
11.2. 切换+通信
看 5.2 (5) 通信案例(二)课程提供的综合案例