Vue学习笔记——步步高点读机,麻麻再也而不用担心我的Vue!
这是一个学习笔记,根据b站蔡蔡小趴菜的vue教学视频做的
注:CSDN会对段落内的html标签屏蔽,所以本文非代码块的html标签以缺半的形式展示
文章目录
Vue应用结构
Vue大致结构如下:
-
package-lock.json: 在使用 npm 安装包时自动生成的一个文件,它用于锁定当前项目的包的版本,以确保在不同的环境中安装的 包的版本一致
-
package.json: 项目的配置文件,包含了项目的依赖、脚本命令、版本号等信息。
-
README.MD: 说明
-
vite.config.js: Vite 构建工具的配置文件,它用于配置和定制 Vite 项目的构建行为和开发环境
-
public文件夹: 存放不需要经过Webpack处理的静态资源文件。这些文件会直接复制到构建目录中,并可以在HTML文件中
-
src文件夹: 源代码
-
assets文件夹: 用于存放应用需要的静态资源文件,如图片、字体、样式等。这些文件可以在组件中通过相对路径引用 (与public区别是会经过Webpack处理,如果部署在服务器上的话就把静态资源放在这里)
-
components文件夹: 存放vue组件
-
App.vue: vue的根组件
-
index.html: 这个文件里面是最基本的html页面代码,body中有
作为引入vue组件的入口 -
main.js:
import { createApp } from 'vue' //引入创建vue实例的方法 import App from './App.vue // App就是根组件 const app = createApp(App); // 创建 Vue 应用实例——基于根组件创建 app.mount('#app'); // 将应用挂载到 id 为 "app" 的 DOM 元素上(也就是index.html的入口)
-
view文件夹: 存放vue的页面文件,基于路由进行页面跳转
-
router文件夹: 路由文件夹
-
index.js: 路由配置文件,包括路由页面和对应路径
模板语法、属性绑定、条件渲染、列表渲染 忘记笔记了,反正很简单,跳过(
事件处理
事件处理分为内联事件处理器和方法事件处理器
事件通过 @click = "handler " 或者 v-on:click="methodName"触发
内联事件处理器
handler处是一段简单的Js代码,为一个可以返回值的表达式
<template>
<h3>内联处理</h3>
<button @click="count++">Add</button>
<p>{{ count }}</p>
</template>
<script>
export default {
data() {
return{
count:0
}
}
}
</script>
方法事件处理器
和内联事件处理不同,handler是一个方法名,调用methods:{}内的方法
PS:this.可以全局调用data(){}内的变量
<template>
<h3>内联处理</h3>
<button @click="addCount">Add</button>
<p>{{ count }}</p>
</template>
<script>
export default {
data() {
return{
count:0
},
methods:{
addCount(){
this.count++;
}
}
}
}
</script>
事件传参
也就是说methodName(variable) 可以传递参数
其中,$event 表示传递事件对象
<template>
<h3>事件传参</h3>
<!-- $event 表示事件传参传递了一个event对象 -->
<p @click="getNameHandler(item,$event)" v-for="(item,index) of names" key="index">{{ item }}</p>
</template>
<script>
export default {
// data里的值都可以用this来索引到
data() {
return{
names:["iwen","ime","frank"]
}
},
methods:{
addCount(msg) {
this.count++;
console.log(msg);
},
getNameHandler(name,e){
console.log(name);
console.log(e);
}
}
}
</script>
事件修饰符
这里跳过了,好像没啥用
数组变化侦测
Vue会检测到变更方法对数组的更改,并在页面上同步更新数组,
names.push("Alice")
而替换方法则需要按照以下代码形式使用:
this.numbers1 = this.numbers1.concat(this.numbers2);
会改变原数组的变更方法
- push()
- pop()
- shift()
- unshift()
- splice()
- sort()
- reverse()
不会改变原数组的替换方法
- filiter()
- concat()
- slice()
计算属性
就是:
computed:{
} 内的方法
计算属性的用途:
-
比如下面这个
标签内来判断itbanzhan.content的数组是否有内容
把三目运算符放在computed内成为方法,有利于代码阅读
-
性能优化
computed仅会计算一次,而methods内的方法每次调用都会执行一次
也就是说,当这个页面有10个
{{ itbaizhanContent }}
时,computed内的itbaizhanContent()也只会执行一次而methods内的会执行10次
<template>
<h3>{{ itbaizhan.name }}</h3>
<p>{{ itbaizhanContent }}</p>
</template>
<script>
export default{
data(){
return{
itbaizhan:{
name:"百战程序员",
content:["前端","Java","Python"]
}
}
},
//计算属性
computed:{
itbaizhanContent(){
return this.itbaizhan.content.length >0 ? "Yes" : "No";
}
}
}
</script>
Class绑定
跟CSS有关
class绑定还是基于 v-bond ,但是和普通v-bond不同的是
class的v-bond支持绑定数组和对象
<template>
<!-- 单对象绑定 -->
<p :class="{ active: isActive, 'text-danger': hasError }">Class 样式绑定1</p>
<!-- 多对象绑定 -->
<p :class="classObject">Class 样式绑定2</p>
<!-- 数组形式绑定 -->
<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>
Style绑定
和class绑定同理,v-bond增强至数组和对象
侦听器
侦听器可以侦听
data(){
message:“Hello”
}
内定义的变量变化
侦听器是与data()、methods同级的watch:{}
watch内的一个方法对应监听一个变量
且方法名与被监听变量名要一致
watch:{
message(newValue,oldValue){
}
}
<template>
<h3>侦听器</h3>
<button @click="updataHandle">修改数据</button>
</template>
<script>
export default{
data(){
return{
message:"hello"
}
},
methods:{
updataHandle(){
this.message = "World"
}
},
//侦听器
watch:{
//newVlaue是改变之后的数据
//oldValue是改变之前的数据
//函数名必须与侦听的数据对象保持一致
message(newVlaue,oldValue){
//数据发生变化,自动执行的函数
console.log(newVlaue,oldValue)
}
}
}
</script>
表单输入绑定
v-model
v-model设置在标签里,绑定一个data()内的变量message
绑定的变量message会同步和Input内容更新
<template>
<h3>表单输入绑定</h3>
<form>
<input type="text" v-model="message">
<p>{{ message }}</p>
<input type="checkbox" id="checkbox" v-model="checked">
</form>
<label for="checkbox">{{ checked }}</label>
</template>
<script>
export default{
data(){
return{
message:"",
checked:"false"
}
}
}
</script>
v-bond修饰符
.lazy——只有在change事件后才触发更新
.number——只能接受数字
.trim——消除空格
.lazy:
在输入过程中变量不会和输入一起更新,失去焦点这样的事件触发后才会同步更新
模板引用(DOM引用)
模板引用就是DOM引用
通过给标签设置属性 ref=“container”
后面可以在脚本中使用this.$refs.container,使用原生JS来操控该标签
<template>
<div ref="container" class="container">{{ content }}</div>
<button @click="getElementHandle">获取元素</button>
</template>
<script>
/**
* 内容改变: {{ 模板语法 }}
* 属性改变: v-bond
* 事件: @click=""
*
* 没有特殊需求,不要操控DOM
*/
export default {
data(){
return{
content:"内容"
}
},
methods:{
getElementHandle(){
// innerHTML:原生JS的属性
console.log(this.$refs.container.innerHTML = "hhhhh");
}
}
}
</script>
组件
组件组成
-
首先是文件构成和结构
Vue页面分为两种:主文件App.vue和Component文件夹内的.vue文件
Compnent内的.vue文件就是构成页面的组件
主文件和组件文件一般都包含以下三个部分:
<template> <!-- html --> </template> <script> // JavaScript </script> <style> /* css */ </style>
-
组件引入
下面是组件Mycomponent引入主文件App.vue的例子
MyComponent:
<template> <div class="container">{{ message }}</div> </template> <script> export default { data(){ return{ message:"组件基础组成" } } } </script> <style> .container{ font-size: 30px; color: brown; } </style>
App.vue
<template> <!-- 3.显示组件 --> <MyComponent/> </template> <script> //1. 引入组件 import MyComponent from './components/MyComponent.vue'; export default { //2. 注入组件 components:{ MyComponent } } </script> <style> </style>
-
style scoped
scoped的作用是限制生效作用域,一般用于组件内,使样式只能作用于本组件内的元素,
如果元素被引用,样式不会作用于引用页面的元素
举例:
对于一个组件
ComponentDemo.vue:
<template> <div class = 'title'>这是一个标题</div> </template> <style scoped> .title{ font-size = 50px; } </style>
APP.vue
<template> <div class = "title">App的标题</div> </template> <script> //1. 引入组件 import MyComponent from './components/MyComponent.vue'; export default { //2. 注入组件 components:{ MyComponent } } </script>
如果没有scoped,APP.vue内的 标题因为与组件的class都是 title,
会被组件内的style选择器命中,从而会有和组件标题同样的样式
组件嵌套关系
举个例子:
对于下图这个页面:
主要由五个组件构成:Header、Main、Asiede、Article和Item
引用结构:
------Header
App.vue ------Main -----Article
------Aside -----Item
Main组件内import了Article组件
Aside组件内Import了Item组件
App.vue则import了三个组件:Header、Main和Aside
以上便是组件的嵌套关系
组件的注册方式 ( 引入方式 )
分为全局注册和局部注册
局部注册:
就是之前的三步骤,将一个组件引入另一个组件
全局注册:
在main.js内注册,注册后全局都可以在内引入并直接使用
main.js :
import { createApp } from 'vue'
import App from './App.vue'
import Header from './pages/Header.vue'
const app = createApp(App)
//在这之间进行组件注册
// 注册语句
app.component("Header",Header)
app.mount('#app')
尽量使用局部注册,局部注册的父子组件依赖关系比较明确
组件传递数据_Props
- 传递在组件之间
- 只能父组件传递给子组件
- 可以传递任何类型的数据
- Props内的变量是只读的,无法被修改
步骤:在父组件的template中,对子组件的展示标签增加属性,这些属性变量就是要传递过去的值
如果仅以 title="Parent"这种形式赋值,传递过去的数据是String型静态数据
如果想要动态数据,或者是数字、数组和对象型数据,就需要用 :age:" " 进行属性绑定,在data内定义变量的类型和值
Parent.vue
<template>
<h3>Parent</h3>
<Child title="Parent 数据" demo = "测试" :test="msg" :age="age" :names="names" :people="people"/>
</template>
<script>
import Child from './Child.vue';
export default {
data(){
return{
msg:"动态Message",
age:20,
names:["li",'ime'],
people:{
name:"Ak",
age:16
}
}
},
components:{
Child
}
}
</script>
Child.vue
<template>
<h3>Child</h3>
<p>{{ title }}</p>
<p>{{ demo }}</p>
<p>{{ test }}</p>
<p>{{ age }}</p>
<ul>
<li v-for="(item,index) of names" :key="index">{{ item }}</li>
</ul>
<p>{{ people.name }} {{ people.age }}</p>
</template>
<script>
export default {
data(){
return{
}
},
props:["title","demo","test","age","names","people"]
}
</script>
组件传递Props校验 (对类型的校验、默认值和必选项的校验)
简单props以数组方式接受父组件传递过来的各个变量
如上面:props:[“title”,“demo”,“test”,“age”,“names”,“people”]
如果要对Props进行校验,则要改写成下面的格式:
<script>
export default {
data(){
return{
}
},
props:{
title:{
type:[String,Number,Array,Object],
required:true,
default:"Test"
},
names:{
type:[Array],
default(){
return ["空"]
}
}
}
}
</script>
type:[] 用数组来表示变量的类型限制,变量接收的数据只能是这几种
required: 表示该变量是否是必须的
default则为默认值…注意: 数字、字符串和布尔值可以直接default: ,但是其他类型要函数返回
组件事件 (可以实现子传父)
组件事件就是子组件methods中定义的一个方法,该方法内用this.$emit进行标识
举例:Child.vue 和 ComponentEvent.vue
Child.vue:
通过按钮触发chickEventHandle方法
该方法内的语句 this.$emit标识了该语句就是组件事件触发器
this.$emit() 方法接收一个或两个参数 ,第一个参数是父组件响应方法的方法名,第二个参数是传给父组件响应方法的参数(可选)
<template>
<h3>Child</h3>
<button @click="clickEventHandle">子按钮:传递数据</button>
</template>
<script>
export default {
data(){
return{
msg:"Child数据"
}
},
methods:{
clickEventHandle(){
//自定义事件
this.$emit("myEvent",this.msg)
}
}
}
</script>
ComponentEvent.vue
父组件内,下引用的子组件标签要以
@myEvent=“methodsname” 的形式接收组件事件
其中 myEvent是响应子组件的名字,methodsname是父组件要调用的方法
<template>
<h3>组件事件</h3>
<Child @myEvent="fatherHandle"/>
<p>{{ message }}</p>
</template>
<script>
import Child from './Child.vue';
export default {
components:{
Child
},
data(){
return{
message:""
}
},
methods:{
fatherHandle(data){
this.message = data
}
}
}
</script>
以上,子组件也可以简写为 @click直接调用匿名函数
<button @click="$emit('increaseBy', 1)">
Increase by 1
</button>
组件事件配合v-model
一般来说是子父组件的输入v-model绑定
比如子组件Search.vue中有一个输入框,当子组件输入框输入数据时,父组件Main.vue会同步展示
原理:
子组件input框v-model绑定一个变量 search,search与input输入同步更新
同时子组件新增一个watch侦听变量search,同时通过组件事件this.$emit将search每次更新的新值传递给父组件
Search.vue:
<template>
搜索:<input type="text" v-model="search">
</template>
<script>
export default {
data(){
return {
search:""
}
},
watch:{
search(newValue,oldValue){
this.$emit("MainHandle",newValue)
}
}
}
</script>
父组件在子组件标签内进行接收(这里用了简写的函数形式)
Main.vue
<template>
<h3>Main</h3>
<p>搜索内容为:{{ mes }}</p>
<Search @MainHandle="data=>this.mes = data"/>
</template>
<script>
import Search from './Search.vue';
export default {
data(){
return{
mes:""
}
} ,
components:{
Search
}
}
</script>
效果展示:
组件Props子传父 (函数方法)
原理:
父组件A,子组件B
A有一函数Afunction,Afunction接收到的参数会赋值给msg, 声明在组件B标签里 func = “Afunction”
B会通过props设定func为Function,调用func函数,给func赋值,从而实现B的值传递给A(子传父)
ComponentA.vue
<template>
<h3>ComponentA</h3>
<ComponentB :func="Afunction"/>
<p>A: {{ msg }}</p>
</template>
<script>
import ComponentB from './ComponentB.vue';
export default {
data() {
return {
msg:""
}
},
components:{
ComponentB
},
methods:{
Afunction(data){
this.msg = data;
}
}
}
</script>
ComponentB.vue
<template>
<h3>ComponentB</h3>
<p>{{ func("从B传递的数据") }}</p>
</template>
<script>
export default {
data() {
return {
}
},
props:{
func:Function
}
}
</script>
透传Atrribute
看着挺烦的
感觉这个功能会导致代码阅读混乱(
不学了
插槽
插槽基础
插槽实际就是在组件与组件之间,传递html模版,父组件传递给子组件
比如父组件想把 两行标题html代码传递给子组件,从而实现在子组件内展示该代码
实现原理:
父组件
父组件中对子组件标签的引用,由单标签变为双标签
比如子组件Chile.vue
则父组件中对子组件的引用就改为:
<Child
</Child
在两个子组件标签内放置要传递的Html模板内容
子组件
子组件在的某一位置放置
两个slot标签内部就是父组件传递过来的HTML模板代码,从而在子组件内展示
示例:
App.vue:
<template>
<SlotsBase>
<div>
<h3>插槽标题</h3>
<p>{{ msg }}</p>
</div>
</SlotsBase>
<SlotsTwo>
</SlotsTwo>
</template>
<script>
import SlotsBase from './components/SlotsBase.vue';
export default {
data() {
return {
msg:"插槽内容"
};
},
components: {
SlotsBase,
SlotsTwo
}
}
</script>
<style>
</style>
SlotBase.vue:
<template>
<h3>插槽基础知识</h3>
<slot></slot>
</template>
<script>
export default {
}
</script>
插槽作用域
子组件通过插槽可以访问父组件的数据作用域
(上图有显示)
如果父组件通过插槽传递了
//
{{msg}}
而父组件同时在data内定义了msg:“Message”
子组件是可以通过插槽,访问到父组件变量msg的值的
插槽默认内容
很简单,子组件的两个slot标签内的内容就是缺省显示在屏幕上的,如果插槽传递了模板,则不显示
默认值默认值默认值
具名插槽
插槽可以有名字
这种情况主要用于,父组件向子组件传递多个模板,子组件分别而不是一次性接收这些模板
先看一下下面这种情况
父组件:
<template>
<SlotsTwo>
<div>
<h3>插槽标题</h3>
<p>插槽内容</p>
</div>
</SlotsTwo>
</template>
<script>
import SlotsBase from './components/SlotsBase.vue';
import SlotsTwo from './components/SlotsTwo.vue';
export default {
data() {
return {
};
},
components: {
SlotsTwo
}
}
</script>
<style>
</style>
子组件:
<template>
<h3>Slot1</h3>
<slot></slot>
<h3>Slot2</h3>
<slot></slot>
</template>
<script>
export default {
}
</script>
<style>
</style>
父组件只有一对子组件标签,子组件有两对插槽出口标签
那么最终父组件传递的模板会在子组件中出现两次,如下图
下面进入具名插槽:
具名插槽就是给父组件中不同的插槽起名字,子组件通过插槽的名字锁定,在不同的位置接收插槽
父组件将每一组Slot用标签圈起来,在template标签中给名字
(v-slot)可以简写成#
template v-slot:Slot1>
…
</template
<template #:Slot2
…
</template
子组件则通过
slot name=“Slot1”>
</slot
<slot name = “Slot2”
分别接收
父组件:
<template>
<SlotsTwo>
<template v-slot:Slot1>
<h3>插槽标题</h3>
</template>
<template #Slot2>
<p>插槽内容</p>
</template>
</SlotsTwo>
</template>
<script>
import SlotsTwo from './components/SlotsTwo.vue';
export default {
data() {
return {
};
},
components: {
SlotsTwo
}
}
</script>
<style>
</style>
子组件
<template>
<h3>Slot1</h3>
<slot name="Slot1"></slot>
<h3>Slot2</h3>
<slot name="Slot2"></slot>
</template>
<script>
export default {
}
</script>
<style>
</style>
效果:
插槽数据子传父
原理就是,子组件在标签内增加一个属性存储子组件的一个变量
比如下面的例子
子组件的插槽出口设置了一个属性 sname
这个属性可供父组件调用
子组件:
<template>
<h3>插槽基础知识</h3>
<slot :sname="content"></slot>
</template>
<script>
export default {
data(){
return{
content:"艾克"
}
}
}
</script>
父组件这里,因为子组件只有一个插槽,父组件设定了子组件插槽的名字: v-slot = “slotp”
此时slotp代表子组件的插槽出口,slotp.sname就是在调用子组件插槽出口的属性
父组件:
<template>
<SlotsBase v-slot="slotp">
<h3>{{ slotp.sname }}</h3>
</SlotsBase>
</template>
<script>
import SlotsBase from './components/SlotsBase.vue';
export default {
data() {
return {
};
},
components: {
SlotsBase
}
}
</script>
插槽数据子传父(具名情况下)
具名就是多一个指定名字
原理就是,在基础具名的基础上,#slot1=“slotProps”
这个赋值实际上是给插槽接口赋了个别名,如果不这样赋直接用slot1.msg是无法调用的
父组件
<template>
<SlotsAttr>
<template #slot1="slotProps">
<h3>{{ slotProps.msg }}</h3>
</template>
<template #slot2="slotMsg">
<h3>{{ slotMsg.content }}</h3>
</template>
</SlotsAttr>
</template>
<script>
import SlotsAttr from './components/SlotsAttr.vue';
export default {
data() {
return {
};
},
components: {
SlotsAttr,
}
}
</script>
子组件
<template>
<h3>子组件传递数据</h3>
<slot name="slot1" :msg="childMessage"></slot>
<h3>内容如下</h3>
<slot name="slot2" :content="content"></slot>
</template>
<script>
export default {
data(){
return{
childMessage:"这是标题",
content:"这是内容"
}
}
}
</script>
效果:
组件的生命周期
组件有四个生命周期:
组件创建期、组件挂载期(就是组件渲染在页面的时期)、组件更新期(比如通过按钮触发data的一个变量改变)、组件销毁期
- 生命周期函数
- 创建期: beforCreated created (created的时候页面内组件被创建出来)
- 挂载期: beforeMount mounted (mounted的时候页面DOM才成型)
- 更新期: beforeUpdate updated
- 销毁期: beforeUnmount unmounted
<template>
<h3>组件的生命周期</h3>
<p>{{ msg }}</p>
<button @click="this.msg='更新之后'">更新按钮</button>
</template>
<script>
/**
* 生命周期函数
* 创建期: beforCreated created
* 挂载期: beforeMount mounted
* 更新期: beforeUpdate updated
* 销毁期: beforeUnmount unmounted
*/
export default {
data(){
return{
msg:"更新之前"
}
},
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>
上面代码的效果:
生命周期的应用
首先补充一个知识:
可以在标签内定义DOM结构引用
// <p ref = “name”
则可以用this.$refs.name来引用这个p标签结构
<template>
<h3>组件生命周期函数应用</h3>
<p ref="name">百战程序员</p>
</template>
<script>
export default {
beforeMount(){
console.log(this.$refs.name)
},
mounted(){
console.log(this.$refs.name)
}
}
</script>
这里的知识点是,在页面mounted(渲染)之前,DOM树未形成,所以 this.$refs.name无法获取节点
在一个前端项目中,一般是网页结构先成型,再填充数据,
所以一般网络请求都放置在mounted() 里面
动态组件
原理:
vue提供了一个用于展示组件的特殊标签<component
<component :is=“tabComponent”
tabComponent变量存储组件名称
可以通过按钮改变tabComponent变量的内容,从而实现组件改变
注意下面三元运算符的运用(
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>
ComponentA.vue
<template>
<h3>ComponentA</h3>
</template>
<script>
export default {
}
</script>
ComponentB.vue
<template>
<h3>ComponentB</h3>
</template>
<script>
export default {
}
</script>
组件保持存活
蛮重要的
主打一个keep-alive标签
在上一小节中, component :is="tabComponent"标签执行后,组件会从DOM树卸载,也就是进入销毁期
<keep-alive
component :is=“tabComponent”>
</keep-alive
通过keep-alive标签的设定,组件被切换后不会销毁,而是会保持存活
举例
上一小节的代码中仅ComponentA改变一点:
增加一个 message变量和数据更新按钮
<template>
<h3>ComponentA</h3>
<p>{{ message }}</p>
<button @click="updateHandle">数据更新</button>
</template>
<script>
export default {
data(){
return{
message:"老数据"
}
},
methods:{
updateHandle(){
this.message = this.message =="老数据" ? "新数据" : "老数据"
}
}
}
</script>
效果展示:
下面为没有的情况:
初始状态 数据更新 组件转换
从初始状态点击数据更新,A组件的变量变为新数据
再点击组件转换,组件转为ComponentB
转换为B后,再点击组件转换,切回A
注意ComponentA还是老数据
下面为App.vue中对组件标签环绕了<keep-alive的情况:
按照上面一样的流程
组件转换为B后,再点击组件转换
注意到ComponentA的数据还是新数据,保持不变
总结:
不进行组件存活保持的话
组件卸载后,数据会被销毁
再使用组件切换,组件会被重新装载,此时组件的data会被重新初始化为原来的值
如果组件存活保持的话,反之
异步组件
正常情况下,一个页面内会将import的组件同时加载(即使这个组件当前不展示,比如切换之前的B组件),在页面上看是网页会一次性请求所有vue组件文件
在大型项目中,为了能够加快项目打开速度,会使用异步组件。也就是说暂时用不到的组件在页面初始化的时候不会加载,而是在组件会被用到的时候被加载
实现只需要在引入的时候改一下引入方式即可:
import { defineAsyncComponent } from 'vue';
const ComponentB = defineAsyncComponent(() =>
import("./components/ComponentB.vue")
)
此时会发现,B组件的请求是在点击组件切换的时候才会发出的
依赖注入
依赖注入是为了解决,props参数传递在多级组件嵌套(比如三层父子嵌套)中数据需要沿着组件关系链多次传递的问题
语法就是
上层 provide一个变量
下层 inject 一个接收
相当于跳过传递链,设置一个中转站
下面举个例子
假设 组件链: App<-- A <-- B <-- C
ComponentA.vue
<template>
<h3>ComponentA</h3>
<ComponentB />
</template>
<script>
import ComponentB from './ComponentB.vue';
export default {
data(){
return{
msgA:"A的数据(动态)"
}
},
provide(){
return{
message1:this.msgA,
message2:"A的数据(静态)"
}
},
components:{
ComponentB
},
}
</script>
ComponentC.vue
<template>
<h3>ComponentC</h3>
<h3>下面C会接收A的数据</h3>
<p>{{ message1 }}</p>
<p>{{ message2 }}</p>
</template>
<script>
export default {
inject:["message1","message2"]
}
</script>
效果展示:
在父组件A中 使用了函数式返回值:
provider(){
return{
}
}
这样可以保证静态数据和Data动态数据都可以注入到子组件中
另一种写法只能保证静态:
provider:{
}
路由
路由基础
路由是基于视图的
视图——view,可以看作不同url的页面,由组件组成
http://localhost:8080/
http://localhost:8080/about 这是两个不同的view
路由的作用就是实现view之间的跳转
实现路由需要以下几步:
-
main.js 文件夹内引入router,vue实例使用router
// main.js import { createApp } from 'vue' import App from './App.vue' import router from './router' // .use(router) 声明要使用路由功能 createApp(App).use(router).mount('#app')
-
路由的前提是需要有视图view,src文件夹下创建views文件夹
在views文件夹内新建视图文件 HomeView.vue 和 AboutView.vue
-
src文件夹下创建 router 文件夹 ,router文件夹内创建 index.js
该文件是路由的配置文件,这里又要分几步:
- 引入 vue-router
- 引入路由页面
- 写路由配置信息(路径和对应页面)
- 创建路由
- 导出路由
// index.js // 1.引入 vue-router import {createRouter,createWebHistory} from "vue-router" // 2.引入需要路由的页面 import HomeView from '../views/HomeView.vue' import AboutView from '../views/AboutView.vue' // 配置信息中需要页面的相关配置 // 3.路由页面配置 const routes = [ { path:'/', component:HomeView }, { path:'/about', component:AboutView } ] // 4.创建路由 const router = createRouter({ /** * createWebHashHistory * home: http://localhost:5173/#/ * about: http://localhost:5173/#/about * * createWebHistory * home: http://localhost:5173 * about: http://localhot:5173/about * * */ history:createWebHistory(), routes }) // 5.导出路由 export default router带参路由
-
App.vue内进行路由展示和路由链接
<template> <!-- 路由跳转 --> <router-link to="/">首页</router-link> | <router-link to="/about">关于</router-link> <!-- 路由的显示入口 --> <router-view></router-view> </template> <script> export default { } </script>
带参路由
带参路由比较抽象,字面意思是跳转页面的时候会携带参数
拿例子来说
比如在新闻社分类里面有:百度新闻、网易新闻、头条新闻和搜狐新闻四种链接
点进每一个链接就会展示对应新闻社的介绍,也就是说
这四个链接点击任何一个,都会跳进同一个页面,但是可以通过携带他们对应的参数,从而向后端发送不同的请求来获取页面内容
假设我们新建一个页面 NewsDetailsView.vue
下面是带参路由的配置:
-
index.js
// 注意到在newsdetails路由后面多了一个key的设置,这个key就作为存储参数的变量,后面接收的时候也使用这个key来接收 import { createRouter, createWebHistory } from 'vue-router' import HomeView from '../views/HomeView.vue' // 首页直接引入,速度快 // 其他页面异步引入 const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: [ { path: '/', name: 'home', component: HomeView }, { path: '/about', name: 'about', // route level code-splitting // this generates a separate chunk (About.[hash].js) for this route // which is lazy-loaded when the route is visited. component: () => import('../views/AboutView.vue') }, { path: '/news', name: 'news', // 这是异步加载方式,节省内存空间 component: () => import('../views/NewsView.vue') }, { // /:name是key path: '/newsdetails/:name', name: 'newsdetails', component: () => import('../views/NewsDetailsView.vue') } ] }) export default router
-
NewView.vue(引用了路由链接的页面)
<-- 可以看到下面的to内的传参方式 --> <template> <h3>新闻</h3> <ul> <li><router-link to="/newsdetails/百度">百度新闻</router-link></li> <li><router-link to="/newsdetails/网易">网易新闻</router-link></li> <li><router-link to="/newsdetails/头条">头条新闻</router-link></li> <li><router-link to="/newsdetails/搜狐">搜狐新闻</router-link></li> </ul> </template>
-
NewDetailsView.vue(路由接收参数页面)
可以看到接收方法是 $route.params.name
<template> <h3>新闻详情</h3> <p> {{ $route.params.name }}</p> </template>
嵌套路由
应用就是,二级导航菜单
简单的说就是index.js里面的一级路径属性里面加一个child
不想写了直接上链接
// idnex.js
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/about',
name: 'about',
//重定向,就是默认跳转
redirect:"/about/us",
// route level code-splitting
// this generates a separate chunk (About.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import('../views/AboutView.vue'),
children:[
{
//二级导航的路径不要加 '/'
path:"info",
component:() => import("../views/AboutSub/AboutInfo.vue")
},
{
path:"us",
component:() => import("../views/AboutSub/AboutUs.vue")
}
]
}
]
})
export default router
这里还有一个隐藏知识点
redirect 重定向 也就是默认会跳转到那里