本文已参与「新人创作礼」活动,一起开启掘金创作之路。
生命周期钩子
与2.x版本生命周期相对应的组合式API
新建测试组件/components/Test.vue
<template>
<div id="test">
<h3>{{a}}</h3>
<button @click="handleClick">更改</button>
</div>
</template>
<script>
import {
ref,
onMounted,
onBeforeMount,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted,
} from "vue";
export default {
// 初始化数据阶段的生命周期,介于beforeCreate和created之间
setup() {
const a = ref(0);
console.log("👌");
function handleClick() {
a.value += 1;
}
onBeforeMount(() => {
console.log("组件挂载之前");
});
onMounted(() => {
console.log("DOM挂载完成");
});
onBeforeUpdate(() => {
console.log("DOM更新之前", document.getElementById("test").innerHTML);
});
onUpdated(() => {
console.log("DOM更新完成", document.getElementById("test").innerHTML);
});
onBeforeUnmount(() => {
console.log("实例卸载之前");
});
onUnmounted(() => {
console.log("实例卸载之后");
});
return { a, handleClick };
}
};
</script>
按照官方上说的那样,你不需要立马弄明白所有的东西,不过随着你的不断学习和使用,它的参考价值会越来越高。
依赖注入
provide和inject提供依赖注入,功能类似2.x的provide/inject。两者都只能在当前组件的setup()中调用
App.vue provide数据源
<template>
<div>
<Article></Article>
</div>
</template>
<script>
import {
ref,
provide
} from "vue";
import Article from "./components/Article";
export default {
setup() {
const articleList = ref([
{ id: 1, title: "Vue3.0学习", author: "小马哥" },
{ id: 2, title: "componsition api", author: "尤大大" },
{ id: 3, title: "Vue-router最新", author: "vue官方" }
]);
/*
provide 函数允许你通过两个参数定义 property:
property 的 name (<String> 类型)
property 的 value
*/
provide("list",articleList);
return {
articleList
};
},
components: {
Article
}
};
</script>
Article.vue注入数据
<template>
<div>
{{articleList[0].title}}
</div>
</template>
<script>
import { inject } from "vue";
export default {
setup() {
const articleList = inject('list',[]);
return {articleList};
},
};
</script>
模板引用refs
当使用组合式API时,reactive refs 和 template refs的概念已经是统一了。为了获得对模板内元素或者组件实例的引用,可以直接在setup()中声明一个ref并返回它
<template>
<div>
<div ref='wrap'>hello vue3.0</div>
<Article ref='articleComp'></Article>
</div>
</template>
<script>
import {
ref,
onMounted,
provide
} from "vue";
import Article from "./components/Article";
export default {
setup() {
const isShow = ref(true);
const wrap = ref(null);
const articleComp = ref(null);
const articleList = ref([
{ id: 1, title: "Vue3.0学习", author: "小马哥" },
{ id: 2, title: "componsition api", author: "尤大大" },
{ id: 3, title: "Vue-router最新", author: "vue官方" }
]);
/*
provide 函数允许你通过两个参数定义 property:
property 的 name (<String> 类型)
property 的 value
*/
provide("list", articleList);
onMounted(() => {
console.log(wrap.value); //获取div元素
console.log(articleComp.value); //获取的article组件实例对象
});
return {
articleList,
wrap,
articleComp
};
},
components: {
Article
}
};
</script>
<style scoped>
</style>
效果图:
组件通信
- props
- $emit
- expose /ref
- attrs
- v-model
- provide/inject
- vuex
- mitt
props
// Parent.vue 传送
<child :msg1="msg1" :msg2="msg2"></child>
<script>
import child from "./child.vue"
import { ref, reactive } from "vue"
export default {
setup(){
// 创建一个响应式数据
const msg1 = ref("这是传级子组件的信息1")
const msg2 = reactive(["这是传级子组件的信息2"])
return {
msg1
msg2
}
}
}
</script>
// Child.vue 接收
<script>
export default {
props: ["msg1", "msg2"],// 如果这行不写,下面就接收不到
setup(props) {
console.log(props) // { msg1:"这是传给子组件的信息1", msg2:"这是传给子组件的信息2" }
},
}
</script>
$emit
// Child.vue 派发
<template>
// 写法一
<button @click="$emit('myClick',123)">按钮</buttom>
</template>
<script>
export default {
emits:['myClick']
//emits:{
//myClick:null
//}
}
</script>
// Parent.vue 响应
<template>
<child @myClick="onMyClick"></child>
</template>
<script setup>
import child from "./child.vue"
const onMyClick = (msg) => {
console.log(msg) // 这是父组件收到的信息 123
}
</script>
重大改变
Teleport
Teleport 就像是哆啦 A 梦中的「任意门」,任意门的作用就是可以将人瞬间传送到另一个地方。有了这个认识,我们再来看一下为什么需要用到 Teleport 的特性呢,看一个小例子: 在子组件Header中使用到Dialog组件,我们实际开发中经常会在类似的情形下使用到 Dialog ,此时Dialog就被渲染到一层层子组件内部,处理嵌套组件的定位、z-index和样式都变得困难。 Dialog从用户感知的层面,应该是一个独立的组件,从 dom 结构应该完全剥离 Vue 顶层组件挂载的 DOM;同时还可以使用到 Vue 组件内的状态(data或者props)的值。简单来说就是,即希望继续在组件内部使用Dialog, 又希望渲染的 DOM 结构不嵌套在组件的 DOM 中。 此时就需要 Teleport 上场,我们可以用<Teleport>包裹Dialog, 此时就建立了一个传送门,可以将Dialog渲染的内容传送到任何指定的地方。 接下来就举个小例子,看看 Teleport 的使用方式。
我们希望 Dialog 渲染的 dom 和顶层组件是兄弟节点关系, 在index.html文件中定义一个供挂载的元素:
<body>
<div id="app"></div>
<div id="dialog"></div>
</body>
定义一个Dialog组件Dialog.vue, 留意 to 属性, 与上面的id选择器一致:
<template>
<teleport to="#dialog">
<!-- 即希望继续在组件内部使用Dialog, 又希望渲染的 DOM 结构不嵌套在组件的 DOM 中。 此时就需要 Teleport 上场,
我们可以用<Teleport>包裹Dialog, 此时就建立了一个传送门,可以将Dialog渲染的内容传送到任何指定的地方 -->
<div class="dialog">
<div class="dialog_wrapper">
<div class="dialog_header">
<h3>我是弹框 {{ count }}</h3>
</div>
</div>
</div>
</teleport>
</template>
<script>
import { reactive, toRefs } from 'vue'
export default {
setup() {
const state = reactive({
count: 0,
})
return {
...toRefs(state),
}
},
}
</script>
<style lang="less" scoped></style>
Suspense
试验性
Suspense 是一个试验性的新特性,其 API 可能随时会发生变动。特此声明,以便社区能够为当前的实现提供反馈。
生产环境请勿使用
该 <suspense> 组件提供了另一个方案,允许将等待过程提升到组件树中处理,而不是在单个组件中。
自带两个 slot 分别为 default、fallback。顾名思义,当要加载的组件不满足状态时,Suspense 将回退到 fallback状态一直到加载的组件满足条件,才会进行渲染。
Suspense.vue
<template>
<button @click="loadAsyncComponent">点击加载异步组件</button>
<Suspense v-if="loadAsync">
<template #default>
<!-- 加载对应的组件 -->
<MAsynComp></MAsynComp>
</template>
<template #fallback>
<div class="loading"></div>
</template>
</Suspense>
</template>
<script>
import { ref, defineAsyncComponent } from 'vue'
export default {
components: {
MAsynComp: defineAsyncComponent(() => import('./AsynComp.vue')),
},
setup() {
const loadAsync = ref(false)
const loadAsyncComponent = () => {
loadAsync.value = true
}
return {
loadAsync,
loadAsyncComponent,
}
},
}
</script>
<style lang="less" scoped>
button {
padding: 12px 12px;
background-color: #1890ff;
outline: none;
border: none;
border-radius: 4px;
color: #fff;
cursor: pointer;
}
.loading {
position: absolute;
width: 36px;
height: 36px;
top: 50%;
left: 50%;
margin: -18px 0 0 -18px;
background-image: url('../assets/loading.png');
background-size: 100%;
animation: rotate 1.4s linear infinite;
}
@keyframes rotate {
from {
transform: rotate(0);
}
to {
transform: rotate(360deg);
}
}
</style>
AsynComp.vue
<template>
<h1>this is async component</h1>
</template>
<script>
import { setup } from 'vue'
export default {
name: 'AsyncComponent',
async setup() {
const sleep = (time) => {
return new Promise((reslove, reject) => {
setTimeout(() => {
reslove()
}, time)
})
}
await sleep(3000) //模拟数据请求
},
}
</script>
Fragments
Vue3.0组件中可以允许有多个根组件,避免了多个没必要的div渲染
<template>
<div>头部</div>
<div>内容</div>
</template>
这样做的好处:
- 少了很多没有意义的div
- 可以实现平级递归,对实现tree组件有很大帮助
emits
- emits 可以是数组或对象
- 触发自定义事件
- 如果emits是对象,则允许我们配置和事件验证。验证函数应返回布尔值,以表示事件参数是否有效。
Emits.vue
<template>
<div>
<button @click="$emit('submit',{username:'xiaomage',password:'123'})">自定义事件</button>
</div>
</template>
<script>
export default {
// emits:['submit'],//可以是数组
emits: {
submit: payload => {
if(payload.username && payload.password){
return true;
}else{
console.warn('无效的payload,请检查submit事件');
return false
}
}
},
setup() {
return {};
}
};
</script>
<style scoped>
</style>
App.vue
<Emits @submit="submitHandle"></Emits>
<script>
import Emits from "./components/Emits";
export default{
components:{
Emits
},
setup(){
function submitHandle(payload) {
console.warn("自定义事件触发",payload);
}
return {
}
}
}
</script>
效果展示:
全局Vue API更改为应用程序实例
上面已经讲过了,不做一一赘述了。
API可做Tree shakable优化
在vue2.0有不少的全局api是作为静态函数直接挂在在Vue构造函数上的,你应该手动操作过DOM,会遇到如下模式。如果我们未是在代码中用过它们,就会形成我们所谓的"死代码",这类全局api造成的"死代码"无法使用webapck的tree-shaking进行'死代码消除'。
import Vue from 'vue'
Vue.nextTick(()=>{
//一些和DOM相关的东西
})
因此,vue3.0做了相应的改变,将它们抽离成为独立的函数,这样打包工具的摇树优化可以将这些"死代码"排除掉。全局 API 现在只能作为 ES 模块构建的命名导出进行访问。例如,我们之前的片段现在应该如下所示
import {nextTick} from 'vue'
nextTick(()=>{
//一些和DOM相关的东西
})
受影响的API
-
Vue2.x中这些全局API受此更改的影响:
-
Vue.nextTick
-
Vue.observable(用Vue.reactive替换)
-
Vue.version
-
Vue.compile(仅完全构建时)
-
Vue.set(仅兼容版本)
-
Vue.delete(仅兼容版本)
TreeShaking.vue
<template>
<div >
<hr />摇树优化,把没引入的不必要的代码进行优化
<div id='name'>小马哥</div>
<h3 ref='myMsg'>{{msg}}</h3>
<button @click="changeMsg('hai!')">改变</button>
</div>
</template>
<script>
import { ref, nextTick } from "vue";
export default {
setup() {
const msg = ref("hello!");
const myMsg = ref(null);
async function changeMsg(newV) {
msg.value = newV;
// console.log(myMsg.value.innerText); //直接获取DOM还是以前的
// nextTick返回了promise对象
await nextTick();
console.log(myMsg.value.innerText);
}
return {
msg,
myMsg,
changeMsg
};
}
};
</script>