本文以coderwhy前端系统课 - 阶段六VUE为主。
一刷版本的笔记有些乱,目前还在二刷整理,同时参考了一部分其他的资料,并加上个人使用总结
暂时停更,在备考,考完分享《软考中级-软件评测师》的复习资源和笔记
-----2023-10-23
建议使用 资源绑定 或 链接 里的html文件进行阅读
直接下载
资源说明:
- 资源内有自定义的样式更便于阅读,这里的样式不做额外编写
- 资源内点击侧边栏开关时文章阅读位置会偏移,点击目录定位即可
- 资源内的图片无法像csdn的可以点击放大,但能看清的
- 该文章有些定位点链接,资源内可点击,文章这点了没效果,不做额外修改
- 该文章出现一些看不懂的符号拼接啥的,略过
(一些自定义语法,太多了,这回应该删完了,后面更新估计懒得删)----2023.10.23 更新----
- 图片经停1秒会放大显示
(会有文章抖动现象,vscode的md目前没有像csdn的可以点击放大图片)
放在开头为了方便对比,后面的内容以vue3为主
/.wrap2 <
/.box <
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>
/->
/.box <
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>
/->
/->
– 来自chatgpt,两者都使用Vue Router来实现路由功能
/.wrap2 <
/.box <
vue2
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
指定该路由对应的组件。
new Vue({
router,
render: h => h(App),
}).$mount('#app')
在 Vue3 中,render 函数的写法有所不同,使用了新的 createApp API。
/->
/.box <
vue3
提供了一个基于函数式API的新特性:createRouter()
import { createRouter, createWebHistory } from 'vue-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
属性。
createApp(App).use(router).mount('#app')
这里使用createApp()方法来创建Vue实例,并通过use()方法将router实例添加到Vue实例中。
/->
/->
备注
vue2 中的 render: h => h(App)
{{}}
/.wrap2 <
/.box <
html
<h2>{{ message }}h2>
<h2>当前计数: {{ counter }} h2>
<h2>计数双倍: {{ counter * 2 }}h2>
<h2>展示的信息: {{ info.split(" ") }}h2>
<h2>{{ age >= 18? "成年人": "未成年人" }}h2>
<h2>{{ formatDate(time) }}h2>
/->
/.box <
javascript
//data
counter: 100,
info: "my name is why",
age: 22,
time: 123
//method
formatDate: function(date) {
return "2022-10-10-" + date
}
/->/->
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}
某值改变时才更新内容,可查看《vue3.2新增指令v-memo的使用 - 南风晚来晚相识》
/.wrap2 <
/.box <
html
<div id="app">
<div v-memo="[name]">
<h2>姓名: {{ name }}h2>
<h2>年龄: {{ age }}h2>
<h2>身高: {{ height }}h2>
div>
<button @click="updateInfo">改变信息button>
div>
/->
/.box <
javascript
data: function() {
return {
name: "why",
age: 18,
height: 1.88
}
},
methods: {
updateInfo: function() {
this.name = "kobe"
this.age = 20
}
}
/->/->
:
原语句 | 语法糖 |
---|---|
v-bind:src="" |
:src |
v-bind:href="" |
:href |
v-bind:class="" |
:class |
绑定方法参考
/.wrap2 <
/.box lit3<
html
<img v-bind:src="showImgUrl" alt="">
<img :src="showImgUrl" alt="">
<a :href="href">百度一下a>
/->
/.box lit7<
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 不是:显示图片1
this.showImgUrl = this.showImgUrl === this.imgUrl1 ? this.imgUrl2: this.imgUrl1
}
}
/->/->
/.wrap2 <
/.box lit3<
前提:
// css
.active {
color: red;
}
//data
isActive: false,
// methods
btnClick: function() {
this.isActive = !this.isActive
},
/->
/.box lit7<
<button :class="isActive ? 'active': ''" @click="btnClick">我是按钮button>
<button class="haha" :class="{ active: isActive }" @click="btnClick">我是按钮button>
<button :class="{ active: isActive, why: true, kobe: false }" @click="btnClick">我是按钮button>
<button class="abc cba" :class="getDynamicClasses()" @click="btnClick">我是按钮button>
//method
getDynamicClasses: function() {
return { active: this.isActive, why: true, kobe: false }
}
<h2 :class="['abc', 'cba']">Hello Arrayh2>
<h2 :class="['abc', className]">Hello Arrayh2>
<h2 :class="['abc', className, isActive? 'active': '']">Hello Arrayh2>
<h2 :class="['abc', className, { active: isActive }]">Hello Arrayh2>
/->/->
,
<h2 style="color: red; font-size: 30px;">哈哈哈哈h2>
<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 Worldh2>
//data
name: "class"
<h2 v-bind="infos">Hello Bindh2>
// data
infos: { name: "why", age: 18, height: 1.88, address: "广州市" },
@
原语句 | 语法糖 |
---|---|
v-on:click=" " |
@click=" " |
绑定方法参考
/.wrap2 <
/.box lit7<
<div class="box" v-on:click="divClick">div>
<div class="box" @click="divClick">div>
<div class="box" @mousemove="divMousemove">div>
<div class="box" @click="divClick" @mousemove="divMousemove">div>
/->
/.box lit3<
methods: {
divClick() {
console.log("divClick")
},
divMousemove() {
console.log("divMousemove")
}
}
/->/->
/.wrap2 <
/.box <
<button @click="btn1Click">按钮1button>
<button @click="btn2Click('pyy', age)">按钮2button>
<button @click="btn3Click('pyy', age, $event)">按钮3button>
/->
/.box <
//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>
使用过
/.wrap2 <
/.box lit3<
回车自动触发:@keyup.enter.native
阻止默认事件:@submit.native.prevent
/->
/.box lit7<
/->/->
其他修饰符
.stop
- 调用 event.stopPropagation(),这是阻止事件的冒泡方法,不让事件向document上蔓延,但是默认事件任然会执行,当你掉用这个方法的时候,如果点击一个连接,这个连接仍然会被打开,解释来源.prevent
- 调用event.preventDefault(),这是阻止默认事件的方法,调用此方法是,连接不会被打开,但是会发生冒泡,冒泡会传递到上一层的父元素;.capture
- 添加事件侦听器时使用 capture 模式,事件冒泡.self
- 只当事件是从侦听器绑定的元素本身触发时才触发回调.{keyAlias}
- 仅当事件是从特定键触发时才触发回调.once
- 只触发一次回调.left
- 只当点击鼠标左键时触发.right
- 只当点击鼠标右键时触发.middle
- 只当点击鼠标中键时触发passive -{ passive: true}模式添加侦听器v-if="条件"
条件成立,执行该段
v-else
条件不成立,执行该段
/.wrap2 <
/.box <
<ul v-if="names.length > 0">
<li v-for="item in names">{{item}}li>
ul>
<h2 v-else>当前names没有数据, 请求获取数据后展示h2>
v-for(元素 in 列表)
遍历
/->
/.box <
//data
names:[] // 无数据
names:[ab, ad, ae] // 有数据
/->/->
/.wrap2 <
/.box <
<div class="info" v-if="Object.keys(info).length">
<h2>个人信息h2>
<ul>
<li>姓名: {{info.name}}li>
<li>年龄: {{info.age}}li>
ul>
div>
<div v-else>
<h2>没有输入个人信息h2>
<p>请输入个人信息后, 再进行展示~p>
div>
/->
/.box <
info: {name:"aa", age:11}
/->/->
<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>
/.wrap2 <
/.box <
<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>
/->
/.box <
// data
isShowCode: true
//method
toggle() {
this.isShowCode = !this.isShowCode
}
/->
/->
注意:
v-if=true/false
也可以控制元素的隐藏显示,区别在于:
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 值
/.wrap2 <
/.box lit4 <
html
<li v-for="movie in movies">{{ movie }}li>
<li v-for="(value, key, index) in info">
{{value}}-{{key}}-{{index}}
li>
<li v-for="(movie, index) in movies">
{{index + 1}} - {{ movie }}
li>
<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>
/->
/.box lit6 <
javascript
// data
// 1.movies
movies: ["星际穿越", "少年派", "大话西游", "哆啦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时,也会执行diff算法,对比遇到不一样的内容时,后面的全部替换:
<li v-for="item in letters" :key="item">{{item}}li>
key的面试问答
当div没有意义,又使用了v-xxx
,那就把 div 换成 template
可以放id
以下来自解释来自于《vue v-if与v-show的区别,template的使用 - 键盘上的那抹灰》
template
替换该元素原理和使用方法
写法:
✗ 手动绑定,先显示message值在input里,input输入其他值时,message同步改变
<input type="text" :value="message" @input="inputChange">
<h2>{{message}}h2>
//data
message: "Hello Model",
// methods
inputChange(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.account
const password = this.password
// url发送网络请求
console.log(account, password)
}
lable 里的 for 为 html 内的知识点,点击文字也可以关联到 input
v-model-lazy
会将绑定的事件切换为 change 事件,只有在提交时(比如回车)才会触发
v-model-number
转为数字类型
v-model-trim
动过滤用户输入的空白字符
使用多个
v-model.lazy.trim=""
可以组合使用
Options API 包含
export default {
data() { return { } }, // 数据
props: [ ] // 接收父组件传递过来的属性
methods:{ }, // 方法
watch: { }, // 监听(数据变化)
computed: { }, // 复杂数据处理
minxins: { }, // 混入(合并),vue2用的多
components: { }, // 局部组件
created() { }, // 监听(创建后)
+
// lifecycle hooks 生命周期钩子函数,如
// created、mounted、updated、destroyed 等
// 以下不确定
provide: [ ] // 提供数据给inject
inject: [ ] // 使用props这些数据
}
<h2>{{ firstName + " " + lastName }}h2>
<h2>{{ score >= 60 ? '及格': '不及格' }}h2>
<h2>{{ message.split(" ").reverse().join(" ") }}h2>
/–split 将字符串转化为数组
/–reverse 反转
/–join 用xx拼接
函数调用
/.wrap2 <
/.box <
<h2>{{ getFullname() }}h2>
<h2>{{ getFullname() }}h2>
<h2>{{ getFullname() }}h2>
<h2>{{ getScoreLevel() }}h2>
<h2>{{ reverseMessage() }}h2>
/->
/.box <
// methods
getFullname() {
return this.firstName + " " + this.lastName
},
getScoreLevel() {
return this.score >= 60 ? "及格": "不及格"
},
reverseMessage() {
return this.message.split(" ").reverse().join(" ")
}
/->/->
弊端:所有的data使用过程都变成了方法的调用
官方:任何包含响应式数据的复杂逻辑,都应该使用计算属性(案例里都算相应式数据的复杂逻辑)
数据更新时会自动处理
computed 位置
const app = Vue.createApp({
data() { return { } },
methods:{ },
computed: { }
}).mount("#app")
处理案例:
/.wrap2 <
/.box lit4 <
<h2>{{ fullname }}h2>
<h2>{{ fullname }}h2>
<h2>{{ fullname }}h2>
<h2>{{ scoreLevel }}h2>
<h2>{{ reverseMessage }}h2>
/->
/.box lit6 <
// 1.创建app
const app = Vue.createApp({
// data: option api
data() {
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.挂载app
app.mount("#app")
/->/->
位置
const app = Vue.createApp({
data() { return { } },
methods:{ },
computed: { },
watch:{ }
}).mount("#app")
dataName(newValue, oldValue) {}
const app = Vue.createApp({
// data: option api
data() {
return {
message: "Hello Vue",
info: { name: "why", age: 18 }
}
},
methods: {
changeMessage() {
this.message = "你好啊, 李银河!"
this.info = { name: "kobe" }
}
},
watch: {
// 1.默认有两个参数: newValue/oldValue
message(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)
methods方法修改内容时,虽然内容会有变化,但是默认的watch不会进行深度监听!,所以在watch里默认没有监听到
修改info.name
,监听变化
handler(){}
相当于info(newValue,oldValue){}
的语法糖deep: true
启动深度监听info.name:kobe
后改深度对象返回的newValue,oldValue是相同的immediate
,第一次渲染时执行一次info.name function
是vue2的知识点,能监听到name的变化监听message
const app = Vue.createApp({
// data: option api
data() {
return {
message: "Hello Vue"
}
},
methods: {
changeMessage() {
this.message = "你好啊, 李银河!"
}
},
// 生命周期回调函数: 当前的组件被创建时自动执行
// 一般在该函数中, 会进行网络请求
created() {
// ajax/fetch/axios
console.log("created")
this.$watch("message", (newValue, oldValue) => {
console.log("message数据变化:", newValue, oldValue)
}, { deep: true })
}
})
vue2编写组件的方式是Options API
但是这种代码有很大的弊端
当组件非常大时,这种碎片化的代码使用、理解和维护这个复杂的组件变得异常困难,并且隐藏了潜在的逻辑问题。并且处理单个逻辑关注点时,需要不断地跳转到相应的代码中
概念及思想
全局组件
在任何其他的组件中都可以使用的组件,app.component( 组件名称, 对象 )
方法
局部组件
只有在注册的组件中才能使用的组件,components( 组件名称, 对象 )
属性
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的全局组件注册使得我们可以在整个应用程序中使用该组件,而不需要重复导入或注册该组件。
课程内容
/.wrap2 <
/.box lit3 <
框架
/->
/.box lit3 <
案例
全局组件的特点: 一旦注册成功后, 可以在任意其他组件的template中使用
案例:
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.创建app
const ProductItem = {
template: "#product",
data() {
return {
title: "我是product的title",
price: 9.9
}
}
}
// 1.1.组件打算在哪里被使用
const app = Vue.createApp({
// components: option api
components: {
ProductItem,
HomeNav: {
template: "#nav",
components: {
ProductItem
}
}
},
// data: option api
data() {
return {
message: "Hello Vue"
}
}
})
// 2.挂载app
app.mount("#app")
组件间的嵌套
两种方式
-
连接组件名称使用驼峰式时,components:{ MyItem }
,在 内使用时,以下两种方法都可以调用
父组件传递给子组件: 通过 props
属性
子组件传递给父组件: 通过 $emit
触发事件
子组件放props:[ ]
,props 位置
// vue3
export default{
components: { }, // 局部组件
data() { return { } }, // 数据
methods:{ }, // 方法
computed: { }, // 复杂数据处理
watch:{ }, // 监听
props:[] // 或{} //接收父组件传递过来的属性
}
根(父)组件
(app.vue)
①<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"
}
}
}
添加emits
属性,方便查看发送的参数名,且父组件编写时会自动提示
export default {
// 1.emits数组语法
emits: ["add"],
methods: { 略 }
}
全貌:
关于emits验证语法,(先执行函数,再验证,即使为false也是先执行函数,仅为提醒作用)
官方概念
解释
用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)上
chatgpt提供案例,简单的 父传子+子传父
课程提供的综合案例
default:()=>[]
默认值:返回空数组
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) // 销毁监听
}
}
定义
抽取共性,预留不同
将共同元素、内容依然在组件内进行封装
不同的元素使用slot作为占位,让外部决定显示什么元素
子组件传递:
父组件接收,方式一:v-slot:插槽名称
方式二:#插槽名称
(父组件未指定接收名字时,名称为default 。v-slot:default
)
子组件
<div class="right">
<slot name="right">rightslot>
div>
里面的right为默认内容,当父组件调用又无实际内容时,显示right
父组件
<template #center>
<span>内容span>
template>
<template v-slot:right>
<a href="#">登录a>
template>
即使有通讯,vue - template - {{ }}
也只是获取自己的 vue-script-data
,这称之为渲染作用域
改良目的:不想制作文字使用,也可以是button
更多:
ppt内的解释:
速览
创建 -> 加载(挂载) -> 更新 -> 销毁(卸载)
/–创建前 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元素,但是该组件的实例上的所有指令和事件监听器都已经被移除。
生命周期详解
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. 准备更新DOM
beforeUpdate() {
console.log("数据发生改变");
console.log("beforeUpdate - 准备更新DOM")
},
// 5.2. 更新DOM
updated() {
console.log("updated - 更新DOM")
},
// 6.卸载VNode -> DOM元素
// 6.1.卸载之前
beforeUnmount() {
console.log("beforeUnmount - 卸载之前")
},
// 6.2.DOM元素被卸载完成 ⭐
unmounted() {
console.log("unmounted - DOM元素被卸载完成")
}
}
移除案例
$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">修改titlebutton>
<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的...
// 3.3.如果banner template是多个根, 拿到的是第一个node节点
// 注意: 开发中不推荐一个组件的template中有多个根元素
// console.log(this.$refs.banner.$el.nextElementSibling)
// 4.组件实例还有两个属性(了解):
console.log(this.$parent) // 获取父组件 打印代理对象
console.log(this.$root) // 获取根组件 打印代理对象
}
}
}
:is="组件名称"
通过
绑定
解释
组件名Home和home的大小写没关系
如:tabA有计数器,选择了10。此时切换到tabB,再切换回tabA,我的计数器是多少?
,计数器仍保持在10的状态
,计数器还原(原因:切换到tabB时,tabA已经被卸载,使用unmounted
可看到提示已被卸载)属性:
include
属性决定哪个要保持存活,不被销毁,(字符串、正则、数组)exclude
属性决定哪个不被缓存,要销毁,(字符串、正则、数组)max
属性决定最多可以缓存多少组件实例,一旦达到这个数字,那么最近没有被访问的实例会被销毁,(数字、字符串),
后不加空格,直接写组件名称<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") // 退出该组件
}
异步组件不常用,一般使用懒加载的方式
学习异步组件前,先了解一下webpack 代码分包
当自己编写的页面代码过多时,首屏渲染速度就会延长。
打包某个组件
方式一:
// 引入方法
import { defineAsyncComponent } from 'vue'
// 引入组件
const AsyncCategory = defineAsyncComponent(() => import("./views/Category.vue"))
// 在components中调用
export default {
components: {
Category: AsyncCategory
},
}
// 注意:
// 是把 import Category from './views/Category.vue' 改为上面defineAsyncComponent方法
普通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>
/.wrap2 <
/.box <
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>
/->
/.box <
Counter.vue
<template>
<div>
<h2>Counter: {{ modelValue }}h2>
<button @click="changeCounter">修改counterbutton>
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>
/->
/->
效果:点击前100,点击后为999
组件自定义名称
<counter2 v-model:counter="appCounter" v-model:why="appWhy">counter2>
/.wrap2 <
/.box <
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>
/->
/.box <
counter2.vue
<template>
<div>
<h2>Counter: {{ counter }}h2>
<button @click="changeCounter">修改counterbutton>
<hr>
<h2>why: {{ why }}h2>
<button @click="changeWhy">修改why的值button>
div>
template>
<script>
export default {
props: {
// 接收counter组件v-model,默认值为数字0,有接收值,为100
counter: {
type: Number,
default: 0
},
// 接收why组件v-model,默认值为空字符串,有接收值,为coderwhy
why: {
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>
/->
/->
组件和组件之间有时候会存在相同的代码逻辑,我们希望对相同的代码逻辑进行抽取,vue2和vue都支持使用Mixin来完成(vue2使用较多,vue3已经不怎么用了)
作用
使用方法
views
文件夹同级新建一个mixins
文件夹/.wrap2 <
/.box <
message-mixin.js
文件export default {
data() {
return {
message: "Hello World"
}
},
created() {
console.log("message:", this.message)
}
}
/->
/.box <
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函数的返回值对象
message-mixin.js
和About.vue
都有message,则以About.vue
的message数据为准)情况二:如果有相同的生命周期钩子函数
情况三:值为对象的选项,例如methods
、components
和directives
,将被合并为同一个对象
message-mixin.js
和About.vue
都有massage,则优先取About.vue
的值)全局混入
app.mixin({})
Composition API是什么,跟options API有什么区别 —— chatgpt
Composition API是Vue 3中提供的一种新的API风格,用于组织和重用组件逻辑。与Options API不同,Composition API将相关的代码组合在一起,以便更好地组织和重用代码。它基于函数而不是对象的形式,使得代码更容易拆分成可组合的逻辑块。此外,使用Composition API可以更好地封装和隐藏信息,并在组件之间共享逻辑,从而提高代码的可维护性和可重用性。总之,Composition API是Vue 3中一个非常实用且令人期待的改进,可以帮助我们更轻松地构建复杂的Web应用程序。
Composition API包含什么 —— chatgpt
reactive
:用于将一个普通对象转换为响应式对象,可以监听该对象属性的变化。
ref
:用于将基本类型数据转换为响应式对象,可以监听该值的变化。
computed
:用于创建计算属性,依赖于其他响应式对象和计算属性。
watchEffect
:用于监听响应式对象的变化,并在变化时执行回调函数。
生命周期钩子:包括onMounted
(挂载到DOM后)、onUpdated
(更新后)、onUnmounted
(卸载后)等,用于在组件生命周期的不同阶段执行相应的操作。
provide
和inject
:用于跨层级传递数据。
封装复用逻辑:通过使用函数封装可复用的逻辑,实现组件之间逻辑共享。
props 和 context
props 从父组件传递过来的属性会被放在props对象中,setup中需要使用时直接通过props参数获取:
context 也称之为SetupContext,包含三个属性:
attrs
:所有的非props的attributeslots
:父组件传递过来的插槽emit
:组件内部需要发出事件时会用到emit(因为我们不能访问this,所以不可以通过this.$emit发出事件)案例
总结:使用setup方法,函数的复用性、简洁性更强
作用
注意: 此时counter并不是响应式数据,因为对于一个定义的变量来说,默认情况下,Vue并不会跟踪它的变化,来引起界面的响应式操作
对传入类型有限制,必须是一个对象或者数组
一般用在复杂类型的数据,如账号密码
传入基本数据类型(String、Number、Boolean)时会有警告
<template>
{{state.name}}
{{state.counter}}
template>
import {reactive} from 'vue'
export default {
setup() {
const state = reactive({
name:'pyy',
counter: 100
})
return {
state
}
}
}
原理:
注意:不能随便对reactive做解构
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
})
readonly
:一般我们通过reactive
或ref
可以获取到一个响应式的对象。但某些情况下,我们传给其他组件的这个响应式对象希望在另外一个组件被使用,但不能被修改。
(总结:响应式对象在其他组件可以被使用,但不能修改)
readonly会返回原始对象的只读代理(也就是它依然是一个Proxy)
一般组件之间的数据传递要符合单项数据流的规范
数据传递给另一个组件时,只允许阅读,不允许修改,称为单向数据流
规范
(同理,react框架有个互通的知识点:react的使用是非常灵活的,但是它有一个重要的原则 —— 任何一个组件都应该像纯函数一样,不能修改传入的props)
方案对比
(旨在表达要修改数据的话,需要把子组件的事件发送到父组件,由父组件决定更改权,明白这个意思可以直接去下一段)(疑问:这不也是在子组件改的kobe?)
/.wrap2 <
/.box <
不规范的方式,(不能在子组件修改数据)
app.vue
<template>
<h2>父组件App: {{ info }}h2>
<show-info :info="info" >show-info>
template>
<script>
import { reactive } from 'vue'
import ShowInfo from './ShowInfo.vue'
export default {
components: {
ShowInfo
},
setup() {
// 本地定义多个数据, 都需要传递给子组件
const info = reactive({
name: "pyy",
age: 18,
})
return {
info
}
}
}
script>
showInfo.vue
<template>
<div>
<h2>ShowInfo: {{ info }}h2>
<button @click="info.name = 'kobe'">ShowInfo按钮button>
div>
template>
<script>
export default {
props: {
// reactive数据
info: {
type: Object,
default: () => ({})
},
}
}
script>
/->
/.box <
规范的方式,(需要在父组件修改数据)
app.vue
<template>
<h2>App: {{ info }}h2>
<show-info :info="info" @changeInfoName="changeInfoName">show-info>
template>
<script>
import { reactive} from 'vue'
import ShowInfo from './ShowInfo.vue'
export default {
components: {ShowInfo},
setup() {
// 本地定义多个数据, 都需要传递给子组件
const info = reactive({
name: "why",
age: 18,
})
// 接收改名字的方法
function changeInfoName(payload) {
info.name = payload
}
return {info,changeInfoName}
}
}
script>
showInfo.vue
<template>
<div>
<h2>ShowInfo: {{ info }}h2>
<button @click="showInfobtnClick">ShowInfo按钮button>
div>
template>
<script>
export default {
props: {
// reactive数据
info: {
type: Object,
default: () => ({})
},
},
emits: ["changeInfoName"],
// (1)中表明了setup有两个参数 props、context
setup(props, context) {
function showInfobtnClick() {
// 不能通过this.emit发送数据
context.emit("changeInfoName", "kobe")
}
return {
showInfobtnClick
}
}
}
script>
/->
/->
方案对比
/.wrap2 <
/.box <
单项数据流
写法,(即 上方右侧的规范写法)
app.vue
<template>
<show-info :info="info"
@changeInfoName="changeInfoName">
show-info>
template>
<script>
import { reactive } from 'vue'
import ShowInfo from './ShowInfo.vue'
export default {
components: {
ShowInfo
},
setup() {
// 本地定义多个数据, 都需要传递给子组件
const info = reactive({
name: "why",
age: 18,
})
// 接收改名字的方法
function changeInfoName(payload) {
info.name = payload
}
return {
info,
changeInfoName
}
}
}
script>
showInfo.vue
<template>
<div>
<h2>ShowInfo: {{ info }}h2>
<button @click="showInfobtnClick">ShowInfo按钮button>
div>
template>
<script>
export default {
props: {
// reactive数据
info: {
type: Object,
default: () => ({})
},
},
emits: ["changeInfoName"],
setup(props, context) {
function showInfobtnClick() {
context.emit("changeInfoName", "kobe")
}
return {
showInfobtnClick
}
}
}
script>
/->
/.box <
readonly
写法
app.vue
<template>
<show-info :roInfo="roInfo"
@changeRoInfoName="changeRoInfoName">
show-info>
template>
<script>
import { readonly } from 'vue'
import ShowInfo from './ShowInfo.vue'
export default {
components: {
ShowInfo
},
setup() {
// 本地定义多个数据, 都需要传递给子组件
const info = reactive({
name: "why",
age: 18,
})
// 使用readOnly包裹info
const roInfo = readonly(info)
function changeRoInfoName(payload) {
info.name = payload
}
return {
roInfo,
changeRoInfoName
}
}
}
script>
showInfo.vue
<template>
<div>
<h2>ShowInfo: {{ roInfo }}h2>
<button @click="roInfoBtnClick">roInfo按钮button>
div>
template>
<script>
export default {
props: {
// readonly数据
roInfo: {
type: Object,
default: () => ({})
}
},
emits: ["changeRoInfoName"],
setup(props, context) {
function roInfoBtnClick() {
context.emit("changeRoInfoName", "james")
}
return {
roInfoBtnClick
}
}
}
script>
/->
/->
总结:在编写代码时遵循单项数据流
原则(规范),也可以不使用readonly
,readonly是vue3有的
(疑问:这也是在子组件修改的数据kobe啊,只是在父组件调用子组件的修改数据代码,感觉该案例并没有做到我理解的统一在父组件修改数据)
看完视频仍有些懵懵懂懂
readonly的理解
在Vue3中,readonly
是一个响应式数据的修饰符,用于创建一个只读的响应式数据。它可以应用于普通的JavaScript对象、数组或者Map。
使用readonly修饰符后,被修饰的数据将变成只读的,即不能通过赋值的方式改变其值。这样做的好处是,当试图修改只读数据时,Vue会发出警告,以防止意外的数据变更引起的问题。
以下是一个使用readonly的示例:
import { readonly } from 'vue';
const data = readonly({
name: 'John',
age: 25
});
console.log(data.name); // 输出: "John"
data.name = 'Bob'; // 试图修改只读数据,将触发警告
在上面的示例中,我们使用readonly创建了一个只读的响应式对象data。由于name属性是只读的,所以我们可以读取它的值,但是不能修改它。
深度冻结(deepFreeze)
需要注意的是,readonly只能确保直接修改属性的操作被拦截,如果属性值是一个对象或数组,仍然可以通过改变对象或数组内部的属性或元素来间接修改值。因此,如果需要完全禁止所有级别的修改,可以考虑使用深度冻结(deepFreeze)。
import { readonly, deepFreeze } from 'vue';
const data = readonly(deepFreeze({
name: 'John',
hobbies: ['reading', 'swimming']
}));
console.log(data.name); // 输出: "John" ⭐⭐
data.name = 'Bob'; // 试图修改只读数据,将触发警告 ⭐⭐
data.hobbies.push('coding'); // 试图修改只读数据,将触发警告
在上面的示例中,使用deepFreeze对data进行了深度冻结,这样任何级别的修改都将被拦截,确保了完全的只读性。
父子组件间的readonly
在Vue中,父子组件之间可以通过props来传递数据。如果你想在父组件中传递一个只读的数据给子组件,可以使用readonly修饰符。
下面是一个示例,展示了如何在父组件中使用readonly修饰符来传递只读的props给子组件:
/.wrap2 <
/.box <
父组件
<template>
<div>
<child-component :data="readonlyData">
child-component>
div>
template>
<script>
import { readonly } from 'vue';
import ChildComponent from './ChildComponent.vue';
export default {
components: {
ChildComponent
},
data() {
return {
data: {
name: 'John',
age: 25
}
};
},
computed: {
readonlyData() {
return readonly(this.data);
}
}
}
script>
在上面的示例中,父组件中的data数据被赋值给了readonlyData计算属性,并使用readonly修饰符将其变为只读数据。然后,将readonlyData作为props传递给子组件ChildComponent。
/->
/.box <
子组件 ChildComponent.vue 中,你可以像访问普通的props一样访问只读的props。子组件可以读取只读props的值,但不能修改它。
<template>
<div>
<p>Name: {{ data.name }}p>
<p>Age: {{ data.age }}p>
div>
template>
<script>
export default {
props: {
data: {
required: true, // 必填
type: Object
}
}
}
script>
在上面的子组件中,我们定义了一个名为data的props,类型为对象。我们可以在模板中直接访问data.name和data.age来显示父组件传递过来的只读props的值。
/->
/->
这样,父组件的数据将以只读的形式传递给子组件,子组件无法直接修改父组件的数据,从而确保了数据的安全性。
数据是父组件获取到的,设置只读的操作也是在父组件完成的,子组件负责接收并显示。
点击按钮修改readonly的值
/.wrap2 <
/.box <
父组件
<template>
<div>
<button @click="changeData">Change Databutton>
<child-component :data="readonlyData">child-component>
div>
template>
<script>
import { ref, readonly } from 'vue';
import ChildComponent from './ChildComponent.vue';
export default {
components: {
ChildComponent
},
setup() {
const data = ref({
name: 'John',
age: 25
});
const readonlyData = readonly(data);
const changeData = () => {
data.value.name = 'Alice'; // 修改只读数据
};
return {
readonlyData,
changeData
};
}
}
script>
/->
/.box <
子组件 ChildComponent.vue
<template>
<div>
<p>Name: {{ data.name }}p>
<p>Age: {{ data.age }}p>
div>
template>
<script>
export default {
props: {
data: {
required: true,
type: Object
}
}
}
script>
/->
/->
读取的是readonlyData
,修改的是data
。
Reactive判断的API
/–isProxy 检查对象是否是由reactive
或readonly
创建
/–isReactive 检查对象是否是由reactive
创建的响应式代理
/–. 如果该代理是readonly
建的,但包裹了由reactive
创建的另一个代理,它也会返回 true;
/–isReadonly 检查对象是否是由readonly
创建的只读代理
/–toRaw 返回reactive
或readonly
代理的原始对象(不建议保留对原始对象的持久引用。请谨慎使用)。
/–shallowReactive 创建一个响应式代理,它跟踪其自身 property 的响应性,但不执行嵌套对象的深层响应式转换 (深层还是原生对象)。
/–shallowReadonly 创建一个 proxy,使其自身的 property 为只读,但不执行嵌套对象的深度只读转换(深层还是可读、可写的)。
以上函数的使用方法:如第一个 isProxy(info)
toRefs
我们对reactivef返回值进行解构获取值,那么之后无论是修改结构后的变量,还是修改reactive返回的state对象,数据都不再是响应式的:
toRefs函数的作用是将一个响应式对象转换为一个普通的对象,该对象的每个属性都是一个只读的响应式引用。这个函数在将响应式对象传递给子组件时特别有用,以确保子组件能够访问到父组件的响应式数据。(简单说:将一个响应式对象转换为只读的响应式对象)
以下是一个使用toRefs的示例:
<template>
<div>
<button @click="changeData">Change Databutton>
<child-component v-bind="readonlyData">child-component>
div>
template>
<script>
import { ref, toRefs } from 'vue';
import ChildComponent from './ChildComponent.vue';
export default {
components: {
ChildComponent
},
setup() {
const data = ref({
name: 'John',
age: 25
});
const readonlyData = toRefs(data); // ⭐⭐ 将一个响应式对象转换为只读的响应式对象
const changeData = () => {
data.value.name = 'Alice';
};
return {
readonlyData,
changeData
};
}
}
script>
在上面的代码示例中,我们使用toRefs将data对象转换为只读的响应式引用对象readonlyData。然后,我们通过v-bind将readonlyData对象的属性传递给子组件,以便子组件能够访问和响应父组件的数据变化。
toRef
toRef函数的作用是将一个响应式对象的 某个属性 转换为只读 的响应式引用。这在一些特定的场景下很有用,例如将父组件的响应式数据传递给子组件时,可以使用toRef创建只读的引用,以确保子组件不能修改父组件的数据。
以下是一个使用toRef的示例:
<template>
<div>
<button @click="changeData">Change Databutton>
<child-component :data="readonlyName">child-component>
div>
template>
<script>
import { ref, toRef } from 'vue';
import ChildComponent from './ChildComponent.vue';
export default {
components: {
ChildComponent
},
setup() {
const data = ref({
name: 'John',
age: 25
});
const readonlyName = toRef(data, 'name'); // ⭐⭐ 将一个响应式对象的 某个属性 转换为只读的响应式引用
const changeData = () => {
data.value.name = 'Alice';
};
return {
readonlyName,
changeData
};
}
}
script>
在上面的代码示例中,我们使用toRef将data.value.name属性转换为只读的响应式引用readonlyName。然后,我们通过:data将readonlyName传递给子组件,以便子组件能够访问父组件的只读数据。
/–unref 如果参数是一个ref,则返回内部值,否则返回参数本身
/–. unref 是val = isRef(val) ? val.value : val
的语法糖
/–isRef 判断值是否是一个ref对象
/–shallowRef 创建一个浅层的ref对象;
/–triggerRef 手动触发和 shallowRef 相关联的副作用
/–. (没看懂,反正意思是强制触发一个响应式引用的重新渲染,举个例子)
<template>
<div>
<p>{{ message }}p>
<button @click="changeMessage">Change Messagebutton>
<button @click="forceRender">Force Renderbutton>
div>
template>
<script>
import { ref, triggerRef } from 'vue';
export default {
setup() {
const messageRef = ref('Hello, Vue 3'); // 创建响应式数据
const changeMessage = () => {
messageRef.value = 'Hello, MOSSAI'; // 点击第一个按钮后数据发生变化
};
// ⭐⭐
const forceRender = () => { // 点击第二个按钮后强制刷新messageRef的渲染值
triggerRef(messageRef); // 即使其值没有发生变化
}; // 我们可以在需要的时候手动触发响应式数据重新渲染,而不必等待其发生实际的变化
return {
message: messageRef,
changeMessage,
forceRender
};
}
}
script>
当我们没有使用ref
或者reactive
定义数据时,修改数据并不是响应式的,此时我们就可以在修改数据的代码下加上triggerRef
,渲染定义的数据
不可以使用this是因为组件实例还没有被创建出来的说法是错误的,正确的说法是原为源码中并没有绑定this
绑定this的方法有:
apply/call、fn.bind()、instance.fn()
视频位置P1049
在Options Api中,我们是使用computed选项来完成
在Composition Api中,我们可以在setup函数中使用computed方法来编写计算属性
computed方法案例
<template>
<h2>{{ fullname }}h2>
<button @click="setFullname">设置fullnamebutton>
<h2>{{ scoreLevel }}h2>
template>
<script>
import { reactive, computed, ref } from 'vue'
export default {
setup() {
// 1.定义数据
const names = reactive({
firstName: "kobe",
lastName: "bryant"
})
// ⭐⭐ 拼接字符串
const fullname = computed(() => {
return names.firstName + " " + names.lastName
})
// 2.⭐⭐ 判断是否及格
const score = ref(89)
const scoreLevel = computed(() => {
return score.value >= 60 ? "及格": "不及格"
})
return {
names,
fullname,
scoreLevel
}
}
}
script>
当你给他写函数的时候,本质上是在写它的get语法(复杂,可以略过)
const fullname = computed(() => {
return names.firstName + " " + names.lastName
})
// 修改成get set语法
const fullname = computed({
set: function(newValue) {
const tempNames = newValue.split(" ")
names.firstName = tempNames[0]
names.lastName = tempNames[1]
},
get: function() {
return names.firstName + " " + names.lastName
}
})
console.log(fullname)
function setFullname() {
fullname.value = "coder why"
console.log(names)
}
return {
fullname,
setFullname,
scoreLevel
}
vue3中已经不再使用options里相关的生命周期
只需要定义一个ref对象,绑定到元素或者组件的ref属性上即可
代码示例
<template>
<h2 ref="titleRef">我是标题h2>
<button @click="getElements">获取元素button>
template>
<script>
import { ref } from 'vue'
export default {
setup() {
const titleRef = ref()
console.log(titleRef.value) // ⭐⭐ undefind 此时获取不到元素
// 因为此时setup内还没有挂载titleRef
function getElements() {
console.log(titleRef.value) // ⭐⭐ 此时可以获取到元素,已经挂载
} // 我是标题
return {
titleRef,
getElements
}
}
}
script>
生命周期
onMounted
挂载后
/.wrap2 <
/.box lit6 <
app.vue
<template>
<h2 ref="titleRef">我是标题h2>
<button ref="btnRef">按钮button>
<show-info ref="showInfoRef">show-info>
<button @click="getElements">获取元素button>
template>
<script>
import { ref, onMounted } from 'vue'
import ShowInfo from './ShowInfo.vue'
export default {
components: {
ShowInfo
},
setup() {
const titleRef = ref()
const btnRef = ref()
const showInfoRef = ref() // ⭐⭐
// ⭐⭐ mounted的生命周期函数
onMounted(() => {
console.log(titleRef.value)
console.log(btnRef.value)
console.log(showInfoRef.value) // ⭐⭐ 输出一个proxy
showInfoRef.value.showInfoFoo() // ⭐⭐ 调用组件showInfoRef里的showInfoFoo方法
})
function getElements() {
console.log(titleRef.value) // ⭐⭐ 挂载后,手动点击按钮去获取的
}
return {
titleRef,
btnRef,
showInfoRef,
getElements
}
}
}
script>
/->
/.box lit4 <
ShowInfo.vue
<template>
<div>ShowInfodiv>
template>
<script>
export default {
// vue2写法
methods: {
showInfoFoo() {
console.log("showInfo foo function")
}
}
// vue3写法
setup() {
function showInfoFoo() {
console.log("showInfo foo function")
}
return {
showInfoFoo
}
}
}
script>
/->
/->
setup可以用来替代data、methods、computed等等选项,也可以替代生命周期钩子
写法对比
/.wrap2 <
/.box <
/->
/.box <
/->
/->
完整对比
/.wrap2 <
/.box <
vue2
new Vue({
beforeCreate() {
// 实例初始化后
},
created() {
// 实例创建完成后
},
beforeMount() {
// 挂载前
},
mounted() {
// 挂载后
},
beforeUpdate() {
// 数据更新前
},
updated() {
// 数据更新后
},
beforeDestroy() {
// 实例销毁前
},
destroyed() {
// 实例销毁后
}
})
/->
/.box <
vue3
import { onBeforeCreate, onCreated, onBeforeMount,
onMounted, onBeforeUpdate, onUpdated,
onBeforeUnmount, onUnmounted } from 'vue'
createApp({
setup() {
onBeforeCreate(() => {
// 实例初始化后
})
onCreated(() => {
// 实例创建完成后
})
onBeforeMount(() => {
// 挂载前
})
onMounted(() => {
// 挂载后
})
onBeforeUpdate(() => {
// 数据更新前
})
onUpdated(() => {
// 数据更新后
})
onBeforeUnmount(() => {
// 实例销毁前
})
onUnmounted(() => {
// 实例销毁后
})
}
}).mount('#app')
/->
/->
表格对比
vue2 | vue3 | 含义 | 备注 | |
---|---|---|---|---|
beforeCreate | setup setup是围绕beforeCreate、created运行的 所以不需要显式的定义它们 在这两个钩子中编写的任何代码都应该直接在setup函数中编写 |
实例初始化后 | 数据观测 (data observer) 和 event/watcher 事件配置之前被调用 | |
created | 实例创建后,被立即调用 | 此时实例已完成数据观测 (data observer),属性和方法的运算,watch/event 事件回调 | ||
beforeMount | onBeforeMount | 挂载前 | 此时模板编译/解析已完成,但尚未将组件挂载到DOM中 | |
mounted | onMounted | 挂载后 | 此时组件已经被挂载到DOM中 | |
beforeUpdate | onBeforeUpdate | 数据更新前 | 发生在虚拟DOM重新渲染和打补丁之前 | |
updated | onUpdated | 数据更新后 | 发生在虚拟DOM重新渲染和打补丁之后 | |
BeforeUnmount | onBeforeUnmount | 卸载前 | 此时实例仍然完全可用 | |
Unmounted | onUnmounted | 卸载后 | 调用后,所有事件监听器都会被移除,所有子实例都会被销毁 | |
activated | onActivated | 进入该组件 | keep-alive组件监听 | |
deactivated | onDeactivated | 退出该组件 | keep-alive组件监听 |
vue3不再事件总线的Provide/Inject API 而改为使用Provide/Inject函数
() => obj.address 是一个箭头函数,用于返回 obj.address 的值。
实际可用的案例
修改后,监听结果为flase,且能明确看到新旧值不一样的!!
watch(()=>({...info.friend}), (newValue, oldValue) => {
console.log(newValue, oldValue)
console.log(newValue === oldValue)
}, {
immediate: true,
deep:true
})
效果测试
监听值 | 监听到的新旧值变化了吗 | ===呢 |
---|---|---|
info | 新旧值显示的都是新值 | true |
info.friend | 新旧值显示的都是新值 | true |
()=>({…info}) | 新旧值显示的都是新值 | false |
()=>({…info.friend}) | 新值正确,旧值正确 | false |
设置条件停止调用
把相同逻辑的js代码抽取到同一个的文件夹(Hook)中
return里的...userCounter
等于setup里的 const { counter, increment, decrement } = useCounter()
...name
为语法糖写法
/.wrap2 <
/.box <
Hook文件夹新增useTitile.js
import { ref, watch } from "vue";
export default function useTitle(titleValue) {
// document.title = title
// 定义ref的引入数据
const title = ref(titleValue)
// 监听title的改变
watch(title, (newValue) => {
document.title = newValue // 拿到新值,更新标题
}, {
immediate: true
})
// 返回ref值
return {
title
}
}
/->
/.box <
第一种
<script>
import useTitle from './hooks/useTitle'
export default {
setup() {
function changeTitle() {
useTitle("app title")
}
return {
changeTitle,
}
}
}
</script>
/.wrap2 <
/.box <
useScrollPosition.js
import { reactive } from 'vue'
export default function useScrollPosition() {
// 1.使用reative记录位置
const scrollPosition = reactive({
x: 0,
y: 0
})
// 2.监听滚动
document.addEventListener("scroll", () => {
scrollPosition.x = window.scrollX
scrollPosition.y = window.scrollY
})
return {
scrollPosition
}
}
document.addEventListener(事件名, 函数)
,事件名是设置好的,有滚动、点击等
/->
/.box <
调用
<template>
<div class="scroll">
<h2>x: {{ scrollPosition.x }}h2>
<h2>y: {{ scrollPosition.y }}h2>
div>
template>
<script>
import useScrollPosition from '../hooks/useScrollPosition'
export default {
setup() {
const { scrollPosition } = useScrollPosition() // 这个是对return做解构
// const { x, y } = scrollPosition
// ↑ 这个才是对xy做解构,但不能对reactive做解构,所以我们不能这样做
console.log(scrollPosition)
return {
scrollPosition
}
}
}
script>
/->
/->
省去script里的setup(){}
和return{}
,只需要在script标签后加上setup,在script里直接写上相应的代码即可,也不需要再写return
vue3.2版本后正式支持该写法
完整案例
Props defineProps
emits defineEmits
看11.3
SPA(Single Page Application,单页应用程序)是一种前端开发模式,其中整个应用程序的所有页面和资源都在一个单独的HTML页面中加载,通过JavaScript来动态更新内容,而不是每次页面切换时重新加载整个页面。SPA通常使用AJAX技术(异步JavaScript和XML)从服务器获取数据,并使用前端框架(如React、Vue或Angular)进行页面路由和组件管理。
传统的多页应用程序(MPA)在用户导航时会重新加载整个页面,这可能会导致页面刷新的延迟和用户体验的不连贯。相比之下,SPA在初始加载后,只需加载一次HTML、CSS和JavaScript文件,然后通过异步请求数据和动态更新内容,有效地提供了更快的页面切换和更流畅的用户体验。
SPA的优势
SPA的劣势
然而,SPA也存在一些挑战和注意事项,如SEO问题(由于内容动态生成),初始加载时间(可能需要加载大量JavaScript代码)和内存管理(长时间运行的应用可能会导致内存泄漏)等。
总的来说
SPA是一种提供更好用户体验的前端开发模式,因其动态加载和更新内容的方式而受到广泛使用。
前端页面不需要再进行F5整体刷新,切换路由就好
模式 | # |
怎样的 | 优缺点 | 简单说 |
---|---|---|---|---|
hash | ✔,http://example.com/#/about |
当URL的哈希值改变时,页面不会重新加载,Vue会根据哈希值的变化来动态渲染对应的组件。 | 优:兼容性良好,即使在不支持HTML5历史API的旧版浏览器中也可以使用 | 有#,url的hash值改变时,页面不会重新加载,但是页面会根据新的hash值进行相应的内容渲染 |
history | ×,http://example.com/about |
路由的改变会使用HTML5的history API来修改URL,同时也会向服务器发送请求 | 优:URL更加美观,没有了#符号,更接近传统的URL,对搜索引擎优化也更友好。 缺:需要后端服务器支持,因为URL的路径在浏览器刷新时会发送给服务器,服务器需要配置相应的路由规则,以便正确地返回对应的页面。 |
无#,当切换路由时,URL会发生改变,但通过使用HTML5的history API来让页面不会重新加载, |
router.js
const router = new VueRouter({
// 方式一
mode: 'hash', // 或者 'history'
// 方式二
history: createWebHashHistory(), // 或者 'createWebHistory' ⭐
routes: [...]
})
(可略过,看表格)
通过监听URL的改变对URL和内容进行映射
URL的hash也就是锚点(#),本质上是改变window.location的href属性
hash的优势是兼容性更好,在老版本IE浏览器都可以运行,但是缺陷是有一个#,显得不像一个真实的链接
/–replaceState 替换原来的路径,跳转而不留下历史痕迹
/–pushState 使用新的路径
/–popState 路径的回退
/–go 向前或向后改变路径
/–forward 向前改变路径
/–back 向后改变路径
(chatgpt的使用方法)
// 在某个 Vue 组件的方法中
someMethod() {
this.$router.replace('/new-route'); // 使用 replaceState 进行页面跳转
this.$router.push('/new-route'); // 使用 push 进行页面跳转
this.$router.go(-1); // 使用 go 进行前进或后退
this.$router.back(); // 使用 back 进行后退
this.$router.forward(); // 使用 forward 进行前进
}
/.flexImg <
前端三大框架 及其路由
认识 Vue router
Vue router 是vuejs的官方路由,它与vuejs核心深度集成,让用vuejs构建单页应用变得非常容易
vue-router是基于路由和组件的,路由用于设定访问路径, 将路径和组件映射起来;在vue-router的单页面应用中, 页面的路径的改变就是组件的切换
使用VUE-Router
npm install vue-router
app.use(router)
占位首页
redirect
router/index.js
一般使用在给默认路径的重定向,(输入https://www.aaa.com/
后自动定位到home页面)
routes:[
// 可以这么写,但是一般用重定向的方法
{path:'/', component: Home},
// ⭐ redirect 重定向
{path:'/', redirect: "/home"}
]
()=>import('路径')
分包router/index.js
解释
优化性能的方式之一!打包后默认只有app.js一个逻辑文件,把app.js的代码块按组件划分成多个文件,使首屏加载速度更快一些
懒加载的含义:不用的时候不加载,用的时候再加载相应的js包
使用
打包命令npm run build
代码:router/index.js
// 以前用的是这种写法
import Home from '../Views/Home.vue'
import About from '../Views/About.vue'
// 现在的写法(一)
const Home = ()=>import(/* webpackChunkName:'home'*/'../Views/Home.vue') // webpackChunkName:'home' 为webpack3开始支持的魔法注释
const About = ()=>import(/* webpackChunkName:'about'*/'../Views/About.vue')
routes:[..........]
// 现在的写法(二) ⭐
routes:[
{
path:"/",
redirect:"home" // 首页重定向
},
{
path:"/home",
component: () => import(/* webpackChunkName:'home'*/"../Views/Home.vue")
},
{
path:"/about",
component: () => import(/* webpackChunkName:'about'*/"../Views/About.vue")
}
]
补充PPT解释
name
meta
router/index.js
{
path: "/home", // 网址路径
name: "home", // 独一无二的名字
component: () => import("../Views/Home.vue")
meta: { // 自定义数据
name: "pyy",
age: 88
}
},
$route.params.id
如http://localhost:8080/#/user/123
与 http://localhost:8080/#/user/321
同一个组件,显示不同的id
html
<router-link to="/user/123">(跳转链接)用户vueter-link>
<h2>(调用)user{{$route.params.id}}h2>
js
path:"/user/:id"
component: () => import("../Views/user.vue")
调用部分未写完整,补充:
// html与图片一致
// js 部分
import { userRoute } from 'vue-router'
const route = useRoute()
console.log(route.params.id)
onBeforeRouteUpdate
在切换到同一个组件时没有经历销毁和重新创建,所以在切换时上面的console.log(route.params.id)
并没有显示最新的值
这个方法获取不到
onActivation(() =>{
const route = useRoute()
console.log("代码中:",route.params.id)
})
这里我们使用onBeforeRouteUpdate
接动态路由部分,在vue文件使用,template相同,js部分
import { useRoute, onBeforeRouteUpdate } from 'vue-router'
// 加载进入页面时调用一次
const route = useRoute()
console.log(route.params.id)
// 后续发生变化时调用
onBeforeRouteUpdate((to, from) => {
console.log("from:", from.params.id)
console.log("to:", to.params.id)
})
onBeforeRouteUpdate 解释
官方:
/user/:id
,在/users/1
和/users/2
之间跳转BeforeRouteUpdate
onBeforeRouteUpdate
课件图
NotFound.vue
<template>
<div class="not-found">
<h2>NotFound: 您当前的路径{{ $route.params.pathMatch }}不正确, 请输入正确的路径!h2>
div>
template>
<script>
import { useRoute } from 'vue-router'
// 加载进入页面时调用一次
const route = useRoute()
console.log(route.params.pathMatch)
script>
{
path:'/:pathMatch(.*)',
component: () =>import('../pages/NotFound.vue')
}
其中有两种写法,是否解析/
写法 | 效果 |
---|---|
path:'/:pathMatch(.*)', |
user/hahaha/123 |
path:'/:pathMatch(.*)*' |
["user","hahaha","123"] |
chidren里的redirect
(重定向):当访问 /home
时,vue路由会自动重定向到 /home/reoduct
(看到chatgpt案例里省略了children重定向里的/home
)
这里Home.vue扮演着容器的角色,它负责渲染并包含子级路由的内容。父级组件可以用来布局整个页面的结构,提供共享的样式和功能,同时也可以在子级路由之间共享数据和状态。
src/router/index.js,路由嵌套省略
import { createRouter, createWebHashHistory } from 'vue-router' // 哈希模式
// createWebHistory history模式
const router = createRouter({
history:createWebHashHistory(), // 哈希模式
routes: [
{
path: "/", // 网址路径
redirect: "/home" // 重定向,输入网址后直接进入home页面
},
{
path: "/home", // 网址路径
name: "home", // 独一无二的名字
// 指定文件路径,并做分包处理,包的名称为home
component: () => import(/* webpackChunkName:'home'*/"../Views/Home.vue")
meta: { // 自定义数据
name: "pyy",
age: 88
}
},
{
path:"/user/:id" // id是传过来的值
component: () => import("../Views/user.vue")
},
{
path:'/:pathMatch(.*)', // 找不到路径,pathMatch匹配路径,.*匹配上面没有的任何东西
component: () =>import('../pages/NotFound.vue')
}
]
})
<template>
<router-link to="/home">首页vueter-link>
<router-link to="/about">关于vueter-link>
<router-link to="/user/123">(跳转链接)用户vueter-link>
<router-view>router-view>
<h2>user:{{$route.params.id}}h2>
<h2>NotFound: 您当前的路径{{ $route.params.pathMatch }}不正确, 请输入正确的路径!h2>
template>
<script>
import { useRoute, onBeforeRouteUpdate } from 'vue-router'
// 加载进入页面时调用一次
const route = useRoute()
console.log(route.params.id)
// 后续发生变化时调用
onBeforeRouteUpdate((to, from) => {
console.log("from:", from.params.id)
console.log("to:", to.params.id)
})
script>
/–to 跳转到,填写一个字符串,或者是一个对象(如::to="{path:'/home'}"
)
/–replace 替换,当点击时,会调用 router.replace(),而不是router.push(),这样点击后退时就退不回上一页
/–active-class 设置激活a元素后应用的class,默认选中的class为router-link-active
,自定义方式:active-class='activeName'
/–exact-active-class 链接精准激活时,应用于渲染的class,默认是router-link-exact-active
方式一:路由跳转,
方式二:click事件跳转 >> .push
往上叠加,可后退
.replace
替换,后退直接回到原始页面
router.push()
叠加形式的跳转,router.replace
替换原页面的跳转形式
router.push("/home")
router.push({ })
path: " "
页面路径name: " "
名称,因为在首页使用时没有重定向功能,所以推荐全用pathquery: { }
放一些需要传递的对象,网址上会附带<span @click="homeSpanClick">首页span>
<button @click="aboutBtnClick">关于button>
<script setup>
import { useRouter } from 'vue-router' // 调用方法
const router = useRouter() // 函数赋给router
// 监听元素的点击
function homeSpanClick() { // 跳转到首页
router.push("/home") // 直接跳转过去
}
function aboutBtnClick() { // 跳转到关于
router.push({ // 用对象方式,加点值
path: "/about", // 也可以用name: "home",但因为在首页使用时没有重定向功能,所以推荐全用path
query: { // 用该属性带上点值
name: "why",
age: 18
}
})
}
script>
about.vue 获取 query 数据
<h2>About: {{ $route.query }}h2>
back 返回,forward 前进,go(number) 前进后退几步
<template>
<div class="about">
<button @click="backBtnClick">返回button>
div>
template>
<script setup>
import { useRouter } from 'vue-router'
const router = useRouter()
function backBtnClick() {
router.back() // 后退一步 相当于 router.go(-1)
router.forward() // 向前一步 相当于 router.go(1)
}
script>
addRoute
两种添加菜单的方法
使用router里的addRoute
方法
是不是某个角色 >> 是,注册这些路由
>> 不是,下一个角色是不是
route/index.js
const router = createRouter({
history: createWebHashHistory(), // 采用哈希模式,有#
routes: [] // 省略
})
// 正常为登录页输入账号密码,获取到用户身份
let isAdmin = true // 这里省略,直接判断用户是不是isAdmin
if (isAdmin) {
// 这里添加的是一级路由
router.addRoute({
path: "/admin",
component: () => import("../Views/Admin.vue")
})
// 这里添加的是二级路由
router.addRoute("home",{ // 父级路由为home
path:"vip",
component: () => import("../Views/HomeVip.vue")
})
}
以下为 /admin页面 的效果
/.wrap2 <
/.box <
为true时
/->
/.box <
为flase时
/->
/->
ppt的addRoute
一般不用这个功能,了解一下
router.addRoute({ path:'/about', name: 'about', component: About })
// 这将会删除之前已经添加的路由,因为它们具有相同的名字,且名字必须时唯一的
router.addRoute({ path:'/other', name: 'about', component: Home })
removeRoute
方法,传入路由的名称router.addRoute({ path:'/about', name: 'about', component: About })
// 删除路由
router.removeRoute('about')
addRoute
方法的返回值回调const removeRoute = router.addRoute(routeRecord)
removeRoute() // 删除路由如果存在的话
(不太懂略过,第一行会返回一个函数,第二行调用函数时会把路由删掉)
router.hasRoute()
检查路由是否存在
router.getRoutes()
获取一个包含所有路由记录的数组
vue-router 提供的导航守卫主要目的:通过跳转或取消的方式守卫导航
beforeEach
使用全局的前置导航守卫 beforeEach ,有两个参数,三种返回值
to
到哪去,return ‘/xxx’ 时默认是toform
从哪来无返回
/ undefined
进行默认导航false
取消当前导航路由地址
(string
或对象
)next
(不推荐使用)
进入订单页面:
文件: router > index.js
// 每次跳转页面时,都会调用 router.beforeEach
router.beforeEach((to, from) => { // to 去到哪一页,from 从哪一页来
// 1.进入到任何别的页面时, 都跳转到login页面
if (to.path !== "/login") { // if(去的不是登录页)
return "/login"
} // 不做判断直接return login的话会发生死循环,而且直接这么写是不成功的,需要加上判断
// 2.进入到订单页面时, 判断用户是否登录
const token = localStorage.getItem("token") // 获取登录信息
if (to.path === "/order" && !token) { // 进入订单页面 且 未登录
return "/login"
}
})
登录页 login.vue
<template>
<div class="login">
<h2>登录页面h2>
<button @click="loginClick">登录button>
div>
template>
<script>
import { useRouter } from 'vue-router'
function loginClick(){
// 点击登陆后,向服务器放送请求,服务器会返回token,需要保存token
localStorage.setItem("token","sdf4a65s1fas564gsdf")
// 跳转到order页面
router.push("/order")
}
script>
退出页 Home.vue
<template>
<div class="home">
<h2>首页h2>
<button @click="logOutClick">退出登录button>
div>
template>
<script>
function logOutClick(){
// 点击后删除token
localStorage.removeItem("token")
}
script>
占位
后台管理系统完整的登录流程,后面再说
导航守卫官网
完整的导航解析流程
beforeRouteLeave
守卫beforeEach
守卫⭐beforeRouteUpdate
守卫(2.2+)⭐beforeEnter
beforeRouteEnter
beforeRouteEnter(next){this -> 目前还获取不到组件实例}
)beforeRoutesolve
守卫(2.5+)(异步组件解析之后,在跳转之前)afterEach
钩子DOM
更新 (template -> DOM更新)beforeRouteEnter
守卫中传给next
的回调函数
,创建好的组件实例会作为回调函数的参数传入
beforeRouteEnter(to,from,next){ next(vm -> {}) }
)以前用Vuex(vuex4),现在用Pina(vuex5)
认识应用状态管理
在开发中,我们会的应用程序需要处理各种各样的数据,这些数据需要保存在我们应用程序中的某一个位置,对于这些数据的管理我们就称之为是状态管理。
之前管理状态
复杂的状态管理
PPT介绍
手动安装
npm install vuex
src/store/index.js中
import { createStore } from 'vuex'
const store = createStore({
// 创建状态
state:() => ({ // {}外加() 表示返回一个对象
a = 1
})
// 创建相关的方法
mutations:{
fangfa(state){
state.a++
}
}
})
export default store
vue3文件
{{ $store.state.counter }}
直接调用import { useStore } from 'vuex'
const store = useStore()
function fangfa(){
console.log(store.state.counter) // 看看不改,注意这个复制给变量时,不是响应式的
store.commit("increment") // 就是要改的时候,调用一下 ⭐
}
{{counter}}
const counter = toRefs(store.state.counter) // 只读、单个数据的、响应式数据
vue2文件(options api)
{{ storeCounter }}
(名字是自定义的)export default {
computed: {
stroreCounter() {
return this.$store.state.counter
}
}
}
(options api没写在这一块,直接看简单说明就好)
src/store/index.js 创建状态
import { createStore } from 'vuex'
const store = createStore({
// 1.箭头函数写法
state:() => ({ // {} 加上 ({}) 表示返回一个对象,解构的知识
counter: 0
})
// 2.普通写法
// state(){ return { } }
// 3.定义mutations对象,在此定义状态的修改方法,
mutations:{
increment(state){
state.counter++
}
}
})
// 导出store
export default store
home.vue 使用方法
{{ $store.state.counter }}
,它是响应式变化的<button @click="increment">+1button>
<script>
import { useStore } from 'vuex'
const store = useStore()
function increment(){
store.state.counter++ // 这样是不规范的,不可以这样做
store.commit("increment") // vuex中这样才规范
}
script>
跟上面一样
chatgpt
假设你正在构建一个购物车应用,其中包含商品列表、购物车中的商品数量以及用户信息等数据。在使用Vuex时,你可以将这些数据存储在Vuex的单一状态树中。
在Vuex的store中,你可以定义一个状态对象,例如:
const store = new Vuex.Store({
state: {
products: [], // 商品列表
cart: [], // 购物车中的商品
user: null // 用户信息
},
mutations: {},
actions: {},
getters: {}
});
在上述例子中,state对象包含了商品列表、购物车和用户信息。这些数据可以被多个组件共享和访问。
例如,你可以在商品列表组件中获取商品列表:
export default {
computed: {
products() {
return this.$store.state.products;
}
},
// ...
};
同时,在购物车组件中,你可以获取购物车中的商品数量:
export default {
computed: {
cartCount() {
return this.$store.state.cart.length;
}
},
// ...
};
通过单一状态树,你可以方便地在不同的组件中访问和修改这些数据,而不需要通过props或事件来传递数据。
当你在某个组件中修改了状态,例如向购物车中添加了一个商品,你可以使用mutations来更新状态:
const store = new Vuex.Store({
state: {
// ...
},
mutations: {
addToCart(state, product) {
state.cart.push(product);
}
},
// ...
});
组件中可以通过调用mutations来更新状态:
this.$store.commit('addToCart', product);
这样,所有订阅了状态的组件都可以立即响应状态的变化。
这只是一个简单的例子,但希望能够帮助你理解如何使用Vuex的单一状态树来管理应用的状态。在实际应用中,你可以根据具体需求定义更多的状态和相应的操作。
在tamplate中使用{{ $store.state.counter }}
不太简洁,所以我们使用映射的方法来简化{{ }}
的内容
几种方法
(注意:incrementLevel()方法这么写是不对的,只是为了快速参考)
因为mapState在vuex中不好用,所以我们使用最后一种
代码
<template>
<h2>{{name}}h2>
<h2>{{level}}h2>
template>
<script> // vue2 options api
import { mapState } from 'vuex'
export default {
computed: {
...mapState({ // 直接结构
name: state => state.name,
level: state => state.level
})
}
}
script>
<script setup> // vue3 composition api
import { useStore } from 'vuex'
const store = useStore()
<script>
const { name, level } = toRefs(store.state) // const {顺便解构} = 响应式(state)
// 解构 起别名,给个默认值
// const { name: sName, level: level = 0 }
script>
主要有以下几种方法
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 项目名”即可创建项目。
npm install @vue-cli -g
不行时用 npm install -g @vue/cli
vue --version
vue create 项目名称
,(底层打包工具是 webpack)
启动服务 npm run server
,更改了配置文件得重新启动服务
打开项目 http://localhost:8080/
(底层打包工具是vite,越来越流行,打包效率高?待核实)
(来自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 项目名称
。
/–>1%
市场占有率大于1%
last 2 versions
支持最后两个版本
not dead
还在维护的
not ie 11
非ie11
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/*"
]
},
vetur:推荐在 vue2 使用 ✗
volar:推荐在vue3使用,目前比较好用 ⭐
.title { }
,会使底下其他组件的.title { }
生效,为不影响他们,添加
生成自己的作用域proxy 代理对象,解释:《一篇彻底理解Proxy - LBJ》
意思是把前面html页面中id='app进行了染
ES6语法,它是一种方便的方式来从数组或对象中提取值并将其赋给变量。
解构可以用于数组和对象
解构数组
const arr = [1, 2, 3];
const [a, b, c] = arr; // 开始解构
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3
解构对象
const obj = { x: 1, y: 2 };
const { x, y } = obj; // 开始解构
console.log(x); // 1
console.log(y); // 2
保存token localStorage.setItem("token","pyy")
调用token const token = localStorage.getItem("token")
删除token localStorage.removeItem("token")
分析
created(){}
生命周期回调函数:
当前的组件被创建时自动执行一般在该函数中, 会进行网络请求
该高阶仅有一句话,可省略为
+
/ -
看 5.2 (5) 通信案例(二)课程提供的综合案例
简短的切换案例
视频1063~1068
(css解释,margin左右8px,导致文字与图片没有对齐,那就在整体的inner处-8px,让div外扩16px,达到对齐效果)
(模拟网络请求:import并不是请求网络数据,而是导入的本地数据,这里模拟的是:刚刚进入网页时加载空对象ref({}),一秒后获取数据;因为import then返回的是export default对象,所以要.default)
回到7.10