Vue是什么
构用户界面的js的渐进式框架,基于htlm、css、js,并提供声明式组件化的编程模型,帮你高效开发用户界面。无论简单或复杂的界面Vue都可以胜任。
vue是一个框架,同时也是一个生态。因为有很多程序员支持vue,开发了很多如轮播图的东西就形成了一个生态。考虑到web世界的多样性,vue注重灵活性和可被逐步继承。可在一个完整项目中使用vue,或某一个功能上使用vue,或某一个html中使用vue的某个功能。
后期主要是一个完整的项目中使用Vue。
vue分为vue2和vue3。vue3涵盖了vue2,vue3增加了很多新的特性。
官网:cn.vuejs.org v2.cn.vuejs.org
Vue API风格
vue2和vue3新老版本的问题,代码书写风格不一样:选项式API(vue2)、组合式API(vue3)。
使用选项式API,我们可以用包含多个选项的对象来描述组件的逻辑,例如data 、methods 和(mounted。选项所定义的属性都会暴露在函数内部的this 上,它会指向当前的组件实例.
具体参见Vue2如Vue2基础入门_阳光明媚UPUP的博客-优快云博客
通过组合式API,我们可以使用导入的API函数来描述组件逻辑。
组合式api如:
这两种风格的api虽然写法完全不同,但是效果确实完全相同的。
当你不需要使用构建工具,或者打算主要在低复杂度的场景中使用Vue,例如渐进增强的应用场景,推荐采用选项式API
当你打算用Vue构建完整的单页应用,推荐采用组合式API+单文件组件
学习Vue前的准备
熟悉命令行
要求安装15.0或更高版本的nodejs cmd下node -v检测
nodejs简单了解
node.js:是一个开源的,跨平台的javaScript运行环境,简单来说它是一款应用程序,是一款软件,可运行javaScript。
nodejs可开发服务器端应用,nodejs就可在服务器中进行处理,将视频发给用户端。将资源返回给浏览器解析。nodejs就运行在服务器端,将用户的请求做处理,并把资源返回给浏览器。
nodejs可开发工具类应用如webpack、vite、babel,这三个工具可提高前端开发的效率和质量,都是借助于是借助于nodejs。
nodejs可以开发桌面端应用:vscoder、figma、postman,这三都借助于electron框架,electron又是借助于nodejs开发出来的。
nodejs下载安装
选中LTS长期支持版下载
旧版本可在中文网站中全部按转包中CNPM Binaries Mirror
下载.mis直接安装,途中不用勾选那个自动install的选框。
下载zip参考【转载】windows下Nodejs zip版下载安装及环境变量配置_nodezip版如何配置_AllTimeLow丶的博客-优快云博客
创建Vue项目
进入空文件夹,上方cmd敲,会进入到该目录,然后 npm init vue@latest
D:\VueProject>npm init vue@latest
Vue.js - The Progressive JavaScript Framework
√ Project name: ... vue-base 起个项目名,不要有大写字母
√ Add TypeScript? ... No / Yes //添加TS默认NO直接回车即可
√ Add JSX Support? ... No / Yes
√ Add Vue Router for Single Page Application development? ... No / Yes 添加路由,先no
√ Add Pinia for state management? ... No / Yes
√ Add Vitest for Unit Testing? ... No / Yes
√ Add an End-to-End Testing Solution? » No
√ Add ESLint for code quality? ... No / YesScaffolding project in D:\VueProject\vue-base... //全部no默认,然后回车即可。
Done. Now run:
cd vue-base
npm install
npm run dev
D:\VueProject>
然后执行它的提示代码即可:
先cd vue-base
然后执行cnpm install 也可安装cnpm后执行cnpm install
安装cnpm命令为npm install -g cnpm --registry=https://registry.npm.taobao.org
然后使用cnpm install
然后npm run dev运行项目 运行成功后复制地址到浏览器url输入访问即可。
也可使用vscoder打开一个空文件夹,然后ctrl+`在终端里敲上面的初始化代码等。
开发环境
vscoder+volar扩展
vscoder打开vue项目根目录
由于App.vue是字体为灰色,我们在vsccoder左侧扩展里加上一个vue language feature volar
这时App.vue就有颜色了
Vue项目目录结构
.vscode --- vscode工具的配置文件
node_modules --- Vue项目的运行依赖文件夹 执行npm install安装的就是它
public --- 资源文件夹(浏览器图标)
src --- 源码文件夹 进行编码的文件夹 写代码的文件夹
.gitignore --- git忽略文件
index.html --- 入口的HTML文件
package.json --- 信息描述文件 依赖vue3,开发环境dev详细依赖vite脚手架类似webpack
README. md --- 注释文件
vite.config.js --- vue配置文件 做配置,如跨域、打包
模板语法
基于html的模板语法,是我们能够声明式的将其组件实例的数据绑定到呈现的dom上,所有的vue模板都是语法层面合法的html,可被符合规范的浏览器和html解析器解析。
1、文本插值
最基本的数据绑定形式,采用双大括号mustache语法
我们可先删除vue项目src下的components目录下所有的东西,然后App.vue中只保留
<script> </script> <template> </template> template和script上下顺序可颠倒,template建议放在script上面。
我们在App.vue中写入
<template>
<h3>模板语法</h3>
<p>{{msg}}</p>
</template>
<script>
export default{
data(){
return{
msg:"神奇的语法"
}
}
}
</script>
重新运行终端npn run dev访问即可(后期保存自动更新页面)。发现还有样式剧中,样式在main.js中引入了资源文件夹assets文件夹下的main.css删除import语句即可。
assets为资源文件夹,日后公共css及实现都可放在这里,目前assets中资源直接删除掉即可。
2、使用js表达式
仅支持单一表达,即一个有结果的js表达式,可放在return语句中的。
不支持var,不支持for这种换行的表达式,可三元表达式。
<template>
<h3>模板语法</h3>
<p>{{msg.split("").reverse().join("")}}</p>
<p>{{number+1}}</p>
<p>{{ok?'yes':'no'}}</p>
</template>
<script>
export default{
data(){
return{
msg:"神奇的语法",
number:10,
ok:true
}
}
}
</script>
3、原始html
双大括号将会将数据插值为纯文本,而不是HTML。若想插入HTML,你需要使用v-html指令
<template>
<h3>模板语法</h3>
<p>{{url}}</p>
<p v-html="url"></p>
</template>
<script>
export default{
data(){
return{
url:"<a href='http://www.baidu.com'>百度</a>"
}
}
}
</script>
属性绑定
双大括号不能在HTML attributes 中使用。想要响应式地绑定一个attribute,应该使用v-bind指令。
App.vue中改成引入组件文件夹中的HelloWorld.vue
<script setup>
import HelloWorld from './components/HelloWorld.vue'
</script>
<template>
<HelloWorld/>
</template>
src下components下的HelloWorld.vue
<template>
<!-- v-bin:也可简写为:一个冒号 -->
<div v-bind:id="dynamicId" :class="msg" >测试</div>
<button :disabled="isButtonDisabled">button按钮</button>
<div v-bind="objectOfAttrs">一次性绑定多个属性</div>
</template>
<script>
export default{
data(){
return{
msg:"active",//active为选中的意思
dynamicId:"appId",
dynamicTitle:null,//如果为null或undefined,那么该attribute将会从渲染的元素上移
isButtonDisabled:false,
//也可一次性绑定多个值
objectOfAttrs:{
id:"appId",
class:"allClass"
}
}
}
}
</script>
刷新网页即可。所有的标签的属性都可通过这个v-bind为其绑定vue的data值
条件渲染
在vue中,提供了条件渲染,这类似于JavaScript中的条件语句
v-if、v-else、v-else-if、v-show
先src的components文件夹下创建ifDemo.vue
<template>
<h3>条件渲染</h3>
<div v-if="flag">你能看见我吗?</div>
<!-- v-if为假的时候显示 -->
<div v-else>那你还是看看我吧</div>
<div v-if="type === 'A'">A</div>
<div v-else-if="type==='B'">B</div>
<div v-else-if="type==='C'">C</div>
<div v-else>Not A/B/C</div>
<div v-show="flag">你能看见我吗?</div>
</template>
<script>
export default{
data(){
return{
flag : true,
type : "B"
}
}
}
</script>
在App.vue中引入:
<script setup>
import HelloWorld from './components/HelloWorld.vue'
import IfDemo from "./components/ifDemo.vue"
</script>
<template>
<HelloWorld />
<IfDemo />
</template>
v-if是基于对标签的渲染与否,v-show是基于display属性设置为true或false。
总的来说,ve-if有更高的切换开销,而v-show有更高的初始渲染开销。因此,如果需要频繁切换,则使用v-show较好;如果在运行时绑定条件很少改变,则v-if会更合适。
v.if 也是惰性的:如果在初次渲染时条件值为false,则不会做任何事。条件区块只有当条件首次变为true时才被渲染。
列表渲染
我们可以使用v-for指令基于一个数组来渲染一个列表。v-or指令的值需要使用item in items 形式的特殊语法,其中items是源数据的数组,而item是迭代项的别名
src下的components中创建ListDemo.vue
<template>
<h3>列表渲染</h3>
<p v-for="item in names">{{item}}</p>
<p v-for="(item,index) in names">{{item}}--{{ index }}</p>
<!-- 使用in和使用of都一样 -->
<p v-for="item of names">{{item}}</p>
<p v-for="(item,index) of names">{{item}}--{{ index }}</p>
<hr/>
<!-- 对于复杂的json -->
<div v-for="item of results">
<p>{{item.id}}</p>
<p>{{item.title}}</p>
<a :href="item.url">链接</a>
</div>
<!-- 对于一个对象也可遍历 -->
<div>
<p v-for="item in userInfo">{{ item }}</p>
<p v-for="(value,key,index) in userInfo">{{ value }}-{{ key }}--{{index}}</p>
</div>
</template>
<script>
export default{
data(){
return{
names:["张三","李四","王二麻子"],
//对于复杂的json数据
results:[
{
"id":1,
"title":"111hello你好",
"url":"http://www.baidu.com"
},
{
"id":2,
"title":"222hello你好",
"url":"http://www.baidu.com"
},
{
"id":3,
"title":"333hello你好",
"url":"http://www.baidu.com"
}
],
//对于一个对象也可以遍历
userInfo:{
name:"张三",
age:22,
sex:"男"
}
}
}
}
</script>
App.vue中引入
<script setup>
import HelloWorld from './components/HelloWorld.vue'
import IfDemo from "./components/ifDemo.vue"
import ListDemo from "./components/ListDemo.vue"
</script>
<template>
<HelloWorld />
<IfDemo />
<ListDemo />
</template>
通过key管理状态
Vue默认按照"就地更新"的策略来更新通过v-for渲染的元素列表。当数据项的顺序改变时,Vue 不会随之移动DOM元素的顺序,而是就地更新每个元素,确保它们在原本指定的索引位置上渲染
为了给Vue一个提示,以便它可以跟踪每个节点的标识,从而重用和重新排序现有的元素,你需要为每个元素对应的块提供一个唯一的key attribute:使用key可用节省开销。
src的component中创建KeyDemo.vue
<template>
<h3>key属性添加到v-for循环中</h3>
<p v-for="(item ,index) in names" :key="index">{{item}}</p>
<!-- 一般不推荐使用index作为key,一般使用result服务器返回的结果集中的id -->
<div v-for="item of result" :key="item.id">
<p>{{ item.name }}</p>
<a :href="url">链接</a>
</div>
</template>
<script>
export default{
data(){
return{
names:["aaa","sss","ddd"],
result:[
{
"id":1,
"name":"aaa",
"url":"http://www.baidu.com"
},
{
"id":2,
"name":"bbb",
"url":"http://www.baidu.com"
},
{
"id":3,
"name":"ccc",
"url":"http://www.baidu.com"
}
]
}
}
}
</script>
App.vue中引入
<script setup>
import HelloWorld from './components/HelloWorld.vue'
import IfDemo from "./components/ifDemo.vue"
import ListDemo from "./components/ListDemo.vue"
import keyDemo from "./components/KeyDemo.vue"
</script>
<template>
<HelloWorld />
<IfDemo />
<ListDemo />
<keyDemo />
</template>
事件处理
我们可以使用von指令(简写为 @)来监听DOM事件,并在事件触发时执行对应的JavaScript。用法:on:click="methodName”或@click="handler"
事件处理器的值可以是
内联事件处理器:事件被触发时执行的内联JavaScript语句(与onclick类似)方法事件处理器:一个指向组件上定义的方法的属性名或是路径
事件参数
事件参数可以获取event对象和通过事件传递数据
事件修饰符
在处理事件时调用event.preventDefault(或event stopPropagation()是很常见的。尽管我们可以直接在方法内调用,但如果方法能更专注于数据逻辑而不用去处理DOM事件的细节会更好
为解决这一问题,Vue为v-on提供了事件修饰符,常用有以下几个:
.stop 阻止事件冒泡
.prevent 阻止默认事件
.once4 事件只会被触发一次
.enter 回车按键触发的具体参考
地址:https://cn.vuejs.org/guide/essentials/event-handling.html#event-modifiers
demo如下
src下的component创建EventDemo.vue
<template>
<h3>事件处理</h3>
<!-- 内联事件处理器 -->
<button v-on:click="count++">(内联处理器)Add</button>
<p>{{ count }}</p>
<!-- 方法事件处理器 -->
<button @click="addNum">addNum</button>
<button @dblclick="addNum">addNum</button>
<p>{{ number }}</p>
<!-- 事件参数,传递event对象 -->
<button @click="addNum2">addNum222</button>
<p>{{ number }}</p>
<!-- 事件参数,传递事件参数 -->
<button @click="addNum3('hello')" >Add333</button>
<p>{{ number }}</p>
<p @click="getNamesHandler(item)" v-for="(item,index) of names" :key="index">{{ item }}</p>
<p @click="getNamesHandler2(item,$event)" v-for="(item,index) of names" :key="index">{{ item }}</p>
<!-- 阻止默认事件1,在click的方法中调用e.preventDefault(); -->
<a @click="clickHandle" href="http://www.baidu.com">百度链接</a><br/>
<!-- 阻止默认事件2,直接写-->
<a @click.prevent="clickHandle2" href="http://www.baidu.com">百度链接</a>
<!-- 阻止事件冒泡:事件冒泡就是如下 -->
<div @click="clickDiv">
<p @click="clickP">测试事件冒泡</p>
<!-- 还可通过.stop进阻止事件冒泡 -->
<p @click.stop="clickP">测试事件冒泡</p>
</div>
</template>
<script>
export default{
data(){
return{
count:0,
number:0,
names:["aaa","bbb","ccc"]
}
},
methods:{
addNum(){
this.number++;
},
addNum2(e){
console.log(e),
e.target.innerHTML = "Add222"+this.number
this.number++;
},
addNum3(msg){
console.log(msg),
this.number++;
},
getNamesHandler(item){
console.log(item)
},
getNamesHandler2(item,e){
console.log(e)
console.log(item)
},
clickHandle(e){
e.preventDefault();//这点击该a超链接标签后,就不会默认的跳转url事件了。
console.log("点击了")
},
clickHandle2(){
console.log("点击了")
},
clickDiv(){
console.log("DIV");
},
clickP(e){
//e.stopPropagation();//如果不加这个,就会出现事件冒泡,点击p中的测试事件冒泡clickP,会将div父标签的clickDiv也会执行。
console.log("P");
}
}
}
</script>
App.vue中加上
<script setup>
import HelloWorld from './components/HelloWorld.vue'
import IfDemo from "./components/ifDemo.vue"
import ListDemo from "./components/ListDemo.vue"
import keyDemo from "./components/KeyDemo.vue"
import EventDemo from "./components/EventDemo.vue"
</script>
<template>
<HelloWorld />
<IfDemo />
<ListDemo />
<keyDemo />
<EventDemo />
</template>
数组变化的侦测
1变更方法:Vue能够侦听响应式数组的变更方法,并在它们被调用时触发相关的更新。这些变更方法包括:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
2、替换一个数组
变更方法,顾名思义,就是会对调用它们的原数组进行变更。相对地,也有一些不可变
(immutable)方法,例如fiter),concat)和islice(),这些都不会更改原数组,而总是返回一个新数组。当遇到的是非变更方法时,我们需要将旧的数组替换为新的数组。
src下的components中创建ArraysDemo.vue
<template>
<h3>数组变化侦听</h3>
<button @click="addListHandle">添加数组</button>
<ul>
<li v-for="(item,index) of names" :key="index">{{ item }}</li>
</ul>
<button @click="concatHandle">合并数组</button>
<h3>数组1</h3>
<p v-for="(item ,index) of nums1" :key="index">{{ item }}</p>
<h3>数组2</h3>
<p v-for="(item ,index) of nums2" :key="index">{{ item }}</p>
</template>
<script>
export default{
data(){
return{
names:["aaa","bbb","ccc"],
nums1:[1,2,3,4,5],
nums2:[6,7,8,9,10]
}
},
methods:{
addListHandle(){
//引起ui自动更新
//this.names.push("ddd")
//不会引起ui自动更新
this.names = this.names.concat(["ddd"])
},
concatHandle(){
this.nums1 = this.nums1.concat(this.nums2)
}
}
}
</script>
App.vue中引入
<script setup>
import HelloWorld from './components/HelloWorld.vue'
import IfDemo from "./components/ifDemo.vue"
import ListDemo from "./components/ListDemo.vue"
import keyDemo from "./components/KeyDemo.vue"
import EventDemo from "./components/EventDemo.vue"
import ArrayDemo from "./components/ArrayDemo.vue"
</script>
<template>
<HelloWorld />
<IfDemo />
<ListDemo />
<keyDemo />
<EventDemo />
<ArrayDemo />
</template>
计算属性
模板中的表达式虽然方便,但也只能用来做简单的操作。如果在模板中写太多逻辑,会让模板变得臃肿,难以维护。因此我们推荐使用计算属性来描述依赖响应式状态的复杂逻辑。
src下的components中新建ComputedDemo.vue文件
<template>
<h3>{{ user.name}}</h3>
<p>{{ user.hobbies.length>0? 'yes':'no' }}</p>
<!-- 双花括号直接引用计算属性,不用加括号 -->
<p>{{ userContent }}</p>
<!-- 会使用传统的双花括号引一个函数,要加括号 -->
<p>{{ userContents() }}</p>
<p></p>
</template>
<script>
export default{
data(){
return{
user:{
name:"aaa",
hobbies:["h111","h222","h333"]
}
}
},
computed:{//计算属性,用的时候直接写名,不用加括号
userContent(){
return this.user.hobbies.length >0 ? 'Yes':'No'
}
},
methods:{
userContents(){//函数,用加括号
return this.user.hobbies.length >0 ? 'Yes':'No'
}
}
}
</script>
App.vue中引入
<script setup>
import HelloWorld from './components/HelloWorld.vue'
import IfDemo from "./components/ifDemo.vue"
import ListDemo from "./components/ListDemo.vue"
import keyDemo from "./components/KeyDemo.vue"
import EventDemo from "./components/EventDemo.vue"
import ArrayDemo from "./components/ArrayDemo.vue"
import computedDemo from "./components/computedDemo.vue"
</script>
<template>
<HelloWorld />
<IfDemo />
<ListDemo />
<keyDemo />
<EventDemo />
<ArrayDemo />
<computedDemo/>
</template>
计算属性缓存vs方法
你可能注意到我们在表达式中像这样调用一个函数也会获得和计算属性相同的结果:计算属性:计算属性值会基于其响应式依赖被缓存。一个计算属性仅会在其响应式依赖更新时才重新计算
方法:方法调用总是会在重渲染发生时再次执行函数
Class绑定
数据绑定的一个常见需求场景是操纵元素的CSS class列表,因为class.是attribute,我们可以和其他attribute一样使用v-bind 将它们和动态的字符串绑定。但是,在处理比较复杂的绑定时,通过拼接生成字符串是麻烦且易出错的。因此,Vue专门为class 的v-bind用法提供了特殊的功能增强。除了字符串外,表达式的值也可以是对象或数组
src下的component中创建ClassDemo.vue
<template>
<!-- 使用对象的方式绑定class,如果为true就加上该class -->
<p :class="{'active':isActive,'text-danger':hasError}">Class样视绑定1</p>
<!-- :class=一个对象 -->
<p :class="classObject">Class样视绑定2</p>
<!-- class绑定一个数组,数组中加什么class,就引入什么class -->
<p :class="[arrActive,arrHasError]">Class样视绑定3</p>
<!-- 数组加三目运算符,条件渲染 -->
<p :class="[isActive?'active':'']">Class样视绑定4</p>
<!-- 数组和对象嵌套过程中,只能是数组嵌套对象,不能反其道而行 -->
<p :class="[isActive?'active':'',{'text-danger' :hasError}]">Class样视绑定5</p>
</template>
<script>
export default{
data(){
return{
isActive:true,
hasError:true,
classObject:{
'active':true,
'text-danger':true
},
arrActive:"active",
arrHasError:'text-danger'
}
}
}
</script>
<style>
.active{
font-size: 30px;
}
.text-danger{
color: red;
}
</style>
App.vue中引入
<script setup>
import HelloWorld from './components/HelloWorld.vue'
import IfDemo from "./components/ifDemo.vue"
import ListDemo from "./components/ListDemo.vue"
import keyDemo from "./components/KeyDemo.vue"
import EventDemo from "./components/EventDemo.vue"
import ArrayDemo from "./components/ArrayDemo.vue"
import computedDemo from "./components/computedDemo.vue"
import ClassDemo from "./components/ClassDemo.vue"
</script>
<template>
<!-- <HelloWorld />
<IfDemo />
<ListDemo />
<keyDemo />
<EventDemo />
<ArrayDemo />
<computedDemo/> -->
<ClassDemo/>
</template>
style绑定
数据绑定的一个常见需求场景是操纵元素的CSS style列表,因为style是attribute,我们可以和其他
attribute一样使用v-bind将它们和动态的字符串绑定。但是,在处理比较复杂的绑定时,通过拼接生成字符串是麻烦且易出错的。因此,Vue专门为stye的v-ind用法提供了特殊的功能增强。除了字符串外,表达式的值也可以是对象或数组
src下的components里创建StyleDemo.vue,
<template>
<!-- style绑定——行内式的绑定 -->
<p :style="{color:activeColor,fontSize:fontSize+'px'}">Style绑定1</p>
<p :style="styleObject">Style绑定2</p>
<p :style="[styleObject]">Style绑定3</p>
</template>
<script>
export default{
data(){
return{
activeColor:"green",
fontSize:30,
styleObject:{
color:"red",
fontSize:"30px"
}
}
}
}
</script>
App.vue中引入:
<script setup>
import HelloWorld from './components/HelloWorld.vue'
import IfDemo from "./components/ifDemo.vue"
import ListDemo from "./components/ListDemo.vue"
import keyDemo from "./components/KeyDemo.vue"
import EventDemo from "./components/EventDemo.vue"
import ArrayDemo from "./components/ArrayDemo.vue"
import computedDemo from "./components/computedDemo.vue"
import ClassDemo from "./components/ClassDemo.vue"
import StyleDemo from "./components/StyleDemo.vue"
</script>
<template>
<!-- <HelloWorld />
<IfDemo />
<ListDemo />
<keyDemo />
<EventDemo />
<ArrayDemo />
<computedDemo/>
<ClassDemo/>-->
<StyleDemo/>
</template>
由于style绑定的权重高,后期修改麻烦,所以一把不推荐使用style,推荐使用class
侦听器
侦听数据的变化,我们可以使用watch选项在每次响应式属性发生变化时触发一个函数
src下组件包中创建WatchDemo.vue
<template>
<h3>侦听器</h3>
<p>{{message}}</p>
<button @click="updateHandle">修改值触发watch侦听</button>
</template>
<script>
export default{
data(){
return{
message:"hello"
}
},
methods:{
updateHandle(){
this.message="world"
}
},
watch:{
//第一个参数改变后的数据,第二个参数改变前的数据
message(newVaule,oldValue){//函数名必须与侦听的数据保持一致
console.log(newVaule,oldValue)
}
}
}
</script>
App.vue中引入。
表单输入绑定
在前端处理表单时,我们常常需要将表单输入框的内容同步给JavaScript 中相应的变量。手动连接值绑定和更改事件监听器可能会很麻烦,v-model指令帮我们简化了这一步骤。实时获取。
src下components中ModelDemo.vue
<template>
<h3>表单输入绑定</h3>
<form>
<input type="text" v-model="message"><br/>
<!-- 懒加载,点击搜索、回车、或失去焦点之类事件的时候才会将值传给data中的message属性 -->
<input type="text" v-model.lazy="message2"><br/>
<p>{{ message }}</p>
<p>{{ message2 }}</p>
<input type="checkbox" id="checkbox" v-model="checked"/>
<label for="checkbbox">{{ checked }}</label>
</form>
</template>
<script>
export default{
data(){
return{
message:"",
message2:"",
checked:false
}
}
}
</script>
v-model也提供了修饰符:
.lazy、
.number、.trim
.lazy默认情况下,v-model会在每次 input事件后更新数据。你可以添加lazy修饰符来改为在每次change事件后更新数据
模板引用
虽然Vue 的声明性渲染模型为你抽象了大部分对DOM的直接操作 ,如{内容改变:{{模板语法}}属性改变:v-bind:指令事件:v-on:click} ,是不需要直接操作dom的 ,但在某些情况下,我们仍然需要直接访问底层DOM完素。要实现这一点,我们可以使用特殊的ref attribute
挂载结束后引用都会被暴露在this.$refs之上
src打的组件包下RefDemo.vue
<template>
<div ref="container" class="container">{{ content }}</div>
<input type="text" ref="username"/>
<button @click="getElementHandle">获取元素</button>
</template>
<script>
export default{
data(){
return{
content:"内容"
}
},
methods:{
getElementHandle(){
console.log(this.$refs.container)
//innerHTML原生的js属性
console.log(this.$refs.container.innerHTML="哈哈哈")
console.log(this.$refs.username.value)
}
}
}
</script>
App.vue中引入。
如果没有特别的需求,不要操作DOM,操作dom既麻烦,又消耗性能。
组件组成
vue是组件化开发,所有内容都是由组件所组成的。一个项目可能有很多功能,每一个功能都是由一个完整的组件去实现。把这些功能或组件拼在一起形成一个完整的项目。
组件最大的优势就是可复用性
当使用构建步骤时,我们一般会将Vue组件定义在一个单独的.vue文件中,这被叫做单文件组件(简称SFC).由template承载所有的html标签,script承载所有的业务逻辑,style中是所有的样式写在这里。一个vue组件承载了所有的页面中所要呈现的内容。
VueProject目录下创建一个vue项目起名为vue-test,删除main.js中的css引用,删除assets下的内容,删除components下的内容,删除App.vue中的内容:App.vue中直接写内容为
<template>
<div class="container">{{ message }}</div>
</template>
<script>
export default{
data(){
return{
message:"组件的基本组成"
}
}
}
</script>
<style>
.container{
font-size: 30px;
color: red;
}
</style>
一个最基本的组件必须有template,其他script和style都可接着添加。
引入组件
或src下components包中写一个MyComponents.vue,然后在App.vue中引入
<template>
<div class="container">{{ message }}</div>
</template>
<script>
export default{
data(){
return{
message:"组件的基本组成"
}
}
}
</script>
<style>
.container{
font-size: 30px;
color: red;
}
</style>
App.vue
<template>
<!-- 第三步:显示组件 -->
<MyComponents/>
<!-- 也可使用烤肉串,会和驼峰自动转换的 -->
<my-components/>
</template>
<script>
//第一步:引入组件
import MyComponents from './components/MyComponents.vue'
//第二布:注入组件。如果这里不注入,也可在script标签中使用setup自动注入
export default{
components:{
MyComponents
}
}
</script>
<style>
</style>
组件style中的scope
如果MyComponent.vue组件中的style不加scoped,在App.vue中也会生效,如
<template>
<!-- 第三步:显示组件 -->
<MyComponents/>
<!-- 也可使用烤肉串,会和驼峰自动转换的 -->
<my-components/>
<div class="container" >组件style的scoped</div>
</template>
<script>
//第一步:引入组件
import MyComponents from './components/MyComponents.vue'
//第二布:注入组件。如果这里不注入,也可在script标签中使用setup自动注入
export default{
components:{
MyComponents
}
}
</script>
<style>
</style>
这时App.vue中引入container属性的div也会引用MyComponent.vue中的style。
需要在MyComponents中的style中加上scoped即可
<template>
<div class="container">{{ message }}</div>
</template>
<script>
export default{
data(){
return{
message:"组件的基本组成"
}
}
}
</script>
<!-- scoped让当前组件的样式仅在当前组件中生效 -->
<style scoped>
.container{
font-size: 30px;
color: red;
}
</style>
这时App.vue即使div引用了MyComponents.vue的container属性也不会生效。
组件的嵌套关系
组件允许我们将UI划分为独立的、可重用的部分,并且可以对每个部分进行单独的思考。在实际应用中,组件常常被组织成层层嵌套的树状结构
这和我们嵌套HTML元素的方式类似,Vue实现了自己的组件模型,使我们可以在每个组件内封装自定义内容与逻辑
创建一个新的vue的项目,删除asset包下和components包下所有东西,main.js删除css引用,src下创建page文件夹,创建Head.vue
<template>
<h3>Header</h3>
</template>
<script>
</script>
<style scoped>
/* 标签选择器 */
h3{
width: 100%;
height: 100px;
border: 5px solid #999;
text-align: center;
line-height: 100px;
box-sizing: border-box;
}
</style>
page创建Article.vue
<template>
<h3>Article</h3>
</template>
<style scoped>
h3{
width: 80%;
margin: 0 auto;
text-align: center;
line-height: 100px;
box-sizing: border-box;
margin-top: 50px;
background: #999;
}
</style>
page下创建Main.vue,并引用上面的Article组件
<template>
<div class="main">
<h3>Main</h3>
<Article/>
<Article/>
</div>
</template>
<script>
import Article from "./Article.vue"
export default{
components:{
Article
}
}
</script>
<style scoped>
.main{
float: left;
width:70%;
height: 600px;
border: 5px solid #999;
box-sizing: border-box;
}
</style>
page下创建Item.vue
<template>
<h3>Item</h3>
</template>
<style scoped>
h3{
width: 80%;
margin: 0 auto;
text-align: center;
line-height: 100px;
box-sizing: border-box;
margin-top: 10px;
background: #999;
}
</style>
page下创建Aside.vue,并引用上面的Item.vue
<template>
<div class="aside">
<h3>Aside</h3>
<Item/>
<Item/>
<Item/>
</div>
</template>
<script>
import Item from "./Item.vue"
export default{
components:{
Item
}
}
</script>
<style scoped>
.aside{
float: right;
width: 30%;
height: 600px;
border: 5px solid #999;
box-sizing:border-box;
}
</style>
App.vue中 引入
<script setup>
import Header from "./page/Header.vue"
import Main from "./page/Main.vue"
import Aside from "./page/Aside.vue"
</script>
<template>
<Header/>
<Main/>
<Aside/>
</template>
<style>
</style>
组件注册方式
即组件的引入方式。
一个Vue组件在使用前需要先被"注册”,这样Vue才能在渲染模板时找到其对应的实现。组件注册有两种方式:全局注册和局部注册
局部注册:上面demo中都是局部注册,只在某个组件中引入了其他组件,那个组件只能在当前组件中使用,在别的组件中还是不能使用的。
全局注册:在最外层注册一次,在所有组件中都可引用。
全局注册演示
还是解用上面组件嵌套的案例,App.vue中我们删除Header的局部引用,注释调
<script setup>
// import Header from "./page/Header.vue"
import Main from "./page/Main.vue"
import Aside from "./page/Aside.vue"
</script>
<template>
<Header/>
<Main/>
<Aside/>
</template>
<style>
</style>
在main.js中进行吧Header全局注册:
import { createApp } from 'vue'
import App from './App.vue'
import Header from './page/Header.vue'
const app = createApp(App);
// 全局注册的组件需要在create(App)和mounted中间写
app.component("Header",Header)
app.mount('#app')
全局注册虽然很方便,但有以下几个问题:
全局注册,但并没有被使用的组件无法在生产打包时被自动移除(也叫'tree-shaking')。如果你全局注册了一个组件,即使它并没有被实际使用,它仍然会出现在打包后的JS文件中。
全局注册在大型项目中使项目的依赖关系变得不那么明确。在父组件中使用子组件时,不太容易定位子组件的实现。和使用过多的全局变量一样,这可能会影响应用长期的可维护性
局部注册需要使用components选项
组件传递数据_Props
组件与组件之间不是完全独立的,而是有交集的,那就是组件与组件之间是可以传递数据的传递数据的解决方案就是props
src下components下:Parent.vue
<template>
<h3>Parent</h3>
<Child title="Parent数据" demo="demoHello"/>
<!-- 动态数据传递 -->
<Child :hello="message"/>
</template>
<script>
import Child from './Child.vue'
export default{
data(){
return{
message:"动态数据"
}
},
components:{
Child
}
}
</script>
src下components下Child.vue
注意事项:
props传递数据,只能从父级传递到子级,不能反其道而行
组件传递多种数据类型
通过props传递数据,不仅可以传递字符串类型的数据,还可以是其他类型,例如:数字、对象、数组等但实际上任何类型的值都可以作为props的值被传递
App.vue引入parent
<script setup>
import Parent from './components/Parent.vue'
</script>
<template>
<Parent/>
</template>
src的components里创建Parent.vue
<template>
<h3>Parent</h3>
<!-- 传递number类型 、数组类型 、对象类型-->
<Child :age="age" :names="names" :userInfo="userInfo"/>
</template>
<script>
import Child from './Child.vue'
export default{
data(){
return{
age:20,
names:["张三","李四","王五"],
userInfo:{
name:"zhangsan",
age:20
}
}
},
components:{
Child
}
}
</script>
src的components中创建Child.vue
<template>
<h3>Child</h3>
<p>{{ age }}</p>
<p>{{ names }}</p>
<ul>
<li v-for="(item,index) of names" :key="index">{{ item }}</li>
</ul>
<p>{{ userInfo.name }}</p>
<p>{{ userInfo.age }}</p>
</template>
<script>
export default{
data(){
return{
}
},
props:["age","names","userInfo"]
}
</script>
组件传递Props校验
1对类型是有校验的,2如果不传递会有默认值,3还可设置必选项(不传值报警告)
4prop是只读的,不可修改,只能父组件传递,子组件中不可修改该值
src下components中创建ComponentA和ComponentB,App.vue中引入A
<script setup>
import ComponentA from './components/ComponentA.vue'
</script>
<template>
<ComponentA/>
</template>
ComponentA中引入B,并向B传递值
<template>
<h3>ComponentA</h3>
<ComponentB :title="title" :names="names" />
</template>
<script>
import ComponentB from './ComponentB.vue'
export default{
data(){
return{
title:20,
names:["张三","李四"],
userObject:{
name:"王五",
age:20
}
}
},
components:{
ComponentB
}
}
</script>
ComponentB中接收校验等
<template>
<h3>ComponentB</h3>
<p>{{ title }}</p>
<p>{{age}}</p>
<p v-for="(item,index) of names" :key="index">{{ item }}</p>
<p>{{ userObject.name }}</p>
<p>{{ userObject.age }}</p>
</template>
<script>
export default{
data(){
return{
}
},
//props:["title"]
//B组件希望A组件传递过来的数据是某个类型的,可进行组件传递props校验
props:{
title:{
//type:String //如果A组件使用B组件,且传递过来的数据不是String类型,会在f12控制台中报错
//也可改为数据,这样下面4中类型都可以
type:[String,Number,Array,Object],
required:true
},
age:{
type:Number,
default:0,
//如果A组件给B组件传值了,就显示那个值,如果不传递值,就为显示默认default的值
required:true//如果设置required:true就为必选项,不加就会f12控制台报警告
},
//数字和字符串可以直接default,但是如果是数组和对象,必须通过工厂函数返回默认值
names:{
type:Array,
default(){//但是如果是数组和对象,必须通过工厂函数返回默认值,即一个函数defaul(){}
return ["空"]
}
},
userObject:{
type:Object,
default(){
return {
name:"王六",
age:21
}
}
}
}
}
</script>
组件事件
与事件处理的事件不同,组件事件是用来做组件之间的数据传递的。
在组件的模板表达式中,可以直接使用$emit方法触发自定义事件。触发自定义事件的目的是组件之间传递数据。
之前讲的数据传递是父传子,还有一种需求是子传父。就是组件事件$emit
srcd的components中创建ComponentEvent.vue
<template>
<h3>组件事件</h3>
<Child @someEvent="getHandle" @someEvent2="getHandle2"/>
<p>{{ message }}</p>
</template>
<script>
import Child from './Child.vue'
export default{
data(){
return{
message:""
}
},
methods:{
getHandle(){
console.log("触发了")
},
getHandle2(data){
console.log("触发了",data)
this.message=data
}
},
components:{
Child
}
}
</script>
srcd的components中创建Child.vue
<template>
<h3>Child</h3>
<button @click="clickEventHandle">传递数据</button>
<button @click="clickEventHandle2">传递数据2</button>
</template>
<script>
export default{
data(){
return{
msg:"Child!"
}
},
methods:{
clickEventHandle(){
this.$emit("someEvent")
},
clickEventHandle2(){
this.$emit("someEvent2",this.msg)
}
}
}
</script>
App.vue中引入即可。
组件事件-配合v-model使用
实现组件A中一边输入数据,在组件B中一边得到这个数据
$emit配合watch
实现子组件中国的数据通过watch和$emit发给父组件,父组件实时的接收该数据。
src的components中创建父组件Main.vue
<template>
<h3>Main</h3>
<p>{{ search }}</p>
<SearchComponent @searchEvent="getSearch"/>
</template>
<script>
import SearchComponent from './SearchComponent.vue';
export default{
data(){
return{
search:""
}
},
components:{
SearchComponent
},
methods:{
getSearch(data){
this.search=data
}
}
}
</script>
src的components中创建子组件SearchComponent.vue:
<template>
搜索:<input type="text" v-model="search"/>
</template>
<script>
export default{
data(){
return{
search:""
}
},
watch:{
search(newValue,oldValue){
this.$emit("searchEvent",newValue)
}
}
}
</script>
App.vue中引入即可。
组件数据传递
我们之前讲解过了组件之间的数据传递,props和自定义事件两种方式
props:父传子
自定义事件:子传父
出了上述的方案,props 也可以实现子传父
src下的components中加上ComponentA.vue 父组件
<template>
<h3>ComponentA</h3>
<ComponentB title="标题" :onEvent="dataFun" />
<!--1、 A传给B一个onEvent的函数,绑定A的dataFun方法-->
<p>{{ message }}</p>
</template>s
<script>
import ComponentB from './ComponentB.vue'
export default{
data(){
return{
message:""
}
},
components:{
ComponentB
},
methods:{
dataFun(data){//3、 onEvent绑定的函数为dataFun,且正好有一个参数data,就把B回传的参数放入该dataFun函数中
console.log(data)
this.message=data
}
}
}
</script>
src下的components中加上ComponentB.vue 子组件
<template>
<h3>ComponentB</h3>
<p>{{ title }}</p>
<p>{{ onEvent("子传父") }}</p>
<!--2、 B接收A的onEvent函数,并回传"子传父"数据到A的onEvent函数 -->
</template>
<script>
export default{
data(){
return{
}
},
props:{
title:String,
onEvent:Function
}
}
</script>
透传Attributes属性
了解即可,工作场景使用率极低。
“透传attribute"指的是传递给一个组件,却没有被该组件声明为props或emits的attribute或者v-on事件监听器。最常见的例子就是class 、style和iq
当一个组件以单个元素为根作渲染时,透传的attribute 会自动被添加到根元素上
src下的components中加上AttrComponent.vue
<template>
<!-- <h3>透传属性</h3> -->
<!-- 必须是唯一单个元素作为根元素渲染 -->
<h3>透传属性</h3>
</template>
<script>
export default{
// 如果加上inheritAttrs:false就是禁止继承透传的属性
//inheritAttrs:false
}
</script>
<style>
.attr-container{
color:red;
}
</style>
App.vue引入且加上透传属性
<script setup>
import AttrComponent from './components/AttrComponent.vue'
</script>
<template>
<AttrComponent class="attr-container"/>
</template>
插槽Slots
我们已经了解到组件能够接收任意类型的JavaScript值作为props,但组件要如何接收模板内容呢?在某些场景中,我们可能想要为子组件传递一些模板片段,让子组件在它们的组件中渲染这些片段。
<slot元素是一个插槽出口(slot outlet),标示了父元素提供的插槽内容(slot content)将在哪里被渲染
App.vue 父组件
<script setup>
import SlotsBase from './components/SlotsBase.vue'
</script>
<template>
<SlotsBase>
<h3>插槽标题</h3>
<p>插槽内容</p>
</SlotsBase>
</template>
src下的components中加上SlotBase.vue 子组件
<template>
<h3>插槽基础知识</h3>
<slot></slot>
<!-- 会在子组件中显示,通过更改<slot>位置来换位置 -->
</template>
1、渲染作用域:访问父组件动态内容
插槽内容可以访问到父组件的数据作用域,因为插槽内容本身是在父组件模板中定义的
src下的components中加上SlotTwo.vue 子组件
<template>
<h3>Slot续集</h3>
<slot></slot>
</template>
<script>
export default{
data(){
return{
}
}
}
</script>
App.vue 父组件
<script>
import SlotTwo from './components/SlotTwo.vue'
export default{
data(){
return{
message:"插槽标题",
inner:"插槽内容"
}
},
components:{
SlotTwo
}
}
</script>
<template>
<SlotTwo>
<!-- 插槽内容直接访问父组件的动态内容 -->
<!-- 插槽内容可以访问到父组件的数据作用域,因为插槽内容本身是在父组件模板中定义的 -->
<h3>{{ message }}</h3>
<p>{{ inner }}</p>
</SlotTwo>
</template>
2、默认内容
在外部没有提供任何内容的情况下,可以为插槽指定默认内容
src下的components中加上SlotTwo.vue 子组件
<template>
<h3>Slot续集</h3>
<slot>插槽默认值666</slot>
</template>
<script>
export default{
data(){
return{
}
}
}
</script>
App.vue 父组件
<script>
import SlotTwo from './components/SlotTwo.vue'
export default{
data(){
return{
message:"插槽标题",
inner:"插槽内容"
}
},
components:{
SlotTwo
}
}
</script>
<template>
<SlotTwo>
<!-- 插槽中如果不传递值,就会显示子组件中的写的插槽默认值 -->
<!-- <h3>{{ message }}</h3>
<p>{{ inner }}</p> -->
</SlotTwo>
</template>
3、具名插槽
子组件中可能有多个slot标签插槽
App.vue 父组件
<script>
import SlotTwo from './components/SlotTwo.vue'
export default{
data(){
return{
message:"插槽标题"
}
},
components:{
SlotTwo
}
}
</script>
<template>
<SlotTwo>
<!-- 具名插槽:1、插槽中的内容分别使用template加上v-slot:起个名字
v-slot:也可换成#
-->
<template v-slot:header>
<h3>{{ message }}</h3>
</template>
<template #main>
<h3>{{ message }}</h3>
</template>
</SlotTwo>
</template>
src下的components中加上SlotTwo.vue 子组件
<template>
<h3>Slot续集</h3>
<!-- 2、slot中根据name名称指定引用的插槽的部分内容 -->
<slot name="header">插槽默认值666</slot>
<hr>
<slot name="main">插槽默认值666</slot>
</template>
<script>
export default{
data(){
return{
}
}
}
</script>
4、插槽数据子传父
在某些场景下插槽的内容可能想要同时使用父组件域内和子组件域内的数据。要做到这
一点,我们需要一种方法来让子组件在渲染时将一部分数据提供给插槽
我们也确实有办法这么做!可以像对组件传递props那样,向一个插槽的出口上传递attributes
App.vue 父组件
<script>
import SlotAttr from './components/SlotAttr.vue'
export default{
data(){
return{
currentText:"slot再续集"
}
},
components:{
SlotAttr
}
}
</script>
<template>
<SlotAttr v-slot="slotProps">
<!-- 2`父元素中使用v-slot="slotProps"来接收子元素传来的数据, slotProps为对象包含子元素传来的msg
通过slotProps.msg渲染子元素传递来的数据
-->
<h3>{{ currentText }}-{{slotProps.msg}}</h3>
<!-- 当前为父传子 -->
</SlotAttr>
</template>
src下的components中加上SlotAttr.vue 子组件
<template>
<h3>Slot再续集</h3>
<slot :msg="childMessage"></slot>
<!-- 1`子元素内容先传给父元素通过一个:props来传递 -->
<!-- 3`父组件的插槽最终内容通过子组件的<slot>显示 -->
</template>
<script>
export default{
data(){
return{
childMessage:"子组件数据"//1`子元素内容先传给父元素
}
}
}
</script>
5、具名插槽数据子传父
App.vue 父组件
<script>
import SlotAttr from './components/SlotAttr.vue'
export default{
data(){
return{
currentText:"slot再续集"
}
},
components:{
SlotAttr
}
}
</script>
<template>
<SlotAttr>
<!-- #为v-slot: -->
<template #header="slotProps">
<h3>{{ currentText }}-{{slotProps.msg}}</h3>
</template>
<template #main="slotProps">
<h3>{{slotProps.inner}}</h3>
</template>
</SlotAttr>
</template>
src下的components中加上SlotAttr.vue 子组件
<template>
<h3>Slot再续集</h3>
<slot name="header" :msg="childMessage"></slot>
<slot name="main" :inner="inner"></slot>
</template>
<script>
export default{
data(){
return{
childMessage:"子组件数据",
inner:"内容内容"
}
}
}
</script>
组件生命周期
从组件被创建到组件被卸载。
每个Vue组件实例在创建时都需要经历一系列的初始化步骤,比如设置好数据侦听,编译模板,挂载实例到DOM,以及在数据改变时更新DOM。在此过程中,它也会运行被称为生命周期钩子的函数,让开发者有机会在特定阶段运行自己的代码
App.vue
<template>
<h3>组件生命周期</h3>
<p>{{message}}</p>
<button @click="updateHandle">更新数据</button>
</template>
<script>
/**
* 生命周期函数:8个声明周期函数,每一个生命周期函数都是自动执行的.
* 创建期: beforeCreate组件即将创建,初始化组件信息\created组件已经创建完成,还未显示再页面至上
* 挂载期: beforeMount准备渲染,把已经创建好的组件显示在页面上,渲染在dom上\mounted组件已经渲染完成,可被看到了
* 更新期: beforeUpdate当用户行为操作或业务逻辑导致页面数据发生变化的时候,开始渲染\updated渲染完成. 该部分可能多次循环
* 销毁期: beforeUnmount组件不需要的时候进入组件的卸载期\unmounted
*/
export default{
data(){
return{
message:"更新之前"
}
},
methods:{
updateHandle(){
this.message="更新之后"
}
},
beforeCreate(){
console.log("组件创建之前")
},
created(){
console.log("组件创建之后")
},
beforeMount(){
console.log("组件渲染之前")
},
mounted(){
console.log("组件渲染之后")
},
beforeUpdate(){
console.log("组件更新之前")
},
updated(){
console.log("组件更新之后")
},
beforeUnmount(){
console.log("组件销毁之前")
},
unmounted(){
console.log("组件销毁之后")
}
}
</script>
生命周期应用
组件的生命周期会随着我们对vue的了解越多,也会越来越重要,这里我们先讲两个常用的应用常见:
通过ref获取元素DOM结构
模拟网络请求渲染数据
src下的components中加上UserComponent.vue
<template>
<h3>组件生命周期函数应用</h3>
<p ref="name">百战程序员</p>
<ul>
<li v-for="(item,index) in banner" :key="index">
<h3>{{ item.title }}</h3>
<p>{{ item.content }}</p>
</li>
</ul>
</template>
<script>
export default{
data(){
return{
banner:[]
}
},
beforeCreate(){
//组件创建之前没有data,所以网络请求的数据应该放入create函数中
//也就是组件创建之后才有data中的banner,才能赋值
},
created(){//组件创建之后,模拟网络请求获取数据
//this.banner=[{title:"title1",content:"content1"},{title:"title2",content:"content2"},{title:"title3",content:"content3"}]
//还是放到mounted中比较好,因为mounted代表结构已经渲染完成了,再来渲染数据比较好,当刷购物网站有网络延迟时,结构比数据更重要
},
beforeMount(){
console.log(this.$refs.name);//undefined
},
mounted(){//通过ref获取元素DOM结构
console.log(this.$refs.name);
this.banner=[{title:"title1",content:"content1"},{title:"title2",content:"content2"},{title:"title3",content:"content3"}]
}
}
</script>
动态组件
有些场景会需要在两个或多个组件间来回切换,比如Tab界面。
现在显示A组件,通过用户的操作或行为显示B或C或D组件。
src下的components中加上ComponentA.vue
<template>
<h3>ComponentA</h3>
</template>
<script>
</script>
src下的components中加上ComponentB.vue
<template>
<h3>ComponentB</h3>
</template>
<script>
</script>
App.vue
<template>
<component :is="tabComponent"></component>
<button @click="changeHandle">切换组件</button>
</template>
<script>
import ComponentA from './components/ComponentA.vue'
import ComponentB from './components/ComponentB.vue'
export default{
data(){
return{
tabComponent:"ComponentA"
}
},
components:{
ComponentA,
ComponentB
},
methods:{
changeHandle(){
this.tabComponent= this.tabComponent=="ComponentA"?"ComponentB":"ComponentA"
}
}
}
</script>
组件保持存活
当使用<component is=...来在多个组件间作切换时,被切换掉的组件会被卸载。我们可以通过<keepalie>组件强制被切换掉的组件仍然保持"存活"的状态
src下的components中加上ComponentA.vue
<template>
<h3>ComponentA</h3>
<p>{{ message }}</p>
<button @click="updateHandle">更新数据</button>
</template>
<script>
export default{
data(){
return{
message:"老数据"
}
},
beforeUnmount(){
console.log("A组件被卸载前")
},
unmounted(){
console.log("A组件被卸载后")
},
methods:{
updateHandle(){
this.message="新数据"
}
}
}
</script>
src下的components中加上ComponentB.vue
<template>
<h3>ComponentB</h3>
</template>
<script>
</script>
App.vue
<template>
<!-- 需在需要切换的组件用<keep-alive>标签括起来,中间的<component>被切换掉的组件也不会被卸载 -->
<keep-alive>
<component :is="tabComponent"></component>
</keep-alive>
<button @click="changeHandle">切换组件</button>
</template>
<script>
import ComponentA from './components/ComponentA.vue'
import ComponentB from './components/ComponentB.vue'
export default{
data(){
return{
tabComponent:"ComponentA"
}
},
components:{
ComponentA,
ComponentB
},
methods:{
changeHandle(){
this.tabComponent= this.tabComponent=="ComponentA"?"ComponentB":"ComponentA"
}
}
}
</script>
如果App.vue中<component>不用<keep-alive>括起来,当里面组件被切换掉就会被卸载调,再切换回来会从头开始创建组件,这就是当A组件点击更新数据更新‘旧数据’为‘新数据’后,这时切换到B组件,再切换到 A组件,就会还是显示为旧数据。
当再App.vue中加上<keep-alive>括起来后,上面描述切到B组件再切回A组件,就还是上次A组件更新数据后的‘新数据’,即A组件没有被销毁重新创建。
异步组件
在大型项目中,我们可能需要拆分应用为更小的块,并仅在需要时再从服务器加载相关组件。Vue提供了defineAsyncComponent方法来实现此功能。
同步:A-B-C,一个个的按顺序执行
异步:A、B、C同时运行。
异步组件是优化项目的性能。
大型项目有很多组件,几百个组件,需要时从服务器加载组件,如果同步,一百个组件同时加载完才能运行项目。
异步组件是什么时候用到什么时候加载,这样第一次打开项目时会比较快,在以后的项目中较为常见。
当上面的动态组件和组件保持存活中的案例,都是打开网页,A和B组件都加载了,点击切换就没有网络请求了。我们还是接着上面的项目改成异步加载B组件:
修改上面案例的App.vue为:
<template>
<!-- 需在需要切换的组件用<keep-alive>标签括起来,中间的<component>被切换掉的组件也不会被卸载 -->
<keep-alive>
<component :is="tabComponent"></component>
</keep-alive>
<button @click="changeHandle">切换组件</button>
</template>
<script>
import ComponentA from './components/ComponentA.vue'
//import ComponentB from './components/ComponentB.vue'
//B组件的加载改为异步加载组件的方式
import { defineAsyncComponent } from 'vue'
const ComponentB =defineAsyncComponent(()=>
import("./components/ComponentB.vue")
)
export default{
data(){
return{
tabComponent:"ComponentA"
}
},
components:{
ComponentA,
ComponentB
},
methods:{
changeHandle(){
this.tabComponent= this.tabComponent=="ComponentA"?"ComponentB":"ComponentA"
}
}
}
</script>
这时第一次打开网页显示A组件的时候B组件不会被加载,当第一次切换到B组件的时候,B组件才会被加载。
异步组件日后会经常用到。
依赖注入
通常情况下,当我们需要从父组件向子组件传递数据时,会使用 props。想象一下这样的结构:有一些多层级嵌套的组件,形成了一颗巨大的组件树,而某个深层的子组件需要一个较远的祖先组件中的部分数据。在这种情况下,如果仅使用props则必须将其沿着组件链逐级传递下去,这会非常麻烦
App.vue
<template>
<h3>祖宗 </h3>
<Parent title="祖宗的数据"/>
</template>
<script>
import Parent from './components/Parent.vue'
export default{
components:{
Parent
}
}
</script>
Parent.vue
<template>
<h3>Parent</h3>
<Child :title="title"/>
</template>
<script>
import Child from './Child.vue';
export default{
components:{
Child
},
props:["title"]
}
</script>
Child.vue
<template>
<h3>Child</h3>
<p>{{ title }}</p>
</template>
<script>
export default{
components:{
},
props:{
title:{
type:String
}
}
}
</script>
provide和 inject可以帮助我们解决这一问题。一个父组件相对于其所有的后代组件,会作为依赖提供者。任何后代的组件树,无论层级有多深,都可以注入由父组件提供给整条链路的依赖
App.vue
<template>
<h3>祖宗 </h3>
<Parent/>
</template>
<script>
import Parent from './components/Parent.vue'
export default{
data(){
return{
title:"data中的爷爷的标题"
}
},
// provide:{//可通过这种方式直接传递
// message:"祖宗的数据"
// },
provide(){
return{//也可通过函数加return读取data中的数据进行传递
title:this.title,
message:"祖宗的数据"
}
},
// provide:{}和provide(){}优先级是provide(){}高,写了provide(){}对于provide:{}就不生效了
components:{
Parent
}
}
</script>
Parent.vue
<template>
<h3>Parent</h3>
<Child/>
</template>
<script>
import Child from './Child.vue';
export default{
components:{
Child
},
}
</script>
Child.vue
<template>
<h3>Child</h3>
<p>{{ message }}</p>
<p>{{ title }}</p>
<p>{{ fullMessage }}</p>
</template>
<script>
export default{
components:{
},
inject:["message","title"],
data(){
return{
fullMessage:this.message//inject中的数据也可使用this.的方式传给data中的数据
}
}
}
</script>
provide和inject只能由上到下的传递,不能反向传递
除了在一个组件中提供依赖,我们还可以在整个应用层面提供依赖
在main.js中provide提供依赖
import { createApp } from 'vue'
import App from './App.vue'
import Header from './page/Header.vue'
const app = createApp(App);
app.provide("globalData","我是全局数据")
app.mount('#app')
Child.vue
inject:["message","title","globalData"],
不管有没有关系,全局变量都可读取到
Vue应用
应用实例
每个Vue应用都是通过createApp函数创建一个新的应用实例
根组件
我们传入createApp的对象实际上是一个组件,每个应用都需要一个根组件,其他组件将作为其子组件。
挂载应用
应用实例必须在调用了.mount()方法后才会渲染出来。该方法接收一个"“容器"参数,可以是一个实际的DOM元素或是一个CSS选择器字符串
打开main.js
import { createApp } from 'vue'
import App from './App.vue'
// app:Vue的实例对象
// 在一个Vue项目中,有且只有一个Vue实例对象
const app = createApp(App);//App根组件
// App:根组件
app.mount('#app')
打开index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
里面有个div id="app">,这里才是入口。
浏览器可执行文件
html、css、js、图片。
vue通过类似webpack的vite,进行打包发布。所写的vue的代码最终会被编译为一个main.js文件。在htlm中将main.js引入进来,整个程序得以正常运行。<script type="module" src="/src/main.js"></script>
公共资源
在src目录下的assets文件夹的作用就是存放公共资源,例如:图片、公共CSS或者字体图标等