本文主要介绍作者在使用过程中遇到的问题和知识点,开发环境是在vite2.0中,并且采用<script setup>语法糖进行开发
生命周期函数
Vue2.x 中含有8个生命周期函数:
- beforeCreate
- created
- beforeMount
- mounted
- beforeUpdate
- updated
- beforeDestroy
- destroyed
Vue3中,新增了setup
生命周期函数,其执行时期是在beforeCreate
之前,因此此函数中是不能通过this
来获取实例,将beforeDestroy
改名为beforeUnmount
,destroyed
改名为unmounted
,总体来看Vue3的生命周期函数为:
- beforeCreate(建议使用setup代替)
- created(建议使用setup代替)
- setup
- beforeMount
- mounted
- beforeUpdate
- updated
- beforeUnmount
- unmounted
同时vue中的生命周期钩子是通过在生命周期函数前加on
来访问组件的生命周期,可以使用以下生命周期钩子:
- onBeforeMount
- onMounted
- onBeforeUpdate
- onUpdated
- onBeforeUnmount
- onUnmounted
- onErrorCaptured
- onRenderTracked
- onRenderTriggered
常用
相应式API
- ref:主要处理基本数据结构,也是能处理对象和数组,在js中通过.value修改其值
- reactive:主要负责复杂数据结构
- toRef:将对象中的某个属性变为响应式数据,会影响原数据,但不会触发UI界面的更新
- toRefs:类似与toRef,将对象中的所有属性变为响应式数据
响应式监听
- watch,引入:
import { watch } from 'vue'
2. ref定义的值
const test1 = ref(0);
watch(test1, (val, oldVal) => { console.log(val, oldVal, "watch"); });
test1.value++
// 1 0 watch
3. reactive定义的值
const test2 = reactive({ count: 0 });
//侦听时返回值得getter函数
watch(
() => test2.count,
(val, oldVal) => { console.log(val, oldVal, "watch"); }
)
test2.count++
// 1 0 watch
4. 将多个值放在一个数组中监听,返回值也是数组形式
const test1 = reactive({
count: 1,
});
const test2 = ref(2);
watch([() => test1.count, count], (newVal, oldVal) => { console.log(newVal, oldVal); });
test1.count = 3
//[3, 2] [1, 2]
test2.value = 4
//[3, 4] [3, 2]
5. 深度嵌套监听
const deepObj = reactive({ a: { b: { c: "1" } } })
watch(
() => _.cloneDeep(deepObj),
(val, old) => { console.log(val.a.b.c, old.a.b.c) },
{ deep: true }
);
deepObj.a.b.c = "2";
// 2 1, 注:如果不使用_.cloneDeep(deepObj)进行克隆那么打印结果为 2 2
6. 停止监听
const test = ref(0)
const stop = watch(test, (newVal, olVal) => { console.log(newVal, olVal); });
setTimeout(()=>{ test.value++ }, 1000);
stop(); // 停止watch
// 无打印
- watchEffect
watchEffect
不需要指定监听的属性,他会自动的收集依赖, 只要我们回调中引用到了 响应式的属性, 那么当这些属性变更的时候,这个回调都会执行。watch
可以获取到新旧值,而watchEffect
不行
props
使用props
需要引用defineProps
来定义,用法跟Vue2.xprops
写法相似:
<script setup>
import { defineProps } from "vue";
const props = defineProps(['text1', 'object1']);
// 定义类型
const props = defineProps({
text1: String,
object1: {
type: Object,
default: null
}
})
</script>
emits
使用emits
需要引用defineEmits
来定义,用法跟Vue2.xemits
写法相似:
const emit = defineEmits(['clickFun'])
emit('clickFun', 'params')
context
上下文通过useContext
来获取
import { useContext } from 'vue'
const { slots, attrs } = useContext()
异步组件
在Vue2.x中异步组件的写法为:
AsyncComponent: () => import("../components/AsyncComponent.vue"),
在Vue3中需要引入defineAsyncComponent
来进行定义:
import { defineAsyncComponent } from "vue";
AsyncComponent: defineAsyncComponent(() => import("../components/AsyncComponent.vue"))
在Vue3中可以对异步组件进行更加细致的设置:
import { defineAsyncComponent } from "vue";
AsyncComponent: defineAsyncComponent(() => {
delay: 100,
timeout: 3000,
loader: () => import("../components/defineAsyncComponent.vue"),
errorComponent: ErrorComponent,
onError(error, retry, fail, attempts) {
if (attempts <= 3) {
retry();
} else {
fail();
}
},
})
如果需要使用resolve
和reject
const asyncComponent = defineAsyncComponent(
() => new Promise((resolve, reject) => {
/* ..todo.. */
})
)
组件加载
对于异步组件在加载时,我们可以有一些后备的内容用于加载中的渲染fallback
<template>
<div>
<button @click="showButton">展示异步组件</button>
<template v-if="isShowButton">
<Suspense>
<template #default>
<AsyncComponent></AsyncComponent>
</template>
<template #fallback>
<div>组件加载中...</div>
</template>
</Suspense>
</template>
</div>
</template>
$ref
在Vue3中已经没有了this
,那么如何使用$ref,父组件如何调用子组件中的方法,注:以下方案是在setup语法糖中
- 子组件需
defineExpose
导出
// children
<script setup>
import { defineExpose } from "vue"
const refresh = () => {
console.log("refresh");
}
defineExpose({
refresh
})
</script>
// father
<template>
<children ref="childRef"/>
</template>
<script setup>
import { onMounted } from "vue";
import children from "./children.vue"
ref: childRef = null
onMounted(()=>{
childRef.refresh(); // 打印: refresh
})
</script>
- 使用
export
// children
<script setup>
export const refresh = () => {
console.log("refresh");
}
</script>
// father
<template>
<Child ref="childRef" />
</template>
<script setup>
import { onMounted, ref } from "vue";
export { default as Child } from "./children.vue";
export const childRef = ref(null);
onMounted(() => {
childRef.value.refresh() // 打印: refresh
});
</script>
全局变量
以lodash为例子,定义全局变量使用globalProperties
和$
,使用全局变量需使用getCurrentInstance
注:据说ctx只在生产环境下可以,没试过欢迎尝试
// main.js中定义全局变量
app.config.globalProperties.$_= _
// .vue文件中
const { proxy, ctx } = getCurrentInstance();
onMounted(() => {
console.log(proxy.$_);
console.log(ctx.$_;
});
杂项
v-for和v-if优先级
Vue2.x中,v-for
的优先级更高,所以官方不建议v-for和v-if一起使用
Vue3中,v-if
比v-for
的优先级更高
v-for和key
Vue2.x中,v-for
循环需要给每个子节点一个唯一的key,还不能绑定在template标签上
而在Vue3中,key
值可以直接写在template
标签上,就不用每个子节点都设置key
v-model
在Vue2中,我们对父子组件传递基础数据进行双向绑定是通过 回调函数 或者 .sync 来实现
在Vue3中,可以通过v-model.msg1="msg1";v-model.msg2="msg2"
的形式传递多个值进行双向绑定
补充
Teleport
它可以将插槽中的元素或者组件传送到页面的其他位置
<template>
<button @click="showDialog = true">打开遮罩层</button>
<teleport to="body">
<div v-if="showDialog" style="position: fixed">
我是一个遮罩
<button @click="showDialog = false">关闭</button>
</div>
</teleport>
</template>
<script setup>
const showDialog = ref(false)
const msg = ref("hello")
</script>
上面中的teleport
中,会将div渲染到body的底部,虽然在DOM节点上脱离了父组件,但是其组件逻辑还是属于父组件,还是能和父组件通讯。Teleport接收两个参数to
和disabled
- to - string:必须是有效的查询选择器或 HTMLElement,可以id或者class选择器等。
- disabled - boolean:如果是true表示禁用teleport的功能,其插槽内容将不会移动到任何位置,默认false不禁用。