Vue3的基本语法
Vue3新特性
1. API 风格
Vue2 是选项API(Options API),一个逻辑会散乱在文件不同位置(data、props、computed、watch、生命周期钩子等),导致代码的可读性变差。当需要修改某个逻辑时,需要上下来回跳转文件位置。
Vue3 组合式API(Composition API)则很好地解决了这个问题,可将同一逻辑的内容写到一起,增强了代码的可读性、内聚性,其还提供了较为完美的逻辑复用性方案。
Options API
使用选项式 API,我们可以用包含多个选项的对象来描述组件的逻辑,例如 data
、methods
和 mounted
。选项所定义的属性都会暴露在函数内部的 this
上,它会指向当前的组件实例。
<script>
export default {
// data() 返回的属性将会成为响应式的状态
// 并且暴露在 `this` 上
data() {
return {
count: 0
}
},
// methods 是一些用来更改状态与触发更新的函数
// 它们可以在模板中作为事件监听器绑定
methods: {
increment() {
this.count++
}
},
// 生命周期钩子会在组件生命周期的各个不同阶段被调用
// 例如这个函数就会在组件挂载完成后被调用
mounted() {
console.log(`The initial count is ${this.count}.`)
}
}
</script>
<template>
<button @click="increment">Count is: {{ count }}</button>
</template>
疑问1:为什么@click里面的increment不需要this.increment呢?methods里面的属性不是会暴露到实例上面吗,刚刚试了,@click="this.increment"
组合式 API (Composition API)
在单文件组件中,组合式 API 通常会与 <script setup>
搭配使用。这个 setup
attribute 是一个标识,告诉 Vue 需要在编译时进行一些处理,让我们可以更简洁地使用组合式 API。
<script setup>
import { ref, onMounted } from 'vue'
// 响应式状态
const count = ref(0)
// 用来修改状态、触发更新的函数
function increment() {
count.value++
}
// 生命周期钩子
onMounted(() => {
console.log(`The initial count is ${count.value}.`)
})
</script>
<template>
<button @click="increment">Count is: {{ count }}</button>
</template>
使用ref
包裹之后会变成响应式的数据,我们之后也会介绍
2. 重写双向绑定
3. Vue3优化dom
4 .Vue3 Fragment
5. Vue3 Tree shaking
项目搭建
nvm
nvm安装
我们可以通过nvm来管理我们的node版本,方便切换不同的node版本 但是nvm有些坑,总结如下:
- nvm 路径不能有空格和中文
- 如果你已经有Node版本了,你需要把当前版本收录进去,或者直接卸载掉,用nvm重新安装一遍
- 用1.1.7版本,不要用1.1.9最新版本
- install版本失败,有可能需要给权限,cmd右键管理员启动即可
感谢b站的这位同学
nvm命令
# 查看已经安装的node版本
nvm list
# 查看所有的node版本
nvm list available
# 安装node
nvm install 16.3.2(版本号)
# 切换node版本
nvm use 12.17.0
使用vite构建
npm init vue@latest
使用vue脚手架工具构建
使用vue脚手架会自动内置了vue-router
, pinia
,更适合vue的开发
npm init vue@latest
模板语法
文本插值
在script 声明一个变量可以直接在template 使用用法为 {{ 变量名称 }}
<template>
<div class="about">
<h1>{{ message }}</h1>
</div>
</template>
<script setup lang="ts">
const message = "文本插值语法";
</script>
模板语法是可以编写条件运算的
<template>
<div class="about">
<h1>{{ message.length > 5 ? "一高" : "一快" }}</h1>
</div>
</template>
<script setup lang="ts">
const message = "文本插值语法";
</script>
运算符也是可以支持的
<template>
<div class="about">
<h1>{{ message + "运算符" }}}</h1>
</div>
</template>
<script setup lang="ts">
const message = "文本插值语法";
</script>
操作API 也是支持的
<template>
<div class="about">
<h1>{{ message.split("").reverse() }}</h1>
</div>
</template>
<script setup lang="ts">
const message = "文本插值语法";
</script>
Attribute 绑定
任何 Vue 指令 (以 v-
开头的特殊 attribute) attribute 的值中
v-text
<span v-text="msg"></span>
<!-- 等同于 -->
<span>{{msg}}</span>
v-text
通过设置元素的 textContent 属性来工作,因此它将覆盖元素中所有现有的内容。同时设置v-text和标签中的内容会报错
<template>
<div v-text="message">同时使用会报错</div>
</template>
<script setup lang="ts">
const message = "v-text用法";
</script>
v-html
<template>
<div v-html="message"></div>
</template>
<script setup lang="ts">
const message = "<div>innnerHtml</div>";
</script>
v-if
v-if后面的值为真时,才会渲染,当条件改变时会触发过渡效果。
v-if
v-else-if
v-else
v-show
v-show
通过设置内联样式的 display
CSS 属性来工作,当元素可见时将使用初始 display
值。当条件改变时,也会触发过渡效果。但是v-if
会把整个节点变成一个注释节点,v-show的性能会更好一点
<template>
<div v-show="bol">v-show为true显示</div>
</template>
<script setup lang="ts">
const bol = true;
</script>
v-on
v-on:
可以简写为@
当用于普通元素,只能监听原生DOM事件
。当用于自定义元素组件,则监听子组件触发的自定义事件
<!-- 方法处理函数 -->
<button v-on:click="doThis"></button>
<!-- 动态事件 -->
<button v-on:[event]="doThis"></button>
<!-- 内联声明 -->
// $event是一个event对象
<button v-on:click="doThat('hello', $event)"></button>
<!-- 缩写 -->
<button @click="doThis"></button>
<!-- 使用缩写的动态事件 -->
<button @[event]="doThis"></button>
<!-- 停止传播 -->
<button @click.stop="doThis"></button>
<!-- 阻止默认事件 -->
<button @click.prevent="doThis"></button>
<!-- 不带表达式地阻止默认事件 -->
<form @submit.prevent></form>
<!-- 链式调用修饰符 -->
<button @click.stop.prevent="doThis"></button>
<!-- 按键用于 keyAlias 修饰符-->
<input @keyup.enter="onEnter" />
<!-- 点击事件将最多触发一次 -->
<button v-on:click.once="doThis"></button>
<!-- 对象语法 -->
<button v-on="{ mousedown: doThis, mouseup: doThat }"></button>
v-bind
动态的绑定一个或多个 attribute,也可以是组件的 prop。
缩写::
<!-- 绑定 attribute -->
<img v-bind:src="imageSrc" />
<!-- 内联字符串拼接 -->
<img :src="'/path/to/images/' + fileName" />
v-model
在表单输入元素或组件上创建双向绑定。 期望的绑定值类型:根据表单输入元素或组件输出的值而变化
-
仅限:
<input>
<select>
<textarea>
- components
v-for
<template>
<div v-for="item in arr">{{ item }}</div>
</template>
<script setup lang="ts">
v-once
仅渲染元素的组件一次,并跳过之后的更新
<template>
<button @click="increase"></button>
<div>{{ val.count }}</div>
</template>
<script setup lang="ts">
import { ref } from "@vue/reactivity";
const val = ref({ count: 2 });
const increase = () => {
console.log('你更新呀');
val.value.count++;
};
</script>
当我们点击的时候,数值可以正常的更新,但是加了v-once
之后,仅渲染元素和组件一次,并跳过之后的更新。再点击更新count的值元素不会进行更新
<template>
<button @click="increase"></button>
<div v-once>{{ val.count }}</div>
</template>
<script setup lang="ts">
import { ref } from "@vue/reactivity";
const val = ref({ count: 2 });
const increase = () => {
console.log('你更新呀');
val.value.count++;
};
</script>
v-memo
ref全家桶
ref
Vue 提供了一个 ref()
方法来允许我们创建可以使用任何值类型的响应式 ref
ref接受一个内部值并返回一个响应式且可变的 ref 对象。ref 对象仅有一个 .value
property,指向该内部值。
const count = ref(0)
console.log(count) // { value: 0 }
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
如果ref是一个文本插值(即一个{{ }}符号)计算的最终值,它也被将解包,不需要再.value
上取值:
{{ object.foo }}
{{ object.foo.value }}
ref 被传递给函数或是从一般对象上被解构时,不会丢失响应性:
好像自动解包哪些官方文档还没有看明白
ifRef
判断是不是一个ref对象
import { ref, Ref,isRef } from 'vue'
let message: Ref<string | number> = ref("我是message")
let notRef:number = 123
const changeMsg = () => {
message.value = "change msg"
console.log(isRef(message)); //true
console.log(isRef(notRef)); //false
}
shallowRef
ref()
的浅层作用形式。
const state = shallowRef({ count: 1 })
// 不会触发更改
state.value.count = 2
// 会触发更改
state.value = { count: 2 }
特别注意
-
shallowRef只响应到.value,如果这个.value后面在跟值进行修改,就只能在控制台中看见值改变了,而没办法响应到页面中
-
也就是创建一个跟踪自身
.value
变化的 ref,但不会使其值也变成响应式的 -
ref不能跟shallowRef一起使用,不然shallowRef会被Ref影响,从而造成视图的更新(失去浅层次响应的作用)
triggerRef()
triggerRef()会强制更新我们收集的依赖
const shallow = shallowRef({
greet: 'Hello, world'
})
shallow.value.greet = 'Hello, universe'
// 会强制更新
triggerRef(shallow)
customRef()
创建一个自定义的 ref,显式声明对其依赖追踪和更新触发的控制方式。
customRef()
预期接收一个工厂函数作为参数,这个工厂函数接受 track
和 trigger
两个函数作为参数,并返回一个带有 get
和 set
方法的对象。
一般来说,track()
应该在 get()
方法中调用,而 trigger()
应该在 set()
中调用。然而事实上,你对何时调用、是否应该调用他们有完全的控制权。
<template>
<div>customerRef: {{ obj }}</div>
<button @click="change"></button>
</template>
<script setup lang="ts">
import { customRef, ref } from "vue";
const val = ref({ count: 2 });
const obj = MyRef('customerRef')
function change() {
obj.value = 'customer 修改了'
}
function MyRef(value) {
return customRef((tracker, trigger) => {
return {
get() {
// 收集依赖
tracker();
// 收集完返回出去
return value;
},
set(newVal) {
// newVal是收到的新的值
value = newVal;
// 触发依赖更新
trigger();
},
};
});
}
</script>
认识Reactive全家桶
reactive
ref支持所有类型,reactive支持引用类型,Array,Object,Map,Set
ref取值是需要.value的,reactive取值的话不需要.value
reactive 基础用法
import { reactive } from 'vue'
let person = reactive({
name:"小满"
})
person.name = "大满"
数组异步赋值问题
let person = reactive<number[]>([])
setTimeout(() => {
person = [1, 2, 3]
console.log(person);
},1000)
这样赋值页面是不会变化的因为会脱离响应式
解决方法
使用push方法
import { reactive } from 'vue'
let person = reactive<number[]>([])
setTimeout(() => {
const arr = [1, 2, 3]
person.push(...arr)
console.log(person);
},1000)
包裹一层对象
type Person = {
list?:Array<number>
}
let person = reactive<Person>({
list:[]
})
setTimeout(() => {
const arr = [1, 2, 3]
person.list = arr;
console.log(person);
},1000)
shallowReactive
只能对浅层的数据 如果是深层的数据只会改变值 不会改变视图,和shallowRef差不多
认识to全家桶
toRef
toRef只能修改响应式对象的值并且视图发生变化,非响应式的值可以修改,但是视图没有变化
<template>
<div>
<button @click="change">按钮</button>
{{ state }}
</div>
</template>
<script setup lang="ts">
import { reactive, toRef } from "vue";
// 必须加上reactive,才能是响应式,否则toRef没有作用
const obj = reactive({
foo: 1,
bar: 1,
});
// bar 转化为响应式对象,相当于解构
const state = toRef(obj, "bar");
const change = () => {
state.value++;
console.log(obj, state);
};
</script>
toRefs
可以帮我们批量创建ref对象主要是方便我们解构使用
import { reactive, toRefs } from 'vue'
const obj = reactive({
foo: 1,
bar: 1
})
let { foo, bar } = toRefs(obj)
foo.value++
console.log(foo, bar);
toRaw
将响应式对象转化为普通对象
toRaw()
可以返回由 reactive()、readonly() 、shallowReactive() 或者 shallowReadonly() 创建的代理对应的原始对象。
computed计算属性
计算属性就是当依赖的属性的值发生变化的时候,才会触发它的更改,如果依赖值不变,使用的是缓存中的值
函数形式
import { computed, reactive, ref } from 'vue'
let price = ref(0)//$0
let m = computed<string>(()=>{
return `$` + price.value
})
price.value = 500
对象形式
<template>
<div>{{ mul }}</div>
<div @click="mul = 100">click</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
let price = ref<number | string>(1)//$0
let mul = computed({
get: () => {
return price.value
},
set: (value) => {
price.value = 'set' + value
}
})
</script>
<style>
</style>
官方文档案例
<script setup>
import { reactive, computed } from 'vue'
const author = reactive({
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
})
// 一个计算属性 ref
const publishedBooksMessage = computed(() => {
return author.books.length > 0 ? 'Yes' : 'No'
})
</script>
<template>
<p>Has published books:</p>
<span>{{ publishedBooksMessage }}</span>
</template>
不使用计算属性
<p>Has published books:</p>
<span>{{ author.books.length > 0 ? 'Yes' : 'No' }}</span>
如果不使用计算属性的话,需要我们在模板中写很长的一段代码,如果我们在多处地方使用的话,可不希望每个地方都写那么长,所以计算属性的作用就体现出来了
计算属性默认是只读的。当你尝试修改一个计算属性时,你会收到一个运行时警告。只在某些特殊场景中你可能才需要用到“可写”的属性,你可以通过同时提供 getter 和 setter 来创建
watch监听器
watch可以是异步的,compute是不能有副作用的,刚学还不太懂区别,打个疑问三
基本配置
watch
的第一个参数可以是不同形式的“数据源”:它可以是一个 ref (包括计算属性)、一个响应式对象、一个 getter 函数、或多个数据源组成的数组
注意,你不能直接侦听响应式对象的属性值,例如:
const obj = reactive({ count: 0 })
// 错误,因为 watch() 得到的参数是一个 number
watch(obj.count, (count) => {
console.log(`count is: ${count}`)
})
这里需要用一个返回该属性的 getter 函数:
// 提供一个 getter 函数
watch(
() => obj.count,
(count) => {
console.log(`count is: ${count}`)
}
)
watch
第二个参数是一个回调函数 cb(newVal, oldVal)
watch
第三个参数是一个options对象,属性如下
interface WatchOptions extends WatchEffectOptions {
immediate?: boolean // 默认:false
deep?: boolean // 默认:false
flush?: 'pre' | 'post' | 'sync' // 默认:'pre'
onTrack?: (event: DebuggerEvent) => void
onTrigger?: (event: DebuggerEvent) => void
}
immediate
:在侦听器创建时立即触发回调。第一次调用时旧值是undefined
。deep
:如果源是对象,强制深度遍历,以便在深层级变更时触发回调。参考深层侦听器。flush
:调整回调函数的刷新时机。参考回调的刷新时机及watchEffect()
。onTrack / onTrigger
:调试侦听器的依赖。参考调试侦听器。
案例
侦听单个数据源
<template>
<div>
case1:<input v-model="message" type="text">
<hr>
case2:<input type="text">
</div>
<h1>{{tom}}</h1>
</template>
<script setup lang="ts">
import {ref} from "vue";
let message = ref<string>('小满')
//监听器第一个参数:侦听的数据源(sources) 第二个参数 回调函数 cb(newVal,oldVal)
watch(message,(newVal,oldVal)=>{
console.log('新的值----', newVal);
console.log('旧的值----', oldVal);
})
</script>
<style scoped>
</style>
侦听多个数据源
<template>
<div>
case1:<input v-model="message" type="text">
<hr>
case2:<input v-model="message2" type="text">
</div>
<h1>{{tom}}</h1>
</template>
<script setup lang="ts">
import {ref} from "vue";
let message = ref<string>('小满')
let message2 = ref<string>('喜多川学姐')
//使用数组的形式侦听多个数据源,返回的结果也会变成数组,结果顺序 按照 监听顺序
watch([message,message2],(newVal,oldVal)=>{//此时新值旧值也会变成一个数组
console.log('新的值----', newVal);
console.log('旧的值----', oldVal);
})
</script>
<style scoped>
</style>
监听 Reactive
使用 reactive 监听深层对象开启和不开启 deep 效果一样
深层次监听啦啦啦,需要给reactive开启第三个属性option(是一个配置项) => 里面有一个deep就是用来代表深度监听的
//监听语句变成如下
case1:<input v-model="message.nav.bar.name" type="text">
import { ref, watch ,reactive} from 'vue'
let message = ref({//reactive已经隐性开启deep了,不需要再手动开启了
nav:{
bar:{
name:"学姐好好吃饭"
}
}
})
watch([message,message2],(newVal,oldVal)=>{//此时新值旧值也会变成一个数组
console.log('新的值----', newVal);
console.log('旧的值----', oldVal);
},{deep:true})//深度监听
通过控制台打印,我们发现了其中的Proxy下的深层次监听
此时会发现有一个问题,那就是旧值跟新值的内容是一样的(原因是因为引用类型返回的新值是跟旧值一样的)
我们在使用的时候会发现一个问题,那就是我只改变了一个值,为什么没有改变的那个值(例如上面的喜多川学姐
)也跟着带出来了,我只想要得到改变的值,那要怎么办到呢? => 简洁版提问:想要侦听单一属性
- Vue推荐是让我们把要监听的变成一个函数(而不是直接.xxx.xxx得到的字符串,这样会报错的,因为不是Proxy所代理的对象)
- 创建一个回调函数去返回这个要侦听的属性
-
//区别就是()=>message.value.nav.bar.name跟单纯message.value.nav.bar.name,后者会报错 watch(()=>message.value.nav.bar.name,(newVal,oldVal)=>{//此时新值旧值也会变成一个数组 console.log('新的值----', newVal); console.log('旧的值----', oldVal); },{deep:true})
watchEffect高级侦听器
立即运行一个函数,同时会自动
响应式地追踪其依赖,并在依赖更改时重新执行。
如果用到message 就只会监听message 就是用到几个监听几个 而且是非惰性 会默认调用一次
let message = ref<string>('')
let message2 = ref<string>('')
watchEffect(() => {
//console.log('message', message.value);
console.log('message2', message2.value);
})
清除副作用
就是在触发监听之前会调用一个函数可以处理你的逻辑
import { watchEffect, ref } from 'vue'
let message = ref<string>('')
let message2 = ref<string>('')
watchEffect((oninvalidate) => {
//console.log('message', message.value);
oninvalidate(()=>{
})
console.log('message2', message2.value);
})
停止监听
const unwatch = watchEffect(() => {})
// ...当该侦听器不再需要时
unwatch()
配置项
副作用刷新时机 flush 一般使用 post
pre | sync | post | |
---|---|---|---|
更新时机 | 组件更新前执行 | 强制效果始终同步触发 | 组件更新后执行 |
onTrigger 可以帮助我们调试 watchEffect
import { watchEffect, ref } from 'vue'
let message = ref<string>('')
let message2 = ref<string>('')
watchEffect((oninvalidate) => {
//console.log('message', message.value);
oninvalidate(()=>{
})
console.log('message2', message2.value);
},{
flush:"post",
onTrigger () {
}
})
生命周期
onBeforeMount() 在组件DOM实际渲染安装之前调用。在这一步中,根元素还不存在。
onMounted() 在组件的第一次渲染后调用,该元素现在可用,允许直接DOM访问
onBeforeUpdate() 数据更新时调用,发生在虚拟 DOM 打补丁之前。
onUpdated() DOM更新后,updated的方法即会调用。
onBeforeUnmount() 在卸载组件实例之前调用。在这个阶段,实例仍然是完全正常的。
onUnmounted() 卸载组件实例后调用。调用此钩子时,组件实例的所有指令都被解除绑定,所有事件侦听器都被移除,所有子组件实例被卸载。
父子组件传参
父组件传给子组件
-
父组件通过 v-bind 绑定个数据,然后子组件通过 defineProps 接受传过来的值(字符串数据类型不需要v-bind)
-
子组件接受值,通过 defineProps 来接受 defineProps 是无须引入的直接使用即可(defineProps有返回值,返回值就是接收到的props属性)
父组件
<template>
<div>
<AboutPage :title="message" />
</div>
</template>
<script setup lang="ts">
import AboutPage from './views/AboutView.vue'
const message = '父传子'
</script>
<style scoped></style>
子组件
<template>
<div>
我是about页面, {{ title }}
</div>
</template>
<script setup lang="ts">
defineProps({
title: {
type:String,
default:'父传子的默认值',
}
})
</script>
<style scoped></style>
在script中需要使用父组件传给子组件的title时,需要用变量来接受defineProps的返回值
<script setup lang="ts">
const props = defineProps({
title: {
type:String,
default:'父传子的默认值',
}
})
// 直接使用title不行
console.log(props.title)
</script>
使用ts的话可以直接用泛型来定义,更加的简便
<script setup lang="ts">
defineProps<{
title:string
}>()
</script>
父传子使用ts设置默认值
withDefaults(defineProps<{
arr: number[]
}>(),{
arr:()=>[666]
})
子组件传递父组件
-
这时候需要用到
defineEmit
,这个内置函数里参数是一个数组,数组里面放的是我们自定义的名称(这个名称会在父组件中用到),这里我们使用一个常量接住 -
接受的常量里面有两个参数要填写,第一个是我们自定义的参数,也就是刚刚在defineEmit中写到的,这里则是要将其绑定起来
派发
到父组件中,第二个参数则是我们要派发出去的内容(也就是从子组件到父组件的内容) -
下一步我们就要到父组件中去接收了,我们这里要通过事件进行接收(也就是@),还记得我们刚刚说会在父组件名称中用到的自定义名称吗?没错,这里要用到了,@在子组件自定义名称="在父组件自定义名称"
-
父组件自定义名称要在父组件中script中使用,父组件中自定义名称的内容则可以通过子组件触发
看代码吧!!!
子组件
<template>
<div>
我是about页面, {{ title }}
</div>
<button @click="handleClick"></button>
</template>
<script setup lang="ts">
defineProps<{
title:string
}>()
// 先注册我们自定义事件的名称,名叫success,待会父组件中会使用到
const emits = defineEmits(['success'])
// 触发点击事件的时候,进行派发
const handleClick = () => {
// 第一个参数是注册的事件,第二个参数是传递的值
emits('success','子组件给父组件传递数据')
}
</script>
父组件
子组件传递给父组件的值,可以通过函数的参数进行获取
<template>
<div>
<AboutPage :title="message" @success="getValue"/>
</div>
</template>
<script setup lang="ts">
import AboutPage from './views/AboutView.vue'
const message = '父传子'
function getValue(value) {
console.log(value);
}
</script>
使用ts设置
const emits = defineEmits<{
(e: 'success', name:string) :void
// 可以设置多个
}>()
const handleClick = () => {
emits('success','子组件先父组件传递数据')
}
defineExpose子组件暴露自己的属性和方法
父组件
<template>
<div>
<AboutPage ref="aboutRef" :title="message" @success="getValue"/>
<button @click="show"></button>
</div>
</template>
<script setup lang="ts">
import AboutPage from './views/AboutView.vue'
import {ref} from "vue";
const aboutRef = ref<InstanceType<typeof AboutPage> >()
function show() {
console.log(aboutRef.value.name);
}
</script>
子组件
defineExpose({
name:"子组件暴露的属性"
})
全局组件/局部组件/递归组件
配置全局组件
在main.ts 引入我们的组件跟随在createApp(App) 后面 切记不能放到mount 后面这是一个链式调用用
其次调用 component 第一个参数组件名称 第二个参数组件实例
import { createApp } from 'vue'
import App from './App.vue'
import './assets/css/reset/index.less'
import Card from './components/Card/index.vue'
createApp(App).component('Card',Card).mount('#app')
也可以批量注册全局组件
可以参考element ui 其实就是遍历一下然后通过 app.component 注册
配置局部组件
配置递归组件
type TreeList = {
name: string;
icon?: string;
children?: TreeList[] | [];
};
const data = reactive<TreeList[]>([
{
name: "no.1",
children: [
{
name: "no.1-1",
children: [
{
name: "no.1-1-1",
},
],
},
],
},
{
name: "no.2",
children: [
{
name: "no.2-1",
},
],
},
{
name: "no.3",
},
]);
<div v-for="item in data">
<input type="checkbox"><span>{{item.name}}</span>
<Tree v-if="item?.children?.length" :data="item.children"></Tree>
</div>
改变递归组件的组件名,在增加一个script通过export添加name
<script lang="ts">
export default {
name:"TreeItem"
}
</script>
动态组件
适用场景:tab标签页的切换
小tip: ??操作符只处理undefined和null
<template>
<div style="display: flex">
<div @click="switchCom(item,index)" :class="[active == index ? 'active' : '']" class="tabs" v-for="(item,index) in data">
v-for里的index是索引
class是可以写两个的,一个动态一个静态,但不能两静态或者两动态
<div>{{item.name}}</div>
</div>
</div>
<component :is="comId"></component> 默认展示这个A组件了,component是Vue的内置组件
</template>
<script setup lang='ts'>
import { ref, reactive } from 'vue'
import WaterFall from "./components/water-fall.vue";
import A from "./components/A.vue"
import B from "./components/B.vue"
import C from "./components/C.vue"
const comId = ref(A)
const active = ref(0)
const data = reactive([
{
name:"A组件",
com:A
},
{
name:"B组件",
com:B
},
{
name:"C组件",
com:C
}
])
const switchCom = (item:any,index:any)=>{
comId.value = item.com,//要切换的组件
active.value = index//切换的索引
}
</script>
<style lang='less'>
.active{
background: skyblue;
}
.tabs{
border: 1px solid #ccc;
padding: 5px 10px;
margin: 5px;
cursor: pointer;
}
</style>
slot插槽
teleport
<Teleport>
是一个内置组件,它可以将一个组件内部的一部分模板“传送”到该组件的 DOM 结构外层的位置去。
通过to 属性 插入指定元素位置 to="body" 便可以将Teleport
内容传送到指定位置
<Teleport to="body">
<Loading></Loading>
</Teleport>
也可以通过disabled来控制to属性是否生效
使用disabled 设置为 true则 to属性不生效 false 则生效
<teleport :disabled="true" to='body'>
<A></A>
</teleport>
keep-alive缓存组件
有时候我们不希望组件被重新渲染影响使用体验;或者处于性能考虑,避免多次重复渲染降低性能。而是希望组件可以缓存下来,维持当前的状态。这时候就需要用到 keep-alive
组件。
keep-alive就是保持活跃的意思,你在里面填入的东西在你切换成其他组件的时候不会被初始化成最初的样子
开启 keep-alive 生命周期的变化
- 初次进入时: onMounted> onActivated
- 退出后触发
deactivated
- 再次进入:
- 只会触发 onActivated
- 事件挂载的方法等,只执行一次的放在 onMounted 中;组件每次进去执行的方法放在 onActivated 中
<!-- 基本 -->
<keep-alive>
<component :is="view"></component>
</keep-alive>
<!-- 多个条件判断的子组件 -->
<keep-alive>
<comp-a v-if="a > 1"></comp-a>
<comp-b v-else></comp-b>
//v-if与else的切换组件,切换后另外一个组件虽然被切换走了,但是通过声明周期我们可以看到不会被销毁
</keep-alive>
<!-- 和 `<transition>` 一起使用 -->
<transition>
<keep-alive>
<component :is="view"></component>
</keep-alive>
</transition>
include
和 exclude
-
include 和 exclude 允许组件有条件地缓存。二者都可以用逗号分隔字符串、正则表达式或一个数组来表示:
-
写在
include
里面的组件会被缓存起来,只写keep-alive是默认里面信息全部缓存的 -
写在
exclude
里面的组件不会被缓存起来,跟include的属性是完全相反的。如何选择使用可以根据实际情况选择 -
:
max
属性是决定了我们缓存的最大组件数量,假设我们:max="10",也就是最多缓存10个组件,可我们keep-alive内部即将缓存的有11个组件,他有内置算法会优先替换掉我们不常用的那一个
<keep-alive :include="keep-alive里的组件" :exclude="" :max=""></keep-alive>
<keep-alive :max="10">
<component :is="view"></component>
</keep-alive>
这个是只有开启keep-alive的时候才会出现的两个生命周期
//新的两个
onActivated(()=>{
console.log('keep-alive的初始化')
})
onDeactivated(()=>{
console.log('keep-alive的卸载')
})
//对应有关联的两个生命周期
onMounted(()=>{
console.log('初始化')//这个会随着onActivated一起生效
})
onUnMounted(()=>{
console.log('卸载')//如果有onDeactivated,则优先生效onDeactivated,onUnMounted则不再生效
})
//所以有一些卸载操作我们可以写在keep-alive独有的生命卸载周期里面
//一些一次性操作则写到onMounted里面,比如一些接口请求一次就行了。
//onMounted只会在刚开始的时候挂载一次,你组件之前的切换就不会在重新初始化了
//但是这个keep-alive的onActivated初始化则会在组件切换的时候不断触发
依赖注入Provide / Inject
provide()
接受两个参数:第一个参数是要注入的 key,可以是一个字符串或者一个 symbol,第二个参数是要注入的值。
inject()
注入一个由祖先组件或整个应用 (通过 app.provide()
) 提供的值。
第一个参数是注入的 key。Vue 会遍历父组件链,通过匹配 key 来确定所提供的值。如果父组件链上多个组件对同一个 key 提供了值,那么离得更近的组件将会“覆盖”链上更远的组件所提供的值。如果没有能通过 key 匹配到值,inject()
将返回 undefined
,除非提供了一个默认值。
第二个参数是可选的,即在没有匹配到 key 时使用的默认值。它也可以是一个工厂函数,用来返回某些创建起来比较复杂的值。如果默认值本身就是一个函数,那么你必须将 false
作为第三个参数传入,表明这个函数就是默认值,而不是一个工厂函数。
与注册生命周期钩子的 API 类似,inject()
必须在组件的 setup()
阶段同步调用。
tsx风格
完整版用法 请看 @vue/babel-plugin-jsx - npm
下一步计划
- 视频中的源码部分和案例
- vue的高级使用
- 状态管理库的学习
- 继续迭代和完善这个入门文档