<script >
export default {
data() {
return {
msg: '你好,vue',
url: "http://www.baidu.com",
input:{
value:1234
}
}
}
}
script>
<template>
<div>
{{ msg }}
div>
<div>
<a v-bind:href="url">点击跳转百度a>
<a :href="url">点击跳转百度a>
div>
<div>
单项数据绑定: <input type="text" name="" id="" :value="input.value"><br>
双向数据绑定: <input type="text" name="" id="" v-model="input.value">
div>
template>
<script setup lang="ts">
import { ref } from 'vue'
const a: number = 1;
const b: number = 0;
const c: number[] = [1, 2, 3];
const d: string = '我是ts版vue'
const e: string = '我是ts版vue
'
const f: boolean = true
script>
<template>
{{ a }}
<br><br>
{{ b ? 'B为1' : 'B为0' }}
<br><br>
{{ c.map(item => ({ 数组索引: item - 1 })) }}
<div v-for='item in c'>{{item}}div>
<br><br>
<div v-text="d">div>
<br><br>
<div v-html="e">div>
<br><br>
<div v-if="f">如果为true就会展示这段内容,v-if 会在节点上控制DOM的显现,还有v-else-if,v-elsediv>
<div v-show="f">如果为true就会展示这段内容,v-show只是在样式上控制DOM的显现div>
template>
<style scoped>style>
vue2中,有一个属性 methods 专门用来存储事件方法,在setup直接写
<div id="root">
<button v-on:click = "showInfo">点击我,弹出提示信息button>
div>
<script setup>
const showInfo=()=> {
alert('同学您好')
}
script>
当我们一点击按钮,就会弹出对话框,当然这里的 showInfo 方法中,我们也可以加 event 参数,和 JS 语法中一样
带参数的方法
<div id="root">
<button v-on:click = "showInfo($event, 666)">点击我,弹出提示信息button>
div>
<script>
const showInfo=(event, number)=> {
alert('同学您好' + number);
console.log(event);
}
script>
如果我们想同时传入 event 事件和自定义参数,那么需要在模板中的事件上添加 $event 来帮助 event 事件参数进行占位
事件的回调需要配置在 methods 对象中,最终会绑定在 vm 上(虽然也可以直接配置在 data 对象中,但是不推荐)
@click = “demo” 和 @click = “demo($event)” 效果一致,后者只是多传入了一个 event 的对象
Vue 中的事件修饰符(常见):
prevent:阻止默认事件
stop:阻止事件冒泡(放在子级元素身上)
once:事件只触发一次
capture:使用事件的捕获模式(放在父级元素身上)
self:只有 event.target 是当前操作的元素才触发事件
passive:事件的默认行为立即执行,无需等待事件回调执行完毕
sroll 是滚动条的事件,每次触发好多次
wheel 是滚轮的事件,每次触发一次
DOM事件的执行是从根元素随着嵌套到最底层,再对上冒泡
<div id="root">
<div>
<a href="http://www.baidu.com" @click.prevent ="showInfo" class="box1">百度a>
div>
<div @click = "showInfo">
<button @click.stop = "showInfo">点击button>
div>
<div>
<button @click.once = "showInfo">点击button>
div>
<div @click.capture = "showInfo">
<button @click = "showMsg">点击button>
div>
<div @click.self = "showInfo">
<button @click.stop = "showMsg">点击button>
div>
div>
<script>
const vm = new Vue({
el: '#root',
data: {
name: '张三',
},
methods: {
showInfo(e) {
// e.preventDefault();
alert('Vue 很有趣!!')
},
showMsg(e) {
alert('所以,同学快来学 Vue')
}
}
})
script>
Vue 中给常用的按钮起了别名:
回车 => enter
删除 => delete(捕获 “删除” 和 “退格”)
退出 => esc
换行 => tab
上 => up
下 => down
左 => left
右 => right
这里的 tab 键比较特殊,因为 tab 的主要作用就是用来切换焦点的,所以当我们搭配 keyup 时,按钮会失效,只能搭配 keydown 进行触发
<div id="root">
<input type="text" placeholder="按下回车触发事件" @keyup.enter = "showInfo">
div>
<script>
// 定义别名按键
// Vue.config.keyCodes.huiche = 13;
const vm = new Vue({
el:'#root',
methods: {
showInfo(e) {
// console.log(e.key,e.keyCode)
// if(e.keyCode == 13) {
console.log(e.target.value);
// }
}
}
})
script>
注意:键盘上的每一个按键其实都有自己的名称和编码,例如 回车键{ 名称:Enter; 编码: 13}
Vue 未提供别名的按键,可以使用按钮原始的 key 值去绑定,但是注意要转化为 kebab-case(短横线命名),例如回车键,我们可以使用 Enter,大小写切换键 CapsLock 可以使用 caps-lock
系统修饰符(用法特殊):ctrl、alt、shift、meta(即 windows 系统中的徽标键,mac 系统中的 command 键)
配合 keyup 使用:按下修饰符的同时,再按下其他键,随后释放其他键,事件才能触发
配合 keydown 使用:正常触发事件
也可以使用e. keycode 来指定具体的按钮(不推荐)
也可以使用e. code 来指定具体的按钮,能区分左右shift之类的
也可以使用e. key 来指定具体的按钮
Vue.config.keyCodes.自定义键名 = 键码(同样因为使用到 keycode ,不推荐)
<script setup lang="ts">
// 非响应式的,值发生改变不会引起界面上的变化
const man = { name: 'zs' }
const change = () => {
man.name = 'ls'
console.log(man);
}
script>
<template>
{{ man }}
<button @click="change">点击button>
template>
<script setup lang="ts">
// 非响应式的,值发生改变不会引起界面上的变化
import { ref, isRef, shallowRef } from 'vue'
type m = {
name: string
}
const man = ref<m>({ name: 'zs' })
const man1 = shallowRef<m>({ name: 'zs' })
const change = () => {
man.value.name = 'ls'
console.log(man);
// isRef判断一个是不是ref对象
// ref和shallowRef都是ref对象
console.log(isRef(man));
}
const change1 = () => {
man1.value.name = 'ls'
console.log(man1);
}
const change2 = () => {
man1.value = { name: 'ls' }
console.log(man1);
}
// ref和shallowRef不能共用一个函数调用,不让会受到影响
// ref=shallowRef+triggerRef,只要triggerRef被调用,就会响应界面,所以两个一起用会受到影响
script>
<template>
<h2>响应式数据,只有从最底层的属性name改变才能触发响应,在value中修改不会响应到界面h2>
{{ man }}
<button @click="change">点击button>
<br><br>
<h2>浅响应式数据,只有从value改变才能触发响应,在name中修改不会相应到界面h2>
{{ man1 }}
<button @click="change1">从name赋值button>
<button @click="change2">从value赋值button>
template>
<style scoped>style>
<script setup lang="ts">
// 非响应式的,值发生改变不会引起界面上的变化
import { customRef,isRef} from 'vue'
const obj=myRef<string>('自定义ref')
type t = {
value:string
}
function myRef<T>(value:T) {
return customRef((track,trigger) => {
return{
get(){
//获取依赖
track()
return value
},
set(newValue){
console.log('触发了');
value=newValue
//进行依赖的更新
trigger()
}
}
}
)
}
const change = () => {
obj.value = '被修改了'
console.log(obj);
// isRef判断一个是不是ref对象
// ref和shallowRef都是ref对象
console.log(isRef(obj));
}
script>
<template>
{{ obj }}
<button @click="change">点击button>
template>
<style scoped>style>
<script setup lang="ts">
// 非响应式的,值发生改变不会引起界面上的变化
import { ref, customRef, isRef } from 'vue'
//ref还可以读取dom值,用法和react中差不多
const dom=ref<HTMLDivElement>()
const obj = myRef<string>('自定义ref')
type t = {
value: string
}
function myRef<T>(value: T) {
let timer: any
return customRef((track, trigger) => {
return {
get() {
track()
return value
},
set(newValue) {
clearTimeout(timer)
timer = setTimeout(() => {
console.log('触发了');
value = newValue
trigger()
}, 2000);
console.log(dom.value?.innerText);
}
}
}
)
}
const change = () => {
obj.value = '被修改了'
console.log(obj);
// isRef判断一个是不是ref对象
// ref和shallowRef都是ref对象
console.log(isRef(obj));
}
script>
<template>
{{ obj }}
<button @click="change">点击button>
<div ref="dom">ref也可以读取domdiv>
template>
<style scoped>style>
<script setup lang="ts">
import { reactive, readonly,shallowReactive, toRaw } from 'vue'
// reactive proxy 不能直接赋值,不然会破坏响应式对象
//能通过数组方法来调用
// 通过数组的属性值来直接赋值也是可以的
const obj = reactive({
name:'zs',
age:13
})
const list = reactive([
{name:'zs',age:13},
{name:'ls',age:14},
{name:'ww',age:11},
{name:'sq',age:33},
]
)
//toRef只能用于响应式,用于将reactive中的属性单独提出来,适用场景为解构赋值
const name1=toRaw(obj.name)
const {name,age}=toRaw(obj)
console.log(name1);
// toRaw的作用就是将响应式对象变成一般对象,没啥应用场景
const read=readonly(list)
// readonly()只读的,不能修改
// const obj1 = shallowReactive({
// name:'zs',
// age:13
// })
//shallowReactive浅响应,只响应最顶层
// 不能和reactive混用
const submit=() => {
list.push(obj)
}
script>
<template>
<form action="">
<input type="text" v-model="obj.name"><br>
<input type="text" v-model="obj.age"><br>
<button @click.prevent="submit">提交button>
<br>
<ol>
<li v-for="item in list"> 姓名:{{ item.name }}, 年龄:{{ item.age }}li>
ol>
form>
template>
<style scoped>style>
看不懂就算了,没啥用,会用就行
let activeEffect;
export const effect=(fn:Function) => {
const _effect=function () {
activeEffect=_effect
fn()
}
_effect()
}
const targetMap=new WeakMap()
export const track=(target,key) => {
let depsMap=targetMap.get(target)
if (!depsMap) {
depsMap=new Map()
targetMap.set(target,depsMap)
}
let deps=depsMap.get(key)
if (!deps) {
depsMap=new Set()
depsMap.set(key,deps)
}
deps.add(activeEffect)
}
export const trigger=(target,key) => {
const depsMap=targetMap.get(target)
const deps=depsMap.get(key)
deps.forEach(effect => effect());
}
import { track, trigger } from "./effect";
export const reactive = <T extends object>(target: T) => {
return new Proxy(target, {
get(target, key, reactive) {
let res = Reflect.get(target, key, reactive);
track(target,key)
return res;
},
set(target, key, reactive, value) {
let res = Reflect.set(target, key, reactive, value);
trigger(target,key)
return res;
},
});
};
动态的、要通过已有的属性进行计算而得来的属性
1.初次读取的时候会执行一次
2.当依赖的数据发生改变时会被再次调用
优势:与 methods 实现相比较,内部存在缓存机制(复用),效率更高,调用更加方便
1.计算属性最终会落到 vm 上,直接读取使用即可
2.如果计算属性要修改,必须写到 set 函数去响应修改,且 set 中要引起计算时依赖的数据改变
正常写法
let fullName=computed({
get(){
return this.first_name.slice(0,3)+'-'+this.last_name.slice(0,3)
},
set(value){
const arr=value.split('-'),
this.first_name=arr[0],
this.last_name=arr[1]
}
})
我们如果省略了 set 方法,那么可以将原来的 get 方法简写为:
let fullName=computed( ()=>{
return this.first_name.slice(0,3)+'-'+this.last_name.slice(0,3)
}
)
这个计算属性直接写成"方法",并且表示的就是 get 方法
当然,这样写是vue的语法糖,表示将 get 方法绑定到 fullName 属性上,而不是 fullName 就是一个方法,其本质上还只是一个计算属性
计算属性允许我们声明性地计算衍生值。然而在有些情况下,我们需要在状态变化时执行一些“副作用”:例如更改 DOM,或是根据异步操作的结果去修改另一处的状态。
在组合式 API 中,我们可以使用 watch
函数在每次响应式状态发生变化时触发回调函数:
<script setup>
import { ref, watch } from 'vue'
const question = ref('')
const answer = ref('Questions usually contain a question mark. ;-)')
// 可以直接侦听一个 ref
watch(question, async (newQuestion, oldQuestion) => {
if (newQuestion.indexOf('?') > -1) {
answer.value = 'Thinking...'
try {
const res = await fetch('https://yesno.wtf/api')
answer.value = (await res.json()).answer
} catch (error) {
answer.value = 'Error! Could not reach the API. ' + error
}
}
})
script>
<template>
<p>
Ask a yes/no question:
<input v-model="question" />
p>
<p>{{ answer }}p>
template>
watch
的第一个参数可以是不同形式的“数据源”:它可以是一个 ref (包括计算属性)、一个响应式对象、一个 getter 函数、或多个数据源组成的数组:
const x = ref(0)
const y = ref(0)
// 单个 ref
watch(x, (newX) => {
console.log(`x is ${newX}`)
})
// getter 函数
watch(
() => x.value + y.value,
(sum) => {
console.log(`sum of x + y is: ${sum}`)
}
)
// 多个来源组成的数组
watch([x, () => y.value], ([newX, newY]) => {
console.log(`x is ${newX} and y is ${newY}`)
})
注意,你不能直接侦听响应式对象的属性值,例如:
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}`)
}
)
与 VUE2 中的 watch 不同,VUE3 可以多次使用 watch 方法,通过多个watch 方法来监听多个对象。而 VUE2 则是把所有的要监控的对象放在 watch 里面。
VUE2 代码:
watch: {
nums () {},
'demo.name' () {}
}
VUE3 代码:
watch(nums, () => {})
watch(() => demo.name, () => {})
关于 watch 的第三个参数,除了布尔类型的 deep,还有一个布尔类型的 immediate。源码中的接口声明如下:
export declare interface WatchOptions<Immediate = boolean> extends WatchOptionsBase
{
immediate?: Immediate;
deep?: boolean;
}
immediate 的作用就是设置是否立即执行监控,当我们将其值设置为 true 时,那么被监控的对象在初始化时就会触发一次 watch 方法,相当于页面一刷新就会触发。
如果侦听的属性内部还有其他属性,那么我们如果想要继续侦听内部的属性就需要用到参数 deep,将其设置为 true
export default {
setup() {
const route = useRouter();
//获取当前路由地址
watch(()=>route, (newVal, oldVal) => {
console.log(newVal)
console.log( oldVal)
},
//深度监听
{deep: true}
)
}
Vue 中的 watch 默认不监测对象内部值的改变
如果想要让 Vue 中的 watch 可以监测到对象内部值的改变,需要设置deep: true
注意:Vue 自身可以监测到对象内部值的改变,但是 Vue 提供的 watch 默认是监测不到的
computed 能完成的功能,watch 都能完成,反过来,watch 能完成的事情,computed 不一定就能完成
computed 所包含的计算属性的值通过 return 返回,就无法执行异步任务,例如设置一个定时器 setTimeout,而 watch 就可以进行异步任务
重要提醒:
被 Vue 管理的函数,最好写成普通函数,这样 this 的指向才会是 vm 或组件实例对象
不被 Vue 管理的函数(定时器的回调函数,ajax 的回调函数等),最好写成箭头函数,这样 this 的指向才会是 vm 或组件实例对象
<template>
<div>
<input type="text" v-model="message" name="" id=""><br>
<input type="text" v-model="message2" name="" id="">
div>
template>
<script setup lang="ts">
import { ref,watchEffect } from 'vue';
let message=ref<string>('飞机')
let message2=ref<string>('飞机厂')
// watch只会在监听的值发生改变时调用,watchEffect在一开始就进行调用,和react的useEffect差不多
watchEffect((onInvalidate) => {
console.log('message为'+message.value);
onInvalidate(() => {
console.log('重新更新时立即执行');
})
})
script>
Vue3
的watchEffect
侦听副作用传入的函数可以接收一个 onInvalidate
函数作为入参,用来注册清理失效时的回调。当以下情况发生时,这个失效回调会被触发:
watch
和 watchEffect
都能响应式地执行有副作用的回调。它们之间的主要区别是追踪响应式依赖的方式:
watch
只追踪明确侦听的数据源。它不会追踪任何在回调中访问到的东西。另外,仅在数据源确实改变时才会触发回调。watch
会避免在发生副作用时追踪依赖,因此,我们能更加精确地控制回调函数的触发时机。watchEffect
,则会在副作用发生期间追踪依赖。它会在同步执行过程中,自动追踪所有能访问到的响应式属性。这更方便,而且代码往往更简洁,但有时其响应性依赖关系会不那么明确。在 setup()
或 中用同步语句创建的侦听器,会自动绑定到宿主组件实例上,并且会在宿主组件卸载时自动停止。因此,在大多数情况下,你无需关心怎么停止一个侦听器。
一个关键点是,侦听器必须用同步语句创建:如果用异步回调创建一个侦听器,那么它不会绑定到当前组件上,你必须手动停止它,以防内存泄漏。如下方这个例子:
<script setup>
import { watchEffect } from 'vue'
// 它会自动停止
watchEffect(() => {})
// ...这个则不会!
setTimeout(() => {
watchEffect(() => {})
}, 100)
</script>
要手动停止一个侦听器,请调用 watch
或 watchEffect
返回的函数:
const unwatch = watchEffect(() => {})
// ...当该侦听器不再需要时
unwatch()
<template>
<div>
<input type="text" v-model="message" name="" id=""><br>
<input type="text" v-model="message2" name="" id="">
<button @click="stopWatch">停止监听</button>
</div>
</template>
<script setup lang="ts">
import { ref, watchEffect } from 'vue';
let message = ref<string>('飞机')
let message2 = ref<string>('飞机厂')
// watch只会在监听的值发生改变时调用,watchEffect在一开始就进行调用,和react的useEffect差不多
const stop = watchEffect((onInvalidate) => {
console.log('message为' + message.value);
onInvalidate(() => {
console.log('重新更新时立即执行');
})
})
const stopWatch=()=>stop()
</script>
<style scoped></style>
注意,需要异步创建侦听器的情况很少,请尽可能选择同步创建。如果需要等待一些异步数据,你可以使用条件式的侦听逻辑:
// 需要异步请求得到的数据
const data = ref(null)
watchEffect(() => {
if (data.value) {
// 数据加载后执行某些操作...
}
})
当你更改了响应式状态,它可能会同时触发 Vue 组件更新和侦听器回调。
默认情况下,用户创建的侦听器回调,都会在 Vue 组件更新之前被调用。这意味着你在侦听器回调中访问的 DOM 将是被 Vue 更新之前的状态。
如果想在侦听器回调中能访问被 Vue 更新之后的 DOM,你需要指明 flush: 'post'
选项:
watch(source, callback, {
flush: 'post'
})
watchEffect(callback, {
flush: 'post'
})
后置刷新的 watchEffect()
有个更方便的别名 watchPostEffect()
:
import { watchPostEffect } from 'vue'
watchPostEffect(() => {
/* 在 Vue 更新后执行 */
})
<template>
<div>
<input type="text" v-model="message" name="" id="ipt"><br>
<input type="text" v-model="message2" name="" id="">
<button @click="stopWatch">停止监听</button>
</div>
</template>
<script setup lang="ts">
import { ref, watchEffect } from 'vue';
let message = ref<string>('飞机')
let message2 = ref<string>('飞机厂')
// watch只会在监听的值发生改变时调用,watchEffect在一开始就进行调用,和react的useEffect差不多
const stop = watchEffect((onInvalidate) => {
// as HTMLInputElement是断言,用于定义类型的
let ipt: HTMLInputElement = document.querySelector('#ipt') as HTMLInputElement
console.log(ipt);
onInvalidate(() => {
console.log('重新更新时立即执行');
})
},{
//flush刷新
//pre预设更新前执行
//sync强制效果始终同步触发
//post组件更新后执行
//ontrigger(a){debugger}
flush:'post'
})
const stopWatch = () => stop()
</script>
<style scoped></style>
<template>
<div>
<div>父级div>
<div>子组件传来的值为{{ sunName }}div>
<hr>
<HelloWorld :title="name" @on-click="getName" >HelloWorld>
div>
template>
<script setup lang="ts">
import HelloWorld from './components/HelloWorld.vue';
import {ref}from 'vue';
let name= '张三'
let sunName=ref('')
// 在该函数中对子组件传来的值进行处理
const getName=(name:string) => {
sunName.value=name
console.log('子组件传来的值为'+name);
}
script>
<style scoped>style>
<script setup lang="ts">
// 接收父组件传回来的值
// defineProps({
// title: String
// })
// 接收父组件传回来的值,js支持定义默认值
// defineProps({
// title:{
// type:String,
// default:'默认值'
// }
// })
// ts版本
// defineProps<{
// title:string
// }>()
// setup中如果想用props中的值,需要用一个常量接值
// const props=defineProps<{
// title:string
// }>()
// console.log(props.title);
// ts中特有的,如果要设定默认值,用withDefaults,第二个参数为默认值的对象
withDefaults(defineProps<{
title:string
}>(),
{title:'默认值'}
)
// 给父组件传值
// 定义父组件触发条件
// const Emit= defineEmits(['on-click'])
// ts写法
const Emit= defineEmits<{
(e:"on-click",name:string):void
}>()
const send =() => {
// 发布父组件触发条件
Emit('on-click','李四')
}
script>
<template>
<div>子集收到的值:{{ title }}div>
<button @click="send">点击给父组件传值button>
template>
<style scoped>style>
<template>
<div>
<div>父级div>
<div>组件暴露的值为{{name}}div>
<hr>
<HelloWorld ref="waterFall">HelloWorld>
div>
template>
<script setup lang="ts">
import HelloWorld from './components/HelloWorld.vue';
import {onMounted, ref}from 'vue';
const name=ref<string|undefined>("")
const waterFall=ref<InstanceType<typeof HelloWorld>>()
// 读取组件暴露的值,只能在生命周期里读取
onMounted(() => {
console.log(waterFall.value);
name.value=waterFall.value?.name
})
script>
<style scoped>style>
<script setup lang="ts">
defineExpose({
name:'王五'
})
script>
<template>
<div>子集div>
template>
<style scoped>style>
瀑布流的实现
<template>
<div>
<HelloWorld :list="list"></HelloWorld>
</div>
</template>
<script setup lang="ts">
import HelloWorld from './components/HelloWorld.vue';
const list =[
{
height:100,
background:'red'
},
{
height:200,
background:'aqua'
},
{
height:100,
background:'red'
},
{
height:400,
background:'beige'
},
{
height:200,
background:'aqua'
},
{
height:300,
background:'red'
},
{
height:300,
background:'beige'
},
{
height:300,
background:'red'
},
{
height:200,
background:'red'
},
{
height:300,
background:'aqua'
},
{
height:200,
background:'coral'
},
{
height:100,
background:'coral'
},
{
height:400,
background:'red'
},
{
height:200,
background:'aqua'
},
{
height:400,
background:'beige'
},
{
height:300,
background:'coral'
},
{
height:300,
background:'coral'
},
{
height:200,
background:'aqua'
},
]
</script>
<style scoped></style>
<template>
<div class="wraps">
<div class="items"
:style="{ height: item.height + 'px', left: item.left + 'px', top: item.top + 'px', background: item.background }"
v-for="item in waterList"></div>
</div>
</template>
<script setup lang="ts">
import { onMounted, reactive } from 'vue';
const props = defineProps<{
list: any[]
}>()
const waterList = reactive<any[]>([])
const init = () => {
const width = 100;
const x = document.body.clientWidth;
const column = Math.floor(x / width)
const heightList: number[] = []
for (let i = 0; i < props.list.length; i++) {
if (i < column) {
props.list[i].left = i * width;
props.list[i].top = 20;
waterList.push(props.list[i])
heightList.push(props.list[i].height + 20)
} else {
let current = Math.min(...heightList)
let index = heightList.indexOf(current);
props.list[i].top = current + 20;
props.list[i].left = index * width;
heightList[index]=heightList[index] +props.list[i].height+20
waterList.push(props.list[i])
}
}
}
onMounted(() => {
init()
})
</script>
<style scoped lang="less">
.wraps {
position: relative;
.items {
position: absolute;
width: 90px;
}
}</style>
Vue动态组件是一种可以根据不同的数据来渲染不同的组件的技术,可以提高代码的复用性和灵活性。在Vue中,可以使用动态组件的方式有两种:使用标签或使用标签。
<template>
<div class="tabs-container">
//导航栏
<div class="tabs-header">
<div v-for="(item, index) in tabs" :key="item.id"
:class="['tab-header', { active: currentIndex === index }]"
@click="handleTabClick(index)">//点击时将遍历出来的index传回响应
{{ item.title }}
div>
div>
//展示区
<div class="tabs-content">
//通过响应中的数据完成组件的展示
//动态组件引用类似变量
<component :is="tabs[currentIndex].component">component>
div>
div>
template>
<script>
import Tab1 from './Tab1.vue';
import Tab2 from './Tab2.vue';
import Tab3 from './Tab3.vue';
const currentIndexref(0)
const tabs=reactive( [
{
id: 1,
title: 'Tab 1',
component: Tab1
},
{
id: 2,
title: 'Tab 2',
component: Tab2
},
{
id: 3,
title: 'Tab 3',
component: Tab3
}
])
const handleTabClick=(index)=> {
currentIndex.value = index;
}
}
script>
<style scoped>
.tabs-container {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
}
.tabs-header {
display: flex;
border-bottom: 1px solid #ccc;
}
.tab-header {
padding: 10px 20px;
cursor: pointer;
}
.active {
border-bottom: 2px solid red;
}
.tabs-content {
flex: 1;
}
style>
<template>
<div>
<HelloWorld>
<template #header>
<div>
将内容插入了子组件的头部
div>
template>
<template #main="mainProps">
<div>
{{ mainProps.data }}
{{ mainProps.data.name }}--{{ mainProps.data.age }}
div>
template>
<template #footer='{ data }'>
<div>
{{data.name }}--{{data.age }}
div>
template>
HelloWorld>
div>
template>
<script setup lang="ts">
import HelloWorld from './components/HelloWorld.vue';
script>
<style scoped>style>
<template>
<div>
<header class="header">
<slot name="header">slot>
header>
<main class="main">
<div v-for="(item, index) in data" :key="index">
<slot name="main" :data='item'> slot>
div>
main>
<footer class="footer">
<div v-for="(item, index) in data" :key="index">
<slot name="footer" :data='item'> slot>
div>
footer>
div>
template>
<script setup lang="ts">
import { reactive } from 'vue'
const data = reactive([
{
name: '张三',
age: 18
},
{
name: '李四',
age: 20
},
{
name: '王五',
age: 22
}
]
)
script>
<style scoped>
.header {
background-color: rgb(5, 103, 189);
width: 500px;
height: 300px;
}
.main {
background-color: rgb(118, 97, 55);
width: 500px;
height: 300px;
}
.footer {
background-color: rgb(50, 120, 141);
width: 500px;
height: 300px;
}
style>
在Vue 3中,异步组件可以通过defineAsyncComponent函数来定义。类似于React.lazy和Suspense,defineAsyncComponent和Suspense也是可以搭配使用的。
在Vue 3中,我们可以通过defineAsyncComponent函数来定义一个异步组件。defineAsyncComponent接受一个需要异步加载的组件的工厂函数作为参数,该函数返回一个Promise,这个Promise会resolve一个组件对象。
import { defineAsyncComponent } from 'vue'
// 函数式异步组件
const asyncComponent = defineAsyncComponent(() => {
return import('./AsyncComponent.vue')
})
// 对象式异步组件
const asyncComponent2 = defineAsyncComponent({
loader: () => import('./AsyncComponent2.vue'),
errorComponent: ErrorComponent,
delay: 200,
timeout: 3000
})
其中,loader
方法用于加载异步组件,errorComponent
用于显示异步组件加载错误时的组件,delay
用于设置异步组件延迟加载时间,timeout
用于设置异步组件加载超时时间。
在父组件中,通过import
函数或者defineAsyncComponent
函数引入异步组件,然后在模板中使用异步组件的标签即可。
import { defineAsyncComponent, ref } from 'vue'
const asyncComponent = defineAsyncComponent(() => import('./AsyncComponent.vue'))
const showAsyncComponent = ref(false)
// 点击按钮异步加载组件
const handleClick = () => {
showAsyncComponent.value = true
}
}
}
<template>
<div>
<button @click="handleClick">异步加载组件button>
<async-component v-show="showAsyncComponent" />
div>
template>
需要注意的是,异步加载的组件会有一个占位符显示,在组件加载完成之前,这个占位符会一直存在,直到组件加载完成才会被替换。
在Vue3中,异步组件在路由中的使用和Vue2略有不同。以下是一个示例:
import { createRouter, createWebHistory } from 'vue-router'
import Home from './views/Home.vue'
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/',
component: Home
},
// 异步加载组件
{
path: '/async',
component: defineAsyncComponent(() => import('./views/Async.vue'))
}
]
})
export default router
在路由中使用异步组件时,需要使用defineAsyncComponent
函数将异步组件包裹起来,而不是直接引用异步组件。
以上就是Vue3中异步组件的详细使用方法。
跟React一样,在Vue 3中,异步组件的加载需要搭配Suspense组件使用,用于在异步组件加载时显示一个占位符,以防止用户在等待时看到不友好的空白页面。我们用Suspense包裹了异步组件AsyncComponent,并使用#fallback指定了一个fallback元素,在异步组件加载时显示。当异步组件加载完成后,Suspense组件会自动显示异步组件的内容。
1 使用defineAsyncComponent和Suspense的组件需要使用Vue的新组件标记方式,如上面的示例中使用的template标记和#default和#fallback语法。
2 Suspense 组件必须包裹需要异步加载的组件。
<template>
<div>
<h1>异步组件和Suspense的例子h1>
<Suspense>
<template #default>
<AsyncComponent />
template>
<template #fallback>
<div>Loading...div>
template>
Suspense>
div>
template>
<script>
import { defineAsyncComponent, Suspense } from 'vue'
const AsyncComponent = defineAsyncComponent({
loader: () => import('./AsyncComponent.vue')
})
export default {
components: {
AsyncComponent
}
}
script>
Teleport是一个内置组件,它可以将一个组件内部的一部分模板“传送”到该组件的 DOM 结构外层的位置去。
template
<button @click="open = true">Open Modalbutton>
<Teleport to="body">
<div v-if="open" class="modal">
<p>Hello from the modal!p>
<button @click="open = false">Closebutton>
div>
Teleport>
Teleport 接收一个 to prop 来指定传送的目标。to 的值可以是一个 CSS 选择器字符串,也可以是一个 DOM 元素对象。这段代码的作用就是告诉 Vue“把以下模板片段传送到 body 标签下”。
Teleport只改变了渲染的 DOM 结构,它不会影响组件间的逻辑关系。也就是说,如果 Teleport 包含了一个组件,那么该组件始终和这个使用了 teleport 的组件保持逻辑上的父子关系。传入的 props 和触发的事件也会照常工作。
这也意味着来自父组件的注入也会按预期工作,子组件将在 Vue Devtools 中嵌套在父级组件下面,而不是放在实际内容移动到的地方。
在某些场景下可能需要视情况禁用 Teleport。举例来说,我们想要在桌面端将一个组件当做浮层来渲染,但在移动端则当作行内组件。我们可以通过对 Teleport>动态地传入一个 disabled prop 来处理这两种不同情况。
template
<Teleport :disabled="isMobile">
...
Teleport>
这里的 isMobile 状态可以根据 CSS media query 的不同结果动态地更新。
一个可重用的模态框组件可能同时存在多个实例。对于此类场景,多个 组件可以将其内容挂载在同一个目标元素上,而顺序就是简单的顺次追加,后挂载的将排在目标元素下更后面的位置上。
比如下面这样的用例:
template
<Teleport to="#modals">
<div>Adiv>
Teleport>
<Teleport to="#modals">
<div>Bdiv>
Teleport>
渲染的结果为:
<div id="modals">
<div>Adiv>
<div>Bdiv>
div>
Teleport 挂载时,传送的 to 目标必须已经存在于 DOM 中。理想情况下,这应该是整个 Vue 应用 DOM 树外部的一个元素。如果目标元素也是由 Vue 渲染的,你需要确保在挂载 Teleport 之前先挂载该元素。