官方文档:https://cn.vuejs.org/guide/essentials/event-handling.html
Vue(发音为 vju:/,类似 view) 是一款用于构建用户界面的JavaScript 框架。
它基于标准 HTML、CSS 和JavaScript 构建,并提供了一套声明式的、 组件化的编程模型,帮助你高效地开发用户界面。
无论是简单还是复杂的界面,Vue 都可以胜任.
渐进式框架
Vue 是一个框架,也是一个生态。其功能覆盖了大部分前端开发常见的需求。但 Web 世界是十分多样化的,
不同的开发者在 Web 上构建的东西可能在形式和规模上会有很大的不同。考虑到这一点,Vue 的设计非常注重灵活性和“可以被逐步集成”这个特点。
根据你的需求场景,你可以用不同的方式使用 Vue:
简单的来说就是vue可以是只使用一个页面,也可以是整个项目,所以叫渐进式框架,这个点有点有意思,现在大多是用于全部前端。
现在用的比较多的是vue2和vue3,vue3涵盖了vue2的知识,所以直接使用vue3不学习vue2是没有关系的。
如何判断vue的版本?在vue项目中的package.json 中会声明vue的版本号
<script>
export default {
data() {
return {
count: 0
}
}
methods: {
increment() {
this.count++
}
},
mounted(){
console.log(`The initial count is ${this.count}.`)
}
script>
<template>
<button @click="increment">选项式API: Count is: f count button>
template>
npm init vue@latest这个命令的作用是使用最新版本的Vue框架快速初始化一个Vue项目。它会执行以下几件事情:
1. 使用npm创建一个新的文件夹,名称默认为当前目录名。
2. 在这个文件夹内初始化一个npm项目,生成package.json文件。
3. 安装最新版本的Vue和相关依赖包作为开发依赖。
4. 创建基础的Vue代码结构和文件,例如main.js、App.vue等。
5. 根据项目类型预设好webpack或Rollup的配置文件。
6. 创建一个GIT仓库并做首次提交。所以使用这个命令可以非常快速地初始化一个标准的Vue项目设置,不需要自己搭建Vue开发环境和项目脚手架。它npm init vue@latest使用了Vue官方的`@vue/cli`服务,可以确保初始化的项目结构和依赖是官方推荐的最佳实践。
非常适合快速创建一个Vue项目的原型或示例来学习和试用Vue。
这里创建以后会有各种提示让我们输入项目名等选项,如下
上图提示,需要运行npm install 和 npm run dev。那这两个命令是干什么的呢
npm install :npm是管理软件包的命令,执行npm install 并不是安装npm,
而是会根据项目里的package.json中声明的vue项目依赖的软件包然后去下载对应软件。此外npm还可以用来管理代码的版本,对
vue项目进行打包等操作,所以后面的命令就是将vue项目按dev环境进行打包。
这里npm是从国外下载包会很慢,有两种提速的方式,一种是使用cnpm 进行下载,这个得单独安装,这里的源都是国内的,另一种是直接
更改远程仓库,更改远程仓库即使npm下载也很快
npm config set registry https://registry.npm.taobao.org/
npm install 在执行就很快了
npm run dev:运行package.json中的dev指令,一般使用dev指令编译测试环境信息
当展示如下信息时表示我们打包dev环境已经成功了:
下面可以直接访问图中的地址了:http://localhost:5173/
当页面展示如上内容说明我们已经打包部署成功了,这是一个可以用来做为开发的vue环境了。
vue是一种基于html模板语法,使我们能够声明式的将其组件实例的数据绑定到呈现的DOM上,所有的VUE模板都是语法层面合法HTML,可以被符合规范的浏览器和html解析器解析。
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
<script>
script>
<template>
template>
文本插值是最近本的数据绑定形式使用双花括号进行数据绑定,如下:
<script>
// export 用以导出组件,defalut在一个.vue中只能有一次,支持其他地方的import,vue里不能省略该模块,因为会在main.js中导入这个App.vue
export default{
// export导出模块中data()为常用的函数,主要用来做数据渲染。
// data(){
// return {
// msg:"文本插值显示的值2"
// }
// }
// 下面这种写法也是支持的,类似于java里的函数式编程,可以默认进行类型推导,只有一个返回值可以不写return
// 一个export中只能有一个data,这是不同的写法,都是支持的
data: ()=>({
msg:"文本插值显示的值4",
msg2:"第二个文本插值参数5"
})
}
script>
<template>
<h3>模板语法h3>
<p>{{ msg }}p>
<P>{{msg2}}P>
template>
{{}} 会将值解析为纯文本,支持哪些值的填充?
正常使用data函数声明的变量都是支持使用{{}}进行获取的
支持三目运算法
支持加减乘除,支持字段转化,支持一切可以形成终态的态度
不支持在内部进行新属性新方法的声明,不支持if(true)等
其实只要是单行的表达式都是支持的,换行就不行了
下面是一些支持的示例:
<script>
// export 用以导出组件,defalut在一个.vue中只能有一次,支持其他地方的import,vue里不能省略该模块,因为会在main.js中导入这个App.vue
export default{
// 下面这种写法也是支持的,类似于java里的函数式编程,可以默认进行类型推导,只有一个返回值可以不写return
// 一个export中只能有一个data
data:()=>({
msg:"文本插值1",
msg2:"文本插值2",
number:10,
flag:true,
text:"我是谁"
})
}
script>
<template>
<h3>模板语法h3>
<p>{{ msg }}p>
<P>{{ msg2 }}P>
<p>{{ number+10 }}p>
<p>{{ flag?'正确':'错误' }}p>
<p>{{ text.split('').reverse().join('') }}p>
template>
下面是运行展示:
注意1:若是data中声明的变量中包含html信息,{{}}默认是不解析为html的,而是解析成纯文本,若是想要解析成html信息,需要在html元素中增加 v-html属性,然后指向我们声明的变量,如下(不止p支持v-html属性,v-html也不是只支持a标签,其他标签同样是支持的),最终的结果是v-html中的变量信息会被解析成当前标签内的子标签信息
<script>
// export 用以导出组件,defalut在一个.vue中只能有一次,支持其他地方的import,vue里不能省略该模块,因为会在main.js中导入这个App.vue
export default{
// 下面这种写法也是支持的,类似于java里的函数式编程,可以默认进行类型推导,只有一个返回值可以不写return
// 一个export中只能有一个data
data:()=>({
msg:"文本插值1",
msg2:"文本插值2",
number:10,
flag:true,
text:"我是谁",
tiao:"点击跳转百度"
})
}
script>
<template>
<h3>模板语法h3>
<p>{{ msg }}p>
<P>{{ msg2 }}P>
<p>{{ number+10 }}p>
<p>{{ flag?'正确':'错误' }}p>
<p>{{ text.split('').reverse().join('') }}p>
<h1 v-html="tiao">h1>
<P v-html="tiao">P>
template>
vue的文件是可以被其他vue文件导入的,有点类似于html里的功能。引入其他html文件使用import,引入后还需要再模板模块指定如何使用这个页面。
<script setup>
import HelloWorld from './components/HelloWorld.vue'
script>
<template>
<HelloWorld/>
template>
这样HelloWorld.vue中的内容就默认被集成在了当前的vue文件中了,我们开发可以都在HelloWorld.vue中进行。
这里的属性绑定是指,html的属性绑定。我们若是像如下操作是可以的
<script>
export default{
data:()=>({
msg:"我是文本插值"
})
}
script>
<template>
<P>{{ msg }}P>
template>
但是若是msg如果想要是html的一个属性,则vue是无法正常识别的,这就需要使用别的功能了,如下是不可行的。
<script>
export default{
data:()=>({
msg:"我是文本插值"
})
}
script>
<template>
<P>{{ msg }}P>
<P calss="{{ msg }}">测试使用原始文本插值进行属性绑定P>
template>
前端的debug可以看到源码是这样的:
可以看到我们的class这个属性并没有能成功绑定到我们的变量msg。
那该如何做到属性绑定呢?VUE中提供了v-bind,前面还有一个v-html是用来将变量的html属性能正常解析出来的,如下所示:
<script>
export default{
data:()=>({
msg:"我是文本插值",
testClass:"testclass",
testId:"testId"
})
}
script>
<template>
<P>{{ msg }}P>
<P calss="{{ msg }}">测试使用原始文本插值进行属性绑定P>
<p v-bind:id="testId" v-bind:class="testClass">测试p>
template>
注意变量进行属性绑定时,是不需要写双括号的,他的写法也比较简单直观:v-bind:+属性=“变量名” 直接在属性中填写变量名即可,如果绑定的属性是null或者undefined则vue会将该属性移除。验证结果如下:
v-bind:+属性=“变量名” 还支持进行简写,简写写法 : :+属性=“变量名” 属性绑定不仅支持单个变量的绑定,还支持整个对象的绑定,如下我们可以直接将一个对象使用v-bind="对象"来给到html标签
<script>
export default{
data:()=>({
msg:"我是文本插值",
testClass:"testclass",
testId:"testId",
attributeObject:{
zhangsan:"张三",
lisi:"李四"
}
})
}
script>
<template>
<P>{{ msg }}P>
<P calss="{{ msg }}">测试使用原始文本插值进行属性绑定P>
<p :id="testId" v-bind:class="testClass">测试p>
<p v-bind="attributeObject">测试对象绑定到html属性p>
template>
运行的截图如下,这里的zhangsan和lisi都不是正经的p标签属性,这里只是用来测试,真正场景中可以使用id,class,title等。
就是vue中也提供了条件判断的语句,有如下几种:
<script>
export default{
data:()=>({
element:"D"
})
}
script>
<template>
<h1>这里是v-if等的条件渲染学习页h1>
<p v-if="element==='A'">我是Ap>
<p v-else-if="element==='B'">我是Bp>
<p v-else-if="element==='C'">我是Cp>
<p v-else>我是Dp>
template>
下面是运行截图,可以发现不满足条件的p标签是完全没有被生成的,这也是v-if和v-show的主要区别。
下面是v-show的使用:
<script>
export default{
data:()=>({
element:"D",
flag:true,
flag2:false
})
}
script>
<template>
<h1>这里是v-if等的条件渲染学习页h1>
<p v-if="element==='A'">我是Ap>
<p v-else-if="element==='B'">我是Bp>
<p v-else-if="element==='C'">我是Cp>
<p v-else>我是Dp>
<p v-if="flag">我是使用v-if时的标签页p>
<p v-show="flag">我是使用v-show时的标签页p>
<p v-show="flag2">我是使用v-show时的标签页2p>
template>
在之前的场景中已经验证了:若是v-if不满足是不会有标签生成的,可以发现在上面代码中增加了v-show为false的场景,根据下图可以看出此时是有v-show为false的标签的,只是该标签display为none。这也就是v-if和v-show的区别了。所以若是属性值变化比较频繁,推荐使用v-show,若是只使用一次则使用v-if和v-show差别也不是多大,所以说若是没有特殊情况一般使用v-show会更好些
列表渲染使用 v-for="item in/of items"的方式进行列表数据的展示,这里既可以使用in也可以使用of,他们是相同的,v-for可以用来展示数组,也可以用来展示对象,下面是v-for用来处理数组数据时的示例代码:
<script>
export default{
data:()=>({
customers:[
{
"id":"1001",
"name":"张三",
"sex":"男"
},
{
"id":"1002",
"name":"李四",
"sex":"女"
},
{
"id":"1003",
"name":"王五",
"sex":"男"
}
]
})
}
script>
<template>
<h1>这是列表渲染的代码h1>
<div v-for="customer in customers">
<p>{{ customer.id }}+{{ customer.name }}+{{ customer.sex }}p>
div>
template>
通过如上方式我们就可以将数组中的元素进行去除,上面的数据是一个json类型的对象数组,循环取出来的是customer,我们还需要使用customer.的方式获取其真正的属性。此外还可以获取到数组的每次下标,不过表达式需要这么写:v-for=“(customer,index) in customers”,然后遍历时就可以获取到当前数组元素的下标了,如下:
<script>
export default{
data:()=>({
customers:[
{
"id":"1001",
"name":"张三",
"sex":"男"
},
{
"id":"1002",
"name":"李四",
"sex":"女"
},
{
"id":"1003",
"name":"王五",
"sex":"男"
}
]
})
}
script>
<template>
<h1>这是列表渲染的代码h1>
<div v-for="(customer,index) in customers">
<p>{{ customer.id }}+{{ customer.name }}+{{ customer.sex }}+{{ index }}p>
div>
<div v-for="customer in customers">
<p v-for="cus in customer">p>
div>
template>
结果如下:
此外v-for还支持对对象的遍历,在进行对象渲染时可以只获取属性的value,若是只有一个参数默认就是value,若是三个参数则第一个是value,第二个是key,第三个是index,如下所示:
<script>
export default{
data:()=>({
customer:{
id:"1004",
name:"赵六",
sex:"女"
},
customers:[
{
"id":"1001",
"name":"张三",
"sex":"男"
},
{
"id":"1002",
"name":"李四",
"sex":"女"
},
{
"id":"1003",
"name":"王五",
"sex":"男"
}
]
})
}
script>
<template>
<h1>这是列表渲染的代码h1>
<div v-for="(customer,index) in customers">
<p>{{ customer.id }}+{{ customer.name }}+{{ customer.sex }}+{{ index }}p>
div>
<p v-for="(value,key,index) in customer">{{ index }}+{{ key }}:{{ value }}p>
template>
在说key之前必须接着说说v-for,v-for在遍历集合时会默认有个行为:若是集合元素的位置发生了变化,vue做不到直接更换元素的位置,而是使用“就地更新"的策略来更新响应的DOM信息。这样其实是浪费了性能的,因为完全可以做到调换位置解决问题的。key就是做这个事情的。key需要作为集合所在标签的一个属性,html中是没有key这个属性的,这个是vue才会具有的,官方推荐,任何时候都需要为集合声明一个key,防止元素因位置调换造成的性能损耗。
下面是key的代码:
<script>
export default{
data:()=>({
customers:[
{
"id":"1001",
"name":"张三",
"sex":"男"
},
{
"id":"1002",
"name":"李四",
"sex":"女"
},
{
"id":"1003",
"name":"王五",
"sex":"男"
}
]
})
}
script>
<template>
<div v-for="(customer,index) in customers" v-bind:key="customer.id">
<p>{{ index }}:{{ customer.name }}p>
div>
template>
如上代码为div标签增加了key属性,使用了v-bind进行属性绑定,因为不使用v-bind的话,会把index解析成字符串,而不是获取对应的实际值。增加了key以后,vue会自动帮我们进行元素调换而不是采用就地更新的策略。那应该使用什么作为key呢?上面的例子中使用的是customer.id,真正应用中也应该使用这种对于集合的元素来说不会变化的值作为key,这样才能更好的判断元素的现在的真正位置。
事件处理一般分为“内联事件处理器”和“方法事件处理器”,其中内联事件处理器在实际中基本不会使用,真正使用的都是方法事件处理器。
事件处理器使用元素 v-on:事件=“”,来进行事件声明,当双引号之间是一个有结果的操作时,比如++,这种称为内联处理器,当双引号内是一个方法时,则被称为方法处理器。需要说的是v-on:事件=“”,可以被简写为:@事件=“”,这种写法比较简洁,也是使用比较多的方式。
下面是内联处理器的写法代码:
<script>
export default{
data:()=>({
count:0
})
}
script>
<template>
<h1>内联处理器h1>
<button v-on:click="count++">点击button>
<p>{{ count }}p>
template>
<script>
export default{
data:()=>({
count:0
}),
// vue 中 所有的方法都需要声明在methods中,这是固定写法,多个方法使用逗号隔开
methods:{
add(){
console.log("点击了")
this.count++
// 在vue中的data中声明的变量,都可以使用this进行获取
console.log(this.count)
}
}
}
script>
<template>
<h1>方法事件处理器h1>
<button @click="add">点击button>
<p>{{ count }}p>
template>
这里有几个重要的点需要说下,代码本身很简单
事件传参就是将参数传递到方法内,然后由方法进行操作
<script>
export default{
data:()=>({
customers:[
{
"id":"1001",
"name":"张三",
"sex":"男"
},
{
"id":"1002",
"name":"李四",
"sex":"女"
},
{
"id":"1003",
"name":"王五",
"sex":"男"
}
]
}),
methods:{
showName(name,e){
console.log(name);
alert(e);
}
}
}
script>
<template>
<h1>这是方法事件传参h1>
<div v-for="customer in customers" :key="customer.id">
<p @click="showName(customer.name,$event)">{{ customer.name }}p>
div>
template>
这里有一点要着重说的:
若是想要传递event,则需要在调用时使用$event进行传递。,传递event一般是需要进行事件处理,不过vue提供了丰富的事件修饰符,可以简化envent的写法,下一节就是了,常见的stop阻止冒泡,prevent阻止默认事件,once只调用一次等。
事件修饰符,是相对于事件来说的,正常情况下需要把事件传递给方法,然后我们在方法内部对事件进行变更如:event.preventDefault() 或
event.stopPropagation() 。不过vue为v-on提供了事件修饰符,来对事件的编辑进行简洁化,修饰符是使用点表示的指令后缀。事件修饰符都有哪些呢?
事件冒泡是指当一个元素上的事件被触发时,该事件会沿着 DOM 树向上传播,
逐级触发每个祖先元素上相同类型的事件。这意味着如果一个元素上发生了事件,
其父元素、祖父元素及更高层级的祖先元素也会接收到相同类型的事件。
通过事件冒泡,我们可以在更高级别的容器元素上监听事件,而不需要为每个子元素
都添加事件处理程序。这使得事件处理变得更加灵活和简化了代码结构。
例如,如果一个按钮元素嵌套在一个 元素中,并且都绑定了点击事件处理程序。
当点击按钮时,按钮的点击事件将首先触发,然后事件将向上传播到包含它的 元素上,
进而可能触发 上的点击事件处理程序。
在实际开发中,我们可以利用事件冒泡来实现事件委托(Event Delegation)。
通过将事件处理程序绑定到父元素上,可以避免在子元素上添加大量的事件处理程序。
这对于动态生成的元素或具有大量子元素的列表非常有用。
需要注意的是,通过 event.stopPropagation() 方法可以阻止事件继续冒泡,
即停止事件在 DOM 树中的传播。这在需要限制事件冒泡范围或避免多个处理程序同时触发时很有用。
<script>
export default{
data(){
return{
msg:"哈哈哈"
}
},
methods:{
methodA(){
alert("A方法被触发了");
},
methodB(){
alert("B方法被触发了");
}
}
}
script>
<template>
<p @click="methodA()">
<p @click="methodB()">我是字标签的点击p>
p>
template>
上述代码若是触发了子标签的点击,则默认情况下methodA同样会被触发,怎么解决这个问题呢,使用stop就可以组织这种冒泡行为

上面的代码改成如下即可:
<script>
export default{
data(){
return{
msg:"哈哈哈"
}
},
methods:{
methodA(){
alert("A方法被触发了");
},
methodB(){
alert("B方法被触发了");
}
}
}
script>
<template>
<p @click="methodA()">
<p @click.stop="methodB()">我是字标签的点击p>
p>
template>
- .self:这个修饰符只有在事件在原始元素本身上触发时才会触发事件处理程序。如果事件是从子元素冒泡到父元素并触发的,那么使用.self修饰符的父元素将不会被正常冒泡。self是从自身禁止冒泡,stop则是从子元素来禁止冒泡,他们想达到的目的类似,但作用的对象不同
<script>
export default{
data(){
return{
msg:"哈哈哈"
}
},
methods:{
methodA(){
alert("A方法被触发了");
},
methodB(){
alert("B方法被触发了");
}
}
}
script>
<template>
<p @click.self="methodA()">
<p @click="methodB()">我是字标签的点击p>
p>
template>
疑问?self是否是直接中断事件的向上冒泡,还是只是绕过了自己?<div @click="methodC">
<div @click.self="methodB">
<p @click="methodA">我是孙子级点击事件按钮p>
div>
div>
如上当有三个层级的单点事件时,self只会阻止自身的冒泡事件,但不会阻止冒泡事件的向上传递

当把这里的 self换成stop时是什么效果呢?其实是正常输出A、B方法的内容,不会输出C。因为stop是停止向上冒泡,但不禁止自身的事件触发。
- .prevent:这个修饰符用于阻止事件的默认行为。例如,当你在一个表单中提交时,页面会重新加载。使用.prevent修饰符可以阻止表单的默认提交行为。
这里我的理解是:prevent阻止的是dom的默认行为。不知道理解的对不对,如下案例点击时会调用方法a,但不会跳转百度<a href="http://www.baidu.com" @click.prevent="methodA()">点我进百度,测试preventa>
- .once:这个修饰符用于只触发一次事件处理程序。当事件被触发后,事件处理程序将被移除,不会再次触发。
<p @dblclick.once="methodA()">点击我测试oncep>
经验证确实只会被触发一次,再继续点击就再也不会触发了,刷新以后才可以恢复.
- .capture:这个修饰符用于将事件监听器添加到父元素上,并在事件捕获阶段触发。默认情况下,事件监听器是在事件冒泡阶段触发的。使用.capture修饰符可以将事件监听器切换到事件捕获阶段。
- .passive:这个修饰符可以提高滚动性能。当你在一个滚动事件的监听器中使用.passive修饰符时,告诉浏览器该事件处理程序不会调用preventDefault()来阻止默认的滚动行为。这样浏览器可以进行一些优化,提高滚动的性能。
除了这些事件修饰符以外还有很多键盘等其他修饰符,详见官网:https://cn.vuejs.org/guide/essentials/event-handling.html#event-modifiers
2.9数组变化侦测
这里主要是两类关于数组的方法,一类操作数组时会直接引起数组内容的变化,无需我们作额外操作数据的数据就会动态展示在页面中,这类操作有:
-
push:添加一个元素到数组中,不能一次性添加多个
-
pop:从尾部,将数组的元素进行删除一个,一次只能删除一个
-
shift:从头部,将数组的元素进行删除一个,一次只能删除一个
-
unshift:从头部插入一个或者多个元素
-
splice:(start, deleteCount, item1, item2, …): splice 方法用于修改数组,可以用于删除、替换或插入元素。它会修改原始数组,并返回被删除的元素数组。其中,start 是指要修改的起始索引位置,deleteCount 是要删除的元素个数,可选的 item1, item2, … 是要插入到数组的元素。例如:
fruits = ['Apple', 'Banana', 'Orange'];
fruits.splice(1, 1, 'Strawberry', 'Mango');
修改后的数组为 ['Apple', 'Strawberry', 'Mango', 'Orange']
-
sort:sort 方法用于原地对数组进行排序。默认情况下,它按照 Unicode 字符编码的顺序进行排序。如果想要自定义排序逻辑,可以提供一个比较函数作为参数。例如:
numbers = [3, 1, 5, 2, 4];
numbers.sort();
排序后的数组为 [1, 2, 3, 4, 5]
也可以传入一个比较器进行比较:
this.months.sort((a,b)=>{
if(a<b){
return 1;
}else if(a>b){
return -1;
}else{
return 0;
}
});
上面的排序是倒叙排,按正常左减右的相反结果进行返回
-
reverse:reverse 方法用于反转数组中元素的顺序。它会修改原始数组,并返回修改后的数组。例如:
fruits = ['Apple', 'Banana', 'Orange'];
fruits.reverse();
修改后的数组为 ['Orange', 'Banana', 'Apple']
注意的是上面的这些方法操作完数组以后,数组会自动变化,无需我们再像下面这样操作,如果操作了反而不对。
这种写法是错误的
this.months = this.months.reverse();
还有另一种数组的操作,这种操作都会产生一个新的数组,所以我们必须使用原数据进行接着,不然数据的变更无法更新到dom中
-
filter:起到一个过滤的作用,这里的用法与java里的strem的filter很是类似,如下所示:
this.months=this.months.filter((month)=>{
return month=="march";
})
执行此操作后,数组里将只会剩下一个march
-
concat:顾名思义就是拼接一个新的数组到原数组中,操作如下:
this.months = this.months.concat(['june','july']);
默认是在尾部进行拼接元素,如果想拼接在头部可以先这么写,验证了下是ok的:
this.months = this.months.reverse().concat('june','july').reverse();
-
slice:slice是片的意思,他的作用是从原数组中取出一部分作为一个新的数据,操作如下:
this.months = this.months.slice(0,2);
上面的操作是获取0下标开始,2下标结束的元素,注意不包括2,然后组成一个新的数组
2.10 计算属性
这里需要学习一个在export中的新的小模块computed对象,他和methods一样也是对象,他是专门为属性计算设计的,先来看下不使用属性计算时如何写
<script>
export default{
data(){
return {
customer:{
id:"1001",
name:"张三",
address:['住址1','住址2','住址3']
}
}
}
}
script>
<template>
<h1>这是计算属性Computed的学习页h1>
<p v-for="(value,key,index) in customer" v-bind:key="customer.id">
<p>{{ customer.address.length>0?'非空':'空' }}p>
p>
template>
可以看到这把计算写在了双阔号内,这么写语法上是允许的,但是会发现若是表达式比较多就会比较混乱,所以官方推荐以方法的形式写在computed对象内,同时computed内部的方法不支持传参,因为computed总的计算结果会被缓存,若是计算内的值不变则不会触发重新计算,这样更有利于性能,而若是使用方法,方法每次都会进行重新计算,所以感觉computed和methods在使用上差不多,但是性能上是有区别的,这也是computed存在的原因,下面是使用三种方式的代码展示:
<script>
export default{
data(){
return {
customer:{
id:"1001",
name:"张三",
address:['住址1','住址2','住址3']
}
}
},
// computed中的方法不支持传参,因为需要缓存计算结果,传参的话则结果不可控,就无法缓存,这样的话与方法就没有区别了
computed:{
computedTest(){
return this.customer.address.length>0?'非空':'空';
}
},
methods:{
methodTest(){
return this.customer.address.length>0?'非空':'空';
}
}
}
script>
<template>
<h1>这是计算属性Computed的学习页h1>
<p v-for="(value,key,index) in customer" v-bind:key="customer.id">
p>
<p>原生写法:{{ customer.address.length>0?'非空':'空' }}p>
<p>computed写法:{{ computedTest }}p>
<p>methods写法:{{ methodTest() }}p>
template>
下面是运行截图,可以发现三种方式都能达到效果,但是性能最好的是computed的写法:

注意:
- computed会将内部方法计算的结果进行缓存,数据不变化就不会触发重新计算
- computed的方法不支持传参,若是传了参,则结果就是动态的,就无法缓存
- methods的触发每次都会进行重新计算,所以性能上computed高于methods
- {{}} 双阔号内支持调用计算属性,无需加()
- {{}} 双阔号内支持调用方法,但是方法必须加()
2.11 class绑定
html中标签支持class属性,class属性用于指定一个或多个 CSS 类名,将这些类名应用于元素,多个css类名使用空格进行分割,他们共同对当前的html的标签进行生效。
- 单值绑定
代码如下,无论是单值绑定还是多值绑定都必须为其声明css类,因为我们传给html的class属性的是字符串,他会把其解析成css的class类名,然后去style或者引入的css文件中去寻找对应的class,找不到就没有样式。<script>
export default{
data(){
return{
color:"classA"
}
}
}
script>
<style>
.classA{
color:rgb(230, 71, 71);
font-size: 30px;
}
style>
<template>
<h1>验证class绑定h1>
<p v-bind:class="color">单值绑定p>
template>
- 多值绑定 和 数组绑定
其实vue是不支持多值绑定的,vue里的class多值绑定需要使用数组来完成,原始的html中class中若是声明多个css类名需要使用空格隔开,但是vue中属性绑定不支持直接写多个,如果写多个需要以数组的形式进行绑定,不然就只能写原生html的写法<script>
export default{
data(){
return{
color:"classA",
colorB:"classB",
font:"fontA"
}
}
}
script>
<style>
.classA{
color:rgb(230, 71, 71);
font-size: 30px;
}
.classB{
color: rgb(230, 71, 71);
}
.fontA{
font-size: 30px;
}
style>
<template>
<h1>验证class绑定h1>
<p v-bind:class="color">单值绑定p>
<p v-bind:class="[colorB,font]">多值绑定p>
template>
- 对象绑定
对象绑定有两种形式,一种是内联的写法,也就是直接在class中写对象,一种是外部声明的对象,不过对象的写法和直接声明值表示有区别,使用对象时,对象的属性表示css中class的类名,对象的属性值应为boolean类型,表示是否启用,下面是class绑定的对象内联的写法<script>
export default{
data(){
return{
color:"classA",
colorB:"classB",
font:"fontA",
classObject:{
color:"classB",
font:"fontA"
}
}
}
}
script>
<style>
.classA{
color:rgb(230, 71, 71);
font-size: 30px;
}
.classB{
color: rgb(230, 71, 71);
}
.fontA{
font-size: 30px;
}
style>
<template>
<h1>验证class绑定h1>
<p v-bind:class="color">单值绑定p>
<p v-bind:class="[colorB,font]">多值绑定p>
<p v-bind:class="{classB:true,fontA:true}">对象绑定p>
template>

下面是对象绑定时在data中声明的对象,其实没区别,就是把上面的写法挪到data中了,不过需要注意的是这里对象的属性都是css的类名,和一开始属性是变量名不同了。从下面可以看到内联和非内联的写法的区别,内联时对象是应该有中括号包裹的,非内联直接传入对象名即可<script>
export default{
data(){
return{
color:"classA",
colorB:"classB",
font:"fontA",
classObject:{
classB:true,
fontA:false
}
}
}
}
script>
<style>
.classA{
color:rgb(230, 71, 71);
font-size: 30px;
}
.classB{
color: rgb(230, 71, 71);
}
.fontA{
font-size: 30px;
}
style>
<template>
<h1>验证class绑定h1>
<p v-bind:class="color">单值绑定p>
<p v-bind:class="[colorB,font]">多值绑定p>
<p v-bind:class="{classB:true,fontA:true}">对象绑定-内联p>
<p v-bind:class="classObject">对象绑定-非内联p>
template>

- 数组嵌套对象进行class属性绑定
数组内部是可以嵌套对象的,但是对象内部是不能嵌套数组的。这种写法其实很好理解,数组中只能传入我们声明的变量名,而不是css的类名,这是数组和对象使用的最大区别。
对象也是我们声明的变量,故都可以直接传入到数组中<script>
export default{
data(){
return{
color:"classA",
colorB:"classB",
font:"fontA",
classObject:{
classB:true,
fontA:false
},
leftsize:"left"
}
}
}
script>
<style>
.classA{
color:rgb(230, 71, 71);
font-size: 30px;
}
.classB{
color: rgb(230, 71, 71);
}
.fontA{
font-size: 30px;
}
.left{
margin-left: 100px;
}
style>
<template>
<h1>验证class绑定h1>
<p v-bind:class="color">单值绑定p>
<p v-bind:class="[colorB,font]">多值绑定p>
<p v-bind:class="{classB:true,fontA:true}">对象绑定-内联p>
<p v-bind:class="classObject">对象绑定-非内联p>
<p :class="[classObject,leftsize]">对象绑定嵌套数组绑定p>
template>

2.12 style绑定
html中的标签支持class属性用以对应css中的类名,同时也支持内联样式即style(不推荐写内联样式,但是内联样式的优先级最高,若是内联样式与class定义了同样的属性不同的值,肯定是内联生效)。前面学了内联表达式和方法表达式,其实定义是类似的,内联表达式是直接将表达式写在了使用的地方,而不是通过方法进行管理。这里的style也是内联样式。特别注意的是:style本来支持的就是直接写样式,不过vue中可以将font-size这种属性写成fontSize,这么写是合法的。
- 对象绑定
这里的对象绑定写法和class类似,不同的是class中对象绑定的key是css中的类名,value是true或者false。style中的对象绑定key是css的原始的样式声明,value是属性真正的值或者data变量。<script>
export default{
data(){
return{
colorD:"blue",
font:"40px"
}
}
}
script>
<template>
<h1>style绑定h1>
<p :style="{color:colorD,fontSize:font}">对象绑定p>
template>

对象绑定除了上面的内联的写法外还可以写在data里<script>
export default{
data(){
return{
colorD:"blue",
font:"40px",
styleObject:{
color:"blue",
fontSize:"40px"
}
}
}
}
script>
<template>
<h1>style绑定h1>
<p :style="{color:colorD,fontSize:font}">对象绑定-内联p>
<p :style="styleObject">对象绑定-非内联p>
template>

- 数组绑定
数组绑定中传的数组元素也必须是对象,所以在使用style时感觉么有必要使用数组绑定,对象绑定完全够用的,如下所示:<script>
export default{
data(){
return{
colorD:"blue",
font:"40px",
styleObject:{
color:"blue",
fontSize:"40px"
}
}
}
}
script>
<template>
<h1>style绑定h1>
<p :style="{color:colorD,fontSize:font}">对象绑定-内联p>
<p :style="styleObject">对象绑定-非内联p>
<P v-bind:style="[styleObject]">数组绑定P>
template>

数组绑定还可以这么写:<script>
export default{
data(){
return{
colorD:"blue",
font:"70px",
styleObject:{
color:"blue",
fontSize:"40px"
}
}
}
}
script>
<template>
<h1>style绑定h1>
<p :style="{color:colorD,fontSize:font}">对象绑定-内联p>
<p :style="styleObject">对象绑定-非内联p>
<P v-bind:style="[styleObject]">数组绑定P>
<P v-bind:style="[styleObject,{fontSize:font}]">数组绑定嵌套对象P>
template>

小小总结:
- style绑定直接写对象绑定即可,对象中的属性名是各种样式,value是样式的具体值,这里推荐使用data里面声明对象,不是直接卸载v-bind:style=""中
- class绑定感觉数组更好用,里面可以传对象,也可以传普通的单值,这里的key都是class的类名,value都是true或者false
2.13 侦听器
侦听器类似于java里的listener,都能监控到数值的变化,当数值变化时就会调用对应的监听方法。
代码如下:
<script>
export default{
data(){
return{
month:"january"
}
},
methods:{
changeMonth(){
this.month="february"
}
},
watch:{
month(newValue,oldValue){
alert('新值:'+newValue+', 老值:'+oldValue)
}
}
}
script>
<template>
<h1>测试侦听器h1>
<button @click.stop="changeMonth()">点击button>
<p>{{ month }}p>
template>
那侦听器都能监控哪些信息的变化呢
- 响应式数据变化:这是最常见的数据,也就是我们在data中声明的数据
- 监听路由变化
- 监听对象属性
- 监听数组等
注意点
- 监听响应式数据时,监听器中的对应方法名必须数数据名称一致,不然不生效
- 若是监听器中只传入一个参数,则传入的是新值,传入两个参数则默认第一个是新值,第二个是老值
小小总结:
到现在已经学习了export下面的四个主要不分了
- data:data方法用以声明响应式数据
- methods:methods对象用以声明函数
- computed:computed对象用以声明计算属性,结构和methods类似,不过性能更好
- watch:watch对象用以声明监听的响应式数据等,结构和methods类似,入参是newValue,oldValue
2.14 表单输入绑定(双向绑定)
这里叫双向绑定感觉更合适,这种技术之前在angular时被认为是一个亮点,后来的js框架基本都具备这个能力,VUE自然也不会例外,我们使用v-model将响应式数据绑定到输入框即可,下面是测试代码:
<script>
export default{
data(){
return{
message:""
}
},
// 这块代码和这个功能没有关系,只是顺手多写下作为巩固
watch:{
message(val){
alert('监听到了变化:'+val);
}
},
// 这个方法是为了验证message数据变更会不会反向输出到input
methods:{
changeMsg(){
this.message="aaa";
}
}
}
script>
<template>
<h1>测试双向绑定h1>
<form>
<input type="text" v-model="message"/>
<p>{{ message }}p>
<button @click.stop="changeMsg()">点击更改button>
form>
template>
当在输入框输入数据时,message信息也会在下方的p标签内展示,反向测试的话,点击点击更改按钮,也会把信息进行更改也会体现在input上。
不过需要注意的是,v-model支持了三种事件,用来简化数据绑定的后续操作
- lazy:不做数据的实时变更到绑定的属性上,只有提交等才会将变化体现出来,测试时是失去光标触发了
<form>
<input type="text" v-model.lazy="message"/>
<p>{{ message }}p>
<button @click.stop="changeMsg()">点击更改button>
form>
- number:将输入框的数据转为数值类型,不过只有当输入框的type为number时才会生效,其他类型的输入框即使用v-model.number也不会生效。
<form>
<input type="text" v-model.lazy="message"/>
<input type="number" v-model.number="message"/>
<p>{{ message }}p>
<button @click.stop="changeMsg()">点击更改button>
form>
- trim:去除输入框中字符串前后的空格,中间空格无法去除
<form>
<input type="text" v-model.lazy="message"/>
<input type="number" v-model.number="message"/>
<input type="text" v-model.trim="message"/>
<p>{{ message }}p>
<button @click.stop="changeMsg()">点击更改button>
form>

2.15 模板引用:获取DOM树
原始的JS中是支持的获取DOM操作的,上面的VUE已经提供了丰富的功能用来简化这一操作,比如使用v-bind来简化属性绑定,使用v-on来简化事件处理,使用v-model简化数据绑定,使用v-for简化列表的遍历。等vue提供了丰富的支持DOM的操作,不过同时VUE也提供了特殊情况下对DOM的直接操作。分为两步即可直接操作DOM
- 1.需要获取到的标签上声明ref
- 2.方法内部使用this.$refs获取dom,然后就可以操作了
<script>
export default{
data(){
return {
}
},
methods:{
getDom(){
// 注意这里的inpuDom是在input框中的ref的值
console.log(this.$refs.inputDom);
alert(this.$refs.inputDom);
}
}
}
script>
<template>
<h1>测试获取DOMh1>
<input type="text" ref="inputDom"/>
<button @click="getDom()">点击获取DOM内容button>
template>
三、组件
vue结尾的文件就是一个个组件,组件是可以复用的,需要时导入到对应场景即可。vue中有且一个根组件叫App.vue,其他组件声明在component下面。
1 组件组成
下面是组件的标准组成有这三部分:script,style,template。只有template是不可省略的,其他均可省略。
style上的scoped表示,样式只在当前组件内生效,被嵌套到其他组件时不生效。
<script>
script>
<style scoped>
style>
<template>
template>
局部嵌套只需要两步即可:
前提:script 需要携带setup,增加setup相当于增加默认导出
1.引入组件:在script中使用: import 组件名 from './components/组件名.vue'
2.显示组件:在template中引入组件:<组件名/>
3.如果没有前提需要在script增加组件导入
export default{
components:{
CatchDom:CatchDom
}
}
2 组件嵌套关系
组件之间支持相互嵌套,有很多组件是共用的,比如头部的,左侧的菜单栏等在真实项目中都是共用的,所以组件嵌套是很有必要的,类似下图:

组件嵌套就是引入组件即可,不同组件定义好自己的边界即可。
3 组件注册方式
上面在组件组成中说了局部注册,这里说下全局注册,全局注册只需要在main.js中注册一次,然后就可以在所有的文件中进行直接在template中使用了,很省事,但对于VUE项目来说,打包时无论全局引入的组件是否真的被使用,都会被打包带走,这点算是全局注册的弊端,同时全局注册会造成组件间的关系不够清晰(这是官方话,我感觉听清晰的)。
看全局引入前线看下main.js的代码:
// 使用 ES6 的模块导入语法,从 'vue' 模块中导入 createApp 函数。
// createApp 函数是 Vue 3 中用于创建 Vue 应用实例的工厂函数。
import { createApp } from 'vue'
// 导入名为 App 的组件,该组件位于当前文件所在目录下的 App.vue 文件中。
// 这里假设 App.vue 是根组件,它将被挂载到 DOM 中。
import App from './App.vue'
// 调用 createApp 函数,传入 App 组件作为参数,创建一个 Vue 应用实例。
// 这个实例将用于管理组件的生命周期、状态和渲染等。
// 使用 mount 方法将 Vue 应用实例挂载到指定的 DOM 元素上。
// 这里的 #app 是一个选择器,表示选择 id 为 app 的元素作为挂载目标。
createApp(App).mount('#app')
mian.js 中就三行代码,但是这三行代码很重要,没有他项目根本跑不起来,第一行是从vue中导入createApp函数,第二行导入App.vue这个组件,第三行是先用createApp根据导入的App组件创建出一个App,然后调用App的mout函数将App挂载到DOM上,这里的DOM在index.html中。

言归正传怎么做全局导入呢,如下,只需两步:
1.导入组件:import CatchDom from './components/CatchDom.vue';
2.将组件加入到app: createApp(App).component("CatchDom",CatchDom).mount('#app')
然后需要在哪里使用就在哪里的template中引入即可,这里就和setup没有关系了,有没有都不影响引入了
4 组件传递数据 props
既然项目中一个页面是由多个组件共同构成的,那就免不了需要相互之间传递和共享参数,所以props绝对是重点中的重点了,虽然重点但是很简单。
假设有着这样的场景:App这个根组件中引入了one组件,one组件中引入了two组件,想要将one组件的数据传入到two组件中。
下面是one组件的代码
<template>
<h1>oneh1>
<two msg="我是one里面的参数"/>
template>
下面是two组件的代码:
<script>
export default{
props:["msg"]
}
script>
<template>
<h1>twoh1>
<p>{{ msg }}p>
template>
下面是展示效果:

可以看到one里面的信息被传入到了two中,这样就实现了组件间的数据传递
1.父组件传递参数时,参数写在template中引入的子组件中
2.子组件接收参数时,必须使用props接收,写法固定props:[“参数”]
3.子组件接收的参数,相当于自己在data中声明的响应式数据,一样可以使用{{}}进行获取
4.父组件若是想要动态传参,只需要为参数增加v-bind:参数=“文本插值”,这里很有意思,v-bind绑定的数据并不是这正的html属性,而是我们想要传递的参数,这个值可以是任何非关键字的信息
另外展示下动态传参,相对于上面的静态传参的例子,只需改动one组件内容如下即可
<script>
export default{
data(){
return{
msg:"我是one里面的参数-动态"
}
}
}
script>
<template>
<h1>oneh1>
<two v-bind:msg="msg"/>
template>
5 组件传递多种数据类型,类型校验,参数默认值
多种参数类型的传递与基本参数没有啥区别,就是将我们在data区域渲染的数据进行传递,传递以后的数据依然支持使用{{}}进行获取。我们可以想到的数据类型都是支持传递的。
这是父组件信息:
<script>
import sonOne from './sonOne.vue'
export default{
data(){
return{
msg:[12,'122']
}
},
components:{
sonOne:sonOne
}
}
script>
<template>
<h1>父组件信息h1>
<sonOne :msg="msg"/>
template>
这是子组件信息:
<script>
export default{
props:["msg"]
}
script>
<template>
<h1>子组件信息h1>
<p>{{ msg }}p>
template>
5.1 类型校验
若是需要对参数校验则需要改变之前使用props写的接收参数方式,需要更改为使用对象来接收父组件传递的参数,对象内部声明各个接收的参数为对象,为每个对象增加type属性。
== 单类型校验:需要注意的是若是校验不满足,不会影响运行,单console会有警告提示==
props:{
msg:{
type:String,
}
}
多类型校验,后跟数组:满足其中任何一种类型均不会有警告发生
props:{
msg:{
type:[String,Number,Array,Object]
}
}
5.2 组件传参默认值
上面已经说了类型校验,当子组件接收不到父组件信息时,一般是需要一个默认值的,这个默认值可以避免出现undefined或者不展示的问题。代码如下:
数字和字符串直接声明默认值即可,但是对象和数组则需要通过方法来进行声明默认值:
父组件传递参数均没有区别,这里不展示:
props:{
msg:{
// type:Object
type:[String,Number,Array,Object]
},
age:{
type:Number,
default:0
},
month:{
type:Array,
default(){
return ["kong"]
}
},
customer:{
type:Object,
default(){
return{
age:0,
name:"空",
sex:"男"
}
}
}
}
5.3 声明必填
前面说了类型校验、默认值声明,子组件还支持对是否必传进行校验,使用required进行校验,当为true时,若是没有传该值,则在console上给出警告
props:{
month:{
type:Array,
required:true,
default(){
return ["kong"]
}
}
}
7 组件事件和v-model
这里的组件事件就是自定义事件,所谓自定义事件就是支持子组件将信息传递给父组件。也就是在子组件中声明自定义事件名,在父组件中定义事件的执行,这就是组件事件。前面已经有了父传子的方法使用props,子传父就是使用组件事件了,这样无论组件怎么调用都可以实现参数的传递了。
下面是子组件的代码:
<script>
export default{
data(){
return{
msg:"这是子组件信息"
}
},
watch:{
msg(newValue){
// alert("触发了")
this.$emit("myEvent",newValue)
}
}
}
script>
<template>
<h1>组件Twoh1>
<input type="text" v-model="msg"/>
template>
下面是父组件的代码:
<script>
import ComponentTwo from './ComponentTwo.vue'
export default{
components:{
ComponentTwo:ComponentTwo
},
methods:{
fatherFun(data){
this.faMsg=data
}
},
data(){
return{
faMsg:""
}
}
}
script>
<template>
<h1>组件Oneh1>
<input type="text" :value="faMsg"/>
<ComponentTwo @myEvent="fatherFun"/>
template>
注意事项:
- 子组件可以不使用v-model,用其他事件触发$emit 也是一样的,比如单机事件等
- 子组件调用父组件的自定义事件必须使用$emit 如果只传一个参数,默认是父组件的自定义事件名,若是两个参数,则第二个参数是传递的数据
- 父组件中自定义事件必须定义在引入 组件的地方,声明方式可以使用v-on或者@这点与VUE的事件写法没有区别
- 父组件中自定义事件写在引入的子组件时(只能写在这里),绝对不能写参数,这里直接写名称即可也不能带括号,如:
- 自定义事件声明在methods对象中,与普通方法无异,若是有参数需要在这里进行声明,才可以使用,如myEvent(data){}
8 组件事件配合v-model和watch实现父子组件数据的实时互传
上面的例子已经介绍了有v-model的使用,他的作用其实就是方便数据可以实时被变更。因为他的作用本来就是双向绑定的,他实时变更了就可以使用watch对他进行侦听,这样就方便数据回传父组件了。不使用这种方式,单独加个单击事件,当事件触发时回传父组件也是一样的。前面已经有了父传子和子传父,这里介绍下如何使用$emit+v-model+watch实现父子组件数据的实时互传。
这是子组件代码:
<script>
export default{
data(){
return{
msg:"这是子组件信息"
}
},
watch:{
msg(newValue){
this.$emit("myEvent",newValue)
},
data(){
this.msg=this.data;
}
},
props:{
data:{
type:String,
default:"空",
required:true
}
}
}
script>
<template>
<h1>组件Twoh1>
<input type="text" v-model="msg"/>
template>
这是父组件代码:
<script>
import ComponentTwo from './ComponentTwo.vue'
export default{
components:{
ComponentTwo:ComponentTwo
},
methods:{
fatherFun(data){
this.faMsg=data
}
},
data(){
return{
faMsg:"",
faMsg2:""
}
},
watch:{
faMsg(){
// alert("触发了")
this.faMsg2 = this.faMsg;
}
}
}
script>
<template>
<h1>组件Oneh1>
<input type="text" v-model="faMsg"/>
<ComponentTwo v-on:myEvent="fatherFun" :data="faMsg2"/>
template>
以上思路(以上代码更改完没有立即生效,隔了几分钟才生效,不知原因):
1.子组件定义参数msg,页面使用v-model进行展示,这样数据实时的变化就会回传给msg了
2.子组建中定义msg的侦听方法,当msg变动时,用$emit 将msg信息传递给父组件
3.父组件使用自定义事件fatherFun接收传递的msg,将其给到自定义的参数faMsg,然后进行了展示,这里使用v-model绑定faMsg
4.父组件自定义参数faMsg2,给faMsg提供侦听方法,当异动时将faMsg 赋值给faMsg2
5.将faMsg2传递给子组件,父组件传递子组件属于实时传递,这里类似于侦听变化,当值发生变动就会自动传递到子组件,这是VUE定义好的动作
6.子组件在props中使用data接收传递的参数faMsg2
7.为data提供侦听方法,当data变动时实现将data的值赋给第一步的msg。到此就实现了父子组件单个参数的实时传递

这是是根据之前所学,自己琢磨的,感觉这个步骤还可以简化下,在父组件中若是不增加额外的变量直接建faMsg回传给子组件不知道是否可行?
经验证可行,父组件代码可直接简化为如下:
<script>
import ComponentTwo from './ComponentTwo.vue'
export default{
components:{
ComponentTwo:ComponentTwo
},
methods:{
fatherFun(data){
this.faMsg=data
}
},
data(){
return{
faMsg:""
}
}
}
script>
<template>
<h1>组件Oneh1>
<input type="text" v-model="faMsg"/>
<ComponentTwo v-on:myEvent="fatherFun" :data="faMsg"/>
template>
可以这么改的核心是faMsg必须使用v-model进行绑定,不然他的变化没有实时体现到变量faMsg上,这样组父组件就不会实时传递到子组件了,这个是关键点
9 组件数据传递:使用props进行子传父
前面说props只能由父传递到子组件也不错,因为当我们传递到子组建的不是数据而是函数的时候,这时候子组件接收到函数就可以使用,使用时传入的参数,真正调用的点还是在父组件,所以父组件还是可以拿到这个参数的,这个和java里的函数调用没啥区别,这是垮了类而已。
感想:包括声明的scoped 有点和private类似,import 和java的import也是类似,使用时java里需要再次进行操作,vue中也是需要我们再操作一次,不过感觉使用components有些多余了,下面是如何利用props传递方法实现子传父的,不过这种方式无法实现既携带方法又携带数据,若是想要传递数据还需要写别的props,感觉也没有多大优势。
下面是子组件代码:
<script>
export default{
props:{
doTrunser:{
type:Function,
required:true
}
}
}
script>
<template>
<h1>ParentSon组件h1>
<button @click="doTrunser('李四')">点击触发button>
template>
下面是父组件代码:
<script>
import ParentSon from './ParentSon.vue'
export default{
data(){
return{
msg:"张三"
}
},
methods:{
trunser(data){
this.msg=data;
}
},
components:{
ParentSon:ParentSon
}
}
script>
<template>
<h1>Parent组件h1>
<p>{{ msg }}p>
<ParentSon :doTrunser="trunser"/>
template>
感觉还是使用props传递普通参数实现父传子,使用$emit来实现子传父更好些,也更好实现父子间数据的实时传递
10 透传attributes
一个基本没有用的知识点,当在引入的子组件上声明class样式时(就是原始的class样式),若是子组件只有一个根标签,则跟标签会继承这个属性,若是想要禁止这种属性集成,可以如下进行操作,声明为false则取消自动继承。

11 插槽slots
开发VUE项目时需要定义很多的组件,方便我们复用,但是各个组件若是定义的弹窗或者响应格式不统一怎么办?到时候项目就会很难看。为了解决这个问题,VUE还支持了插槽。当我们在父组件中引入了各个子组件时,此时预留一部分功能由父组件实现,所有子组件只做一个接口留给父组件进行真正的操作,这就是插槽,有点类似于java里的接口,对于插槽子组件 只定义在哪里使用,真正怎么展示和内部逻辑是什么样,这个由父组件决定。插槽和组件的引入共同构成了项目的可拆卸的灵活性。
下面展示插槽的基础用法的父组件代码:
<script>
import ComponentB from './ComponentB.vue'
export default{
components:{
ComponentB:ComponentB
},
data(){
return{
message:"测试传参可好使"
}
}
}
script>
<template>
<h1>ComponentA组件h1>
<ComponentB :msg="message">
<template v-slot:slotA>
<h1>插槽Ah1>
template>
<template v-slot:slotB>
<h1>插槽Bh1>
template>
ComponentB>
template>
下面是子组件代码:
<script>
export default{
data(){
return{
}
},
props:{
msg:{
type:String,
default:"空",
required:true
}
}
}
script>
<template>
<h1>ComponentB组件h1>
<slot name="slotA">
<a href ="#">这是默认内容a>
slot>
<p>{{ msg }}p>
<slot name="slotB">
<a href ="#">这是默认内容a>
slot>
template>
- 定义插槽时,引入的子组件标签,必须是双标签,不可以单标签
- 引入插槽时,引入的组件标签,同样支持数据的传递,和自定义事件的回传,他和单标签引入子组件是没有区别的
- 若是需要声明多个插槽,需要使用不同的模板(template)在不同的模板内声明v-slot:插槽名,来对不同的插槽进行声明,注意插槽名前面是冒号,不是等于,插槽名也无需加引号,这就是具名插槽,v-slot:可省略为#
- 插槽内的值同样可以是data中渲染的数据,使用和普通的模板没有区别
- 插槽使用时,只需要slot标签就可以,若是父组件声明了多个插槽,则需要声明插槽的名称,使用name来声明不同的插槽的名称
- 插槽是双标签slot,他的内部支持声明插槽的默认内容,当父标签未声明插槽,或者声明的插槽未找到时,将使用默认的样式。
- 插槽使用的数据,需要在父组件中进行渲染,这里感觉放父组件也没有问题,不过使用插槽一般数据肯定在子组件,到时候还是需要$emit登场,来解决参数传递的问题
- 往后看了下发现VUE提供了插槽传参的方式,专用于将子组件信息通过插槽传递到父组件,这就实现了父子组件数据互传了
下面是使用插槽实现子传父时的子组件代码:
<script>
export default{
data(){
return{
msg22:"我是子组件内容1",
day:"我是子组件内容2"
}
},
props:{
msg:{
type:String,
default:"空",
required:true
}
}
}
script>
<template>
<h1>ComponentB组件h1>
<slot name="slotA" :msg="msg22">
<a href ="#">这是默认内容a>
slot>
<p>{{ msg }}p>
<slot name="slotB" :day="day">
<a href ="#">这是默认内容a>
slot>
template>
下面是父组件代码:
<script>
import ComponentB from './ComponentB.vue'
export default{
components:{
ComponentB:ComponentB
},
data(){
return{
message:"测试传参可好使"
}
}
}
script>
<template>
<h1>ComponentA组件h1>
<ComponentB :msg="message">
<template v-slot:slotA="slotProps">
<h1>{{ slotProps.msg }}h1>
template>
<template #slotB="slotProps">
<h1>{{ slotProps.day }}h1>
template>
ComponentB>
template>
- 子组件中进行属性绑定传值,这点与父组件传递值时是相同的:
- 父组件接收子组件传递的信息时,必须在插槽名后声明slotProps: ,slotProps是子组件插槽属性的对象,他的内部涵盖了子组件声明的所有属性
- 每个插槽都拥有一个slotProps,但是不同的插槽不可共用,不可跨插槽使用,同一个插槽内可声明多个数据进行传递
- 获取数据时slotProps对象直接点对应属性,就可以获取到我们从子组件传递到插槽内的值了
- 父组件中集成了子组件的信息后,还会再回传子组件。这就实现了父子组件通过插槽进行信息互传了
12 组件生命周期
描述的是组件从创建到销毁的全过程,在这个过程中VUE会执行很多方法,来对组件作不同的操作,在这个过程中VUE会运行一些组件生命周期钩子函数,这些函数会在组件生命周期的特定阶段进行触发,因此程序员得以通过这些方法对组件的不同生命周期进行干预和操作。这个有点类似于Spring中Bean的生命周期,在Bean的生命周期中,我们可以定义init和destroy,以及实现一些handler接口和processor接口来进行不同的动作。这个设计思想都是类似的。

上面是老图,下面是新图:

可以看到destroy阶段改为了取消挂载,对应的方法也改成了取消挂载前和取消挂载后,
一般将组件的生命周期分为四个阶段,每个阶段都有前置和后置方法调用:创建阶段、挂载阶段、更新阶段、销毁阶段。每个阶段都有一个操作前和操作后的方法,共8个钩子函数。分别如下:
生命周期
描述
beforeCreate
组件实例被创建之初
created
组件实例已经完全创建
beforeMount
组件挂载之前
mounted
s组件挂载到实例上去之后
beforeUpdate
组件数据发生变化,更新之前
updated
数据数据更新之后
beforeUnmount
组件取消挂载之前
unMounted
组件取消挂载之后
activated
keep-alive 缓存的组件激活时
deactivated
keep-alive 缓存的组件停用时调用
errorCaptured
捕获一个来自子孙组件的错误时被调用
这些方法在特定时期都是会被自动触发的,那这些方法应该写在哪里,怎么写呢?答案是都写在export default中即可,作为和data方法平级的方法就行,VUE在相应阶段就会自动调用:
<script>
export default{
data(){
return{
message:"测试数据更新",
customer:[]
}
},
methods:{
change(){
this.message = "我更新了"
}
},
beforeCreate(){
console.log("创建前");
},
created(){
console.log("创建后");
},
beforeMount(){
console.log("挂载前:"+this.$refs.buttonRef);
this.customer = ['张三','李四','王五']
},
mounted(){
console.log("挂载后:"+this.$refs.buttonRef);
},
beforeUpdate(){
console.log("更新前");
},
updated(){
console.log("更新后");
},
beforeUnmount(){
console.log("销毁前");
},
unmounted(){
console.log("销毁后");
}
}
script>
<template>
<h1>测试组件的生命周期h1>
<p ref="buttonRef">{{ message }}p>
<button @click="change()">单击更新button>
<ul>
<li v-for="(cus,index) in customer" :key="index">{{ cus }}li>
ul>
template>
上面的代码还包含了两个验证:
- 在mounted方法增加了对数组customer的数据初始化,这里正常是可以去做网络请求的,因为dom已经渲染结束了。打开页面可以看到数据已经正常出来了,说明没问题
- 点击更新按钮,更新了message这个变量的值,理论上会触发beforeUpdate、updated两个方法

总结:
初始化:初始化一个空的VUE实例对象,此时只有空的钩子函数和事件,其他都不存在
- 1.创建阶段:初始化data、methods等内容,这个阶段是解析VUE原始信息
间隙:在创建和挂载的间隙,VUE会对模板进行编辑,根据之前的data、methods等数据,这里会编译成一个模板字符串,然后将其渲染为内容中的DOM
- 2.挂载阶段:之前已经解析出了VUE的信息,并放在了内存,这个阶段将内存中的DOM替换到页面中
间隙:数据发生改变,数据响应到了data中,data中的数据为最新数据
- 3.更新阶段:根据data中的数据,在内存中从新渲染出一份新的DOM,然后将DOM信息更新到页面中
间隙:准备取消挂载,此时组件功能一切可用
- 4.将组件信息从挂载处取消
感觉生命周期还是很有用的,完全可以在mounted阶段去将网络请求提前做好,这样就做到了数据的提前加载,降低了数据渲染所需要的时间。
15 动态组件
可以根据条件动态更换展示哪个组件,这种感觉工作中肯定也会碰到的,代码如下:
<script>
import ComponentA from './components/ComponentA.vue'
import LifeCycle from './components/LifeCycle.vue';
export default{
components:{
ComponentA,
LifeCycle
},
data(){
return{
showComponet:"ComponentA"
}
},
methods:{
changeComponent(){
this.showComponet = this.showComponet=="ComponentA"?"LifeCycle":"ComponentA"
}
}
}
script>
<template>
<button @click="changeComponent">更换组件button>
<component :is="showComponet">component>
template>
总结:
- 1.在需要引入多个组件的组件内的模板内使用component标签,这是一个双标签
- 2.为component标签声明一个v-bind:is=“” ,这里声明使用哪个组件即可,这个组件可以是data中渲染的数据,这样就可以做成动态了
- 3.若是动态的,无论是声明初始值还是后来的更改值,组件名必须加引号,也就是字符串
- 4.被切换掉的组件会触发beforeunMount 和 unMounted方法
16 组件保持存活:keep-alive/KeepAlive
组件保持存活用起来很简单,直接在component外面加上keep-alive标签即可,该标签可以让组件被卸载时不被真正卸载,而是进入缓存,同时组件的数据也会被缓存,当组件切换回来时,数据还是之前的数据
<script>
import ComponentA from './components/ComponentA.vue'
import LifeCycle from './components/LifeCycle.vue';
export default{
components:{
ComponentA,
LifeCycle
},
data(){
return{
showComponet:"ComponentA"
}
},
methods:{
changeComponent(){
this.showComponet = this.showComponet=="ComponentA"?"LifeCycle":"ComponentA"
}
}
}
script>
<template>
<button @click="changeComponent">更换组件button>
<KeepAlive>
<component :is="showComponet">component>
KeepAlive>
template>
- 1.使用很简单,直接使用KeepAlive或者keep-alive标签均可以
- 2.使用该标签会缓存组件和数据,切换回来后数据使用的是缓存数据,不是默认数据
17 异步组件
这里的异步组件和java里的异步不是一个概念,这里的异步更像是懒加载,按照懒加载理解基本无误,默认情况VUE会在创建阶段加载全部的组件,然后在挂载阶段去对响应的组件进行挂载。使用异步组件后,组件的初始化和挂载会被迁移到需要的时候才处理(怎么感觉都是懒加载),这就是前端的异步的概念,突出一个不非阻塞的返回。
下面是异步组件的用法 :
- 从VUE中引入defineAsyncComponent:import { defineAsyncComponent } from ‘vue’;
- 使用defineAsyncComponent加载组件,使用该方法加载的组件就是异步组件了,声明的是组件名,和import 组件名 from ''的组件名没有区别
const LifeCycle = defineAsyncComponent(()=>
import("./components/LifeCycle.vue")
)
18 依赖注入
这里的依赖注入和java里的依赖注入有点类似,可以类比,同样的关键字inject。只需要在爷爷组件中在export中使用provide进行数据暴露即可,这个操作类似往池子中扔了一个数据,inject就可以从这个池子中获取这个数据了。
下面是爷爷组件信息:
<script>
import { defineAsyncComponent } from 'vue';
import ComponentA from './components/ComponentA.vue'
// import LifeCycle from './components/LifeCycle.vue';
const LifeCycle = defineAsyncComponent(()=>
import("./components/LifeCycle.vue")
)
export default{
components:{
ComponentA,
LifeCycle
},
data(){
return{
showComponet:"ComponentA",
injectMsg:"我是爷爷组件的数据"
}
},
methods:{
changeComponent(){
this.showComponet = this.showComponet=="ComponentA"?"LifeCycle":"ComponentA"
}
},
provide(){
return{
inMsg:this.injectMsg
}
}
}
script>
<template>
<button @click="changeComponent">更换组件button>
<KeepAlive>
<component :is="showComponet">component>
KeepAlive>
template>
下面是孙子组件代码:
孙子接收时的写法,既可以使用数组来直接接收,也可以和props写法保持一致,对类型、默认值、是否必须等进行限定
<script>
export default{
data(){
return{
msg22:"我是子组件内容1",
day:"我是子组件内容2"
}
},
props:{
msg:{
type:String,
default:"空",
required:true
}
}
,
inject:{
inMsg:{
type:String,
default:"我是爷爷组件的数据的默认值"
}
}
}
script>
<template>
<h1>ComponentB组件h1>
<slot name="slotA" :msg="msg22">
<a href ="#">这是默认内容a>
slot>
<p>{{ msg }}p>
<slot name="slotB" :day="day">
<a href ="#">这是默认内容a>
slot>
<p>{{ inMsg }}p>
template>
- props:父传子正常传递,子传父,可借助函数
- $emit: 通过自定义函数,实现子传父
- slot: 利用插槽的slogProps 属性对象来实现子传父,插槽本身就是父传子,不过传递的是DOM信息
- provide/inject: 依赖注入实现父传子/孙子等,实现跨组件传递数据,爷爷组件通过provide提供数据,孙子组件通过inject获取数据