1、具名插槽
Vue3 具名插槽 Named Slots 文档地址
// 具名插槽添加
<slot name="submit"></slot>
// 具体地方使用submit插槽时
// v-slot:submit 可以缩写为#submit
<template v-slot:submit>
<span class="btn btn-danger">Submit</span>
</template>
// 或者
<template #submit>
<span class="btn btn-danger">Submit</span>
</template>
2、watch监听
应用场景代码:
// watch 简单应用
watch(data, () => {
document.title = 'updated ' + data.count
})
// watch 的两个参数,代表新的值和旧的值
watch(refData.count, (newValue, oldValue) => {
console.log('old', oldValue)
console.log('new', newValue)
document.title = 'updated ' + data.count
})
// watch 多个值,返回的也是多个值的数组
watch([greetings, data], (newValue, oldValue) => {
console.log('old', oldValue)
console.log('new', newValue)
document.title = 'updated' + greetings.value + data.count
})
// 使用 getter 的写法 watch reactive 对象中的一项
// 注意这里不能直接用data.count;因为data.count不是一个响应式数据,所以用() => data.count 箭头函数的方式
watch([greetings, () => data.count], (newValue, oldValue) => {
console.log('old', oldValue)
console.log('new', newValue)
document.title = 'updated' + greetings.value + data.count
})
示例代码:
<!--
* @Description: Vue3新特性之watch监听
* @Author: Rulyc
* @Operating:
-->
<template>
<h1>{{ count }}</h1>
<h2>{{ double }}</h2>
<button @click="btnClick">点击+1</button>
</template>
<script lang="ts">
import { ref, computed, reactive, toRefs, watch } from 'vue' // 引入ref,computed 才能使用
interface DataProps { // 定义数据类型
count: number;
double: number;
btnClick: () => void;
}
export default {
name: "watch",
setup() {
const data: DataProps = reactive({
count: 0,
/** 点击事件 */
btnClick: ()=>{ data.count ++; },
/** 计算属性 */
double : computed(()=>{ return data.count * 2 })
})
const refData = toRefs(data)
/** watch监听 */
// data.double不是响应式数据,故而需要用箭头函数的方式()=> data.double,或者toRefs后的refData.count
watch([refData.count, ()=> data.double], (newValue, oldValue) => {
console.log('old', oldValue)
console.log('new', newValue)
document.title = 'updated ' + data.count+data.double
})
return {
...refData
}
}
}
</script>
结果:
3、ref语法(数据定义)
代码:
<!--
* @Description: Vue3新特性之Ref 语法
* @Author: Rulyc
* @Operating:
-->
<template>
<h1>{{ count }}</h1>
<h2>{{ double }}</h2>
<button @click="btnClick">点击+1</button>
</template>
<script>
import { ref, computed } from 'vue' // 引入ref,computed 才能使用
export default {
name: "ref_computed",
setup() {
// ref是一个函数,他接受一个参数,返回的就是一个神奇的响应式对象
const count = ref(0)
/** 计算属性用法 */
const double = computed(()=>{
return count.value * 2
})
/** 点击事件 */
const btnClick = ()=> {
// 更改值用xx.value; 渲染时用xx; 原因vue3底层自动在渲染时处理了
count.value ++
}
return {
count,
double,
btnClick
}
}
}
</script>
<style scoped>
</style>
4、Reactive函数
导致这个的原因是:
如果需要优化,且避免上述问题,我们需要引入toRefs;优化后的完整代码如下:
<!--
* @Description: Vue3新特性之Reactive 语法
* @Author: Rulyc
* @Operating:
-->
<template>
<h1>{{ count }}</h1>
<h2>{{ double }}</h2>
<button @click="btnClick">点击+1</button>
</template>
<script lang="ts">
import { ref, computed, reactive, toRefs } from 'vue'
interface DataProps { // 定义数据类型
count: number;
double: number;
btnClick: () => void;
}
export default {
name: "Reactive_toRefs",
setup() {
const data: DataProps = reactive({
count: 0,
/** 点击事件 */
btnClick: ()=>{ data.count ++; },
/** 计算属性 */
double : computed(()=>{ return data.count * 2 })
})
const refData = toRefs(data)
return {
// 此时的数据不是响应式的数据,而是取出的为数据类型,number
/* count: data.count,
double: data.double,
btnClick: data.btnClick*/
...refData
}
}
}
</script>
<style scoped>
</style>
使用 ref 还是 reactive 可以选择这样的准则:
第一,就像刚才的原生 javascript 的代码一样,像你平常写普通的 js 代码选择原始类型和对象类型一样来选择是使用 ref 还是 reactive。
第二,所有场景都使用 reactive,但是要记得使用 toRefs 保证 reactive 对象属性保持响应性
5、Vue3中的生命周期函数
在 setup 中使用的 hook 名称和原来生命周期的对应关系
beforeCreate -> 不需要 use setup()
created -> 不需要 use setup()
beforeMount -> onBeforeMount
mounted -> onMounted
beforeUpdate -> onBeforeUpdate
updated -> onUpdated
beforeDestroy-> onBeforeUnmount
destroyed -> onUnmounted
activated -> onActivated
errorCaptured -> onErrorCaptured
// 新增的(调试用的debugger工具函数)
renderTracked -> onRenderTracked
renderTriggered -> onRenderTriggered
<!--
* @Description: 生命周期函数
* @Author: Rulyc
* @Operating:
-->
<template>
<h1>{{ count }}</h1>
<h2>{{ double }}</h2>
<button @click="btnClick">点击+1</button>
</template>
<script lang="ts">
import { ref, computed, reactive, toRefs, onMounted, onUpdated, onRenderTriggered } from 'vue' // 引入ref,computed 才能使用
interface DataProps { // 定义数据类型
count: number;
double: number;
btnClick: () => void;
}
export default {
name: "onMounted",
setup() {
/** 挂载 */
onMounted(() => {
console.log('mounted')
})
/** 数据更新 */
onUpdated(() => {
console.log('updated')
})
/** 调试 */
onRenderTriggered((event) => {
console.log(event)
})
const data: DataProps = reactive({
count: 0,
/** 点击事件 */
btnClick: ()=>{ data.count ++; },
/** 计算属性 */
double : computed(()=>{ return data.count * 2 })
})
const refData = toRefs(data)
return {
...refData
}
}
}
</script>
<style scoped>
</style>
6、Teleport-瞬间移动组件
Teleport组件的出现时为了解决一个问题: 让元素直接挂在到根元素中(常见的场景就是Dialog弹窗,在界面中使用时包裹在其他组件之中,容易被干扰;样式也在其他组件之中,容易变得非常乱),这时候我们需要把弹窗组件渲染到顶层DOM节点之中,如elementUI中,弹窗、气泡是用的js挂在到body中,虽然解决上述说的问题,但是也存在一些bug,比如气泡的bug; vue3推出了teleport组件解决上述问题。
如下:
代码部分:model组件
在与html文件中:
model组件:
<template>
<!-- teleport组件有一个to属性,去界面中使用使用id="#modal" -->
<teleport to="#modal">
<div id="center" v-if="isOpen">
<h1>
this is a modal
<slot></slot>
</h1>
<button @click="btnClick">close</button>
</div>
</teleport>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
name: "Teleport",
props: { // 属性
isOpen: Boolean
},
emits: { // 事件名称
'close-model': null
},
setup(props, context) {
const btnClick = ()=>{
context.emit('close-model')
}
return {
btnClick
}
},
})
</script>
<style scoped>
#center {
width: 200px;
height: 200px;
border: 2px solid black;
background: white;
position: fixed;
left: 50%;
top: 50%;
margin-left: -100px;
margin-top: -100px;
}
</style>
具体界面使用
button @click=“openBtn”>Open Modal
<TeleportDemo :isOpen=“modelIsOpen” @close-model=“closeBtn”>
import { ref} from ‘vue’;
setup() {
const modelIsOpen = ref(false)
/** 打开弹窗按钮事件 /
const openBtn = ()=>{
modelIsOpen.value = true
}
/* 关闭弹窗按钮事件 */
const closeBtn = ()=>{
modelIsOpen.value = false
}
return {
modelIsOpen, // 返回数据
openBtn, // 返回事件
closeBtn // 返回事件
}
}
7、Suspense异步组件
Suspense组件是Vue3中的知名功能之一。
它们允许我们的应用程序在等待异步组件时渲染一些后备内容,可以让我们创建一个平滑的用户体验。
值得庆幸的是,Suspense组件非常容易理解,它们甚至不需要任何额外的导入!
可以解决异步请求的困境; Suspense是Vue3推出的一个内置的特殊组件; 如果使用Suspense,需要返回一个Promise
本文主要是展示一哈加载多个异步组件的场景,遇到不稳定问题,及解决方法(加载单个,只需要引入单个组件即可)
创建第一个组件:
AsyncShow
<!--
* @Description: Suspense - 异步请求好帮手
* @Author: Rulyc
* @Operating:
-->
<template>
<h1>{{ result }}</h1>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
name: "10_AsyncShow",
setup() {
/** 需要返回一个Promise, 使用Suspense */
return new Promise((resolve) => {
setTimeout(()=>{
return resolve({
result: 42
})
}, 3000)
})
}
})
</script>
使用 async await , 新建一个 10_AsyncShow_Img组件
<template>
<img :src="result && result.url" alt="">
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import axios from 'axios'
export default defineComponent({
name: "10_AsyncShow",
async setup() {
const rawData = await axios.get('https://images.dog.ceo/breeds/terrier-irish/n02093991_4404.jpg')
return {
result: rawData.config
}
}
})
</script>
使用界面中:(自行引入两个组件,然后使用如下图中)
警告中说:是一个实验性的特性,它的API很可能会改变slots,除非只有一个根节点。在
故而这里目前应该暂时展示一个根节点(之前的版本中,是可以展示多个异步组件作为根节点的; 以后版本未确定,还不够稳定)
解决这个问题,初步采用了增加一个div包裹(即增加一个根节点)
<Suspense>
<template #default>
<div>
<AsyncShow></AsyncShow>
<AsyncShowImg></AsyncShowImg>
</div>
</template>
<template #fallback>
<div>
Loading... ---- 异步加载中
</div>
</template>
</Suspense>
在4.5.8版本中,是需要增加一个根节点的。目前博主使用的是4.5.8; 之前的版本貌似是不用增加一个根节点。
补充
:
单个组件及template上属性说明如下图:
8、getCurrentInstance获取父元素内容
import { defineComponent, computed, getCurrentInstance } from 'vue'
setup: function (props, context) {
const colStyle = computed(() => {
let {parent} = getCurrentInstance()
while (parent && parent.type.name !== 'LcRow') {
parent = parent.parent
}
const colGutter : any = parent ? parent.props.gutter : 0
return {
paddingLeft: colGutter / 2 + 'px',
paddingRight: colGutter / 2 + 'px',
}
})
return {
colStyle
}
}
这里我是需要拿到父组件的gutter属性绑定值
9、toRefs、unref
- toRefs(props) 获取当前绑定的所有属性
- toRefs(props)[size] // 获取名为size属性的对象
- unref(toRefs(props)[size] // 获取名为size属性对象的value
import { defineComponent, computed, getCurrentInstance, toRefs, unref } from 'vue'
const classList = []
/**
* toRefs(props) 获取当前绑定的属性
* toRefs(props)[size] // 获取名为size属性的对象
* unref(toRefs(props)[size] // 获取名为size属性对象的value
* */
;['xs', 'sm', 'md', 'lg', 'xl'].forEach((size) => {
if(size == 'xs') { // 为了查看打印效果
console.log(toRefs(props), 'toRefs(props)')
console.log(toRefs(props)[size], 'toRefs(props)[size]')
console.log(unref(toRefs(props)[size]), 'unref(toRefs(props)[size]')
}
/** 具体判断示例 */
if (typeof unref(toRefs(props)[size]) === 'number') {
classList.push(`lc-col-${size}-${unref(toRefs(props)[size])}`)
} else if (typeof unref(toRefs(props)[size]) === 'object') {
const propsData = unref(toRefs(props)[size])
Object.keys(propsData).forEach((prop) => {
classList.push(
prop !== 'span'
? `lc-col-${size}-${prop}-${propsData[prop]}`
: `lc-col-${size}-${propsData[prop]}`
)
})
}
})
10、路由跳转
第一步,引入
import { useRouter } from "vue-router" // 引入路由文件
第二步:setup中
// 路由
const router = useRouter();
第三步跳转:
router.push('/');