一、为什么选择CompositionAPI Vue2的局限性
组件逻辑膨胀导致的可读性变差
无法跨组件重用代码
Vue2对TS的支持有限
在传统的OptionsAPI中我们需要将逻辑分散到以下六个部分
OptionsAPI
components
props
data
computed
methods
lifecycle methods
如何使用CompositionAPI解决问题 最佳的解决方法是将逻辑聚合就可以很好的代码可读性。
这就是我们的CompositionAPI语法能够实现的功能。CompositionAPI是一个完全可选的语法与原来的OptionAPI并没有冲突之处。他可以让我们将相同功能的代码组织在一起,而不需要散落到optionsAPI的各个角落
代码重用方法PK Vue2中的跨组件重用代码,我们大概会有四个选择
Mixin - 混入
代码混入其实就是设计模式中的混合模式,缺点也非常明显。
可以理解为多重继承,简单的说就是一个人如何有两个父亲
缺点
Mixin Factory - 混入工厂
返回一个
✅代码重用方便
✅继承关系清洗
ScopeSlots - 作用域插槽
❌可读性不高
❌配置复杂 - 需要再模板中进行配置
❌性能低 - 每个插槽相当于一个实例
CompositionApi - 复合API
✅代码量少
✅没有引入新的语法,只是单纯函数
✅异常灵活
✅工具语法提示友好 - 因为是单纯函数所以 很容易实现语法提示、自动补偿
二、setup & ref 使用CompositionAPI理由 ✅更好的Typescript支持
✅在复杂功能组件中可以实现根据特性组织代码 - 代码内聚性👍 比如: 排序和搜索逻辑内聚
✅组件间代码复用
setup是什么
在以下方法前执行:
Components
Props
Data
Methods
Computed Properties
Lifecycle methods
可以不在使用难于理解的this
有两个可选参数
props - 属性 (响应式对象 且 可以监听(watch))
import {watch} from "vue" export defalut { props : { name : String }, setup (props ) { watch (() => { console .log (props.name ) }) } }
context 上下文对象 - 用于代替以前的this方法可以访问的属性
setup (props,context) { const {attrs,slots,parent,root,emit} = context }
ref是什么
对基本数据类型数据进行装箱操作使得成为一个响应式对象,可以跟踪数据变化。
总结
可维护性明显提高
可以控制哪些变量暴露
可以跟中哪些属性被定义 (属性继承与引用透明)
三、Methods 基础用法
自动拆装箱总结
JS :需要通过.value访问包装对象
模板: 自动拆箱
四、 Computed - 计算属性 这个地方实在没什么好讲的,和Vue2没变化
<template> <div> <div>Capacity: {{ capacity }}</div> <p>Spases Left: {{ sapcesLeft }} out of {{ capacity }}</p> <button @click="increaseCapacity()">Increase Capacity</button> </div> </template> <script> import { ref, computed, watch } from "vue"; export default { setup(props, context) { const capacity = ref(3); const attending = ref(["Tim", "Bob", "Joe"]); function increaseCapacity() { capacity.value++; } const sapcesLeft = computed(() => { return capacity.value - attending.value.length; }); return { capacity, increaseCapacity, attending, sapcesLeft }; }, }; </script>
五、Reactive - 响应式语法
之前reactive 的 Ref 去声明所有的响应式属性
import { ref,computed } from 'vue' export default { setup ( ){ const capacity = ref (4 ); const attending = ref (["Tim" ,"Bob" ,"Joe" ]); const spacesLeft = computed (()=> { return capacity.value - attending.value .length }) function increaseCapacity ( ){ capacity.value ++;} return { capacity,increaseCapacity,attending,spacesLeft} } }
但是有另一个等效的方法用它去代替 reactive 的Ref
import { reactive,computed } from 'vue' export default { setup ( ){ const event = reactive ({ capacity :4 , attending :["Tim" ,"Bob" ,"Joe" ], spacesLeft :computed (()=> { return event.capacity - event.attending .length ; }) }) } }
过去我们用vue2.0的data来声明响应式对象,但是现在在这里每一个属性都是响应式的包括computed 计算属性
这2种方式相比于第一种没有使用.
接下来 我们再声明method 这2种语法都ok,取决于你选择哪一种
setup ( ){ const event = reactive ( ){ capacity :4 , attending :["Tim" ,"Bob" ,"Joe" ], spacesLeft :computed (()=> { return event.capacity - event.attending .length ; }) function increaseCapacity ( ){event.capacity ++} return {event,increaseCapacity} } }
<p>Spaces Left:{{event.spacesLeft}} out of {{event.capacity}}</p> <h2>Attending</h2> <ul>> <li v-for="(name,index) in event.attending" :key="index"> {{name}} </li> </ul> <button @click="increaseCapacity()"> Increase Capacity</button>
在这里我们使用对象都是.属性的方式,但是如果 这个结构变化了,event分开了编程了一个个片段,这个时候就不能用.属性的方式了
import {reactive,computed,toRefs} from 'vue' export default { setup ( ){ const event = reactive ({ capacity :4 , attending :["Tim" ,"Bob" ,"Joe" ], spacesLeft :computed (()=> { return event.capacity -event.attending .length ; }) }) function increaseCapacity ( ){ event.capacity ++ } return {...toRefs (event),increaseCapacity} } }
如果没有 increaseCapacity() 这个方法 直接可以简化为
完整代码
<div> <p>Space Left : {{event.spacesLeft}} out of {{event.capacity}} </p> <h2>Attending</h2> <ul> <li v-for="(name,index)" in event.attending :key="index">{{name}} </li> </ul> <button @click="increaseCapacity">Increase Capacity</button> </div> </template> <script> //第一种 import {ref,computed } from 'vue' export default { setup(){ const capacity = ref(4) const attending = ref(["Tim","Bob","Joe"]) const spaceLeft = computed(()=>{ return capacity.value - attending.value.length; }); function increaseCapacity(){ capacity.value++; } return {capacity,increaseCapacity,attending,spaceLeft} } } //返回一个响应式函数 第二种 import { reactive,computed } from 'vue' export default { setup(){ const event = reactive({ capacity:4, attending:["Tim","Bob","Joe"], spaceLeft:computed(()=>{ return event.capacity - event.attending.length; }) }) //我们不再使用.value function increaseCapacity() { event.capacity++; } //把这个event放入到template中 return { event,increaseCapacity} } } </script>
六、 Modularizing 使用CompositionAPI的两个理由
可以按照功能组织代码
组件间功能代码复用
七、 LifecycleHooks - 生命周期钩子
Vue2
Vue3
beforeCreate
❌setup(替代)
created
❌setup(替代)
beforeMount
onBeforeMount
mounted
onMounted
beforeUpdate
onBeforeUpdate
updated
onUpdated
beforeDestroy
onBeforeUnmount
destroyed
onUnmounted
errorCaptured
onErrorCaptured
| 🎉onRenderTracked
| 🎉onRenderTriggered
setup中调用生命周期钩子
import { onBeforeMount,onMounted } from "vue" ;export default { setup ( ) { onBeforeMount (() => { console .log ('Before Mount!' ) }) onMounted (() => { console .log ('Before Mount!' ) }) }, };
八、Watch - 监听器 watchEffect (() => { results.value = getEventCount (searchInput.value ); }); watch ( searchInput, () => { console .log ("watch searchInput:" ); } ); watch ( searchInput, (newVal, oldVal ) => { console .log ("watch searchInput:" , newVal, oldVal); }, ); watch ( [firstName,lastName], ([newFirst,newLast], [oldFirst,oldlast] ) => { }, ); watch ( searchInput, (newVal, oldVal ) => { console .log ("watch searchInput:" , newVal, oldVal); }, { immediate : true , } );
九、Sharing State - 共享状态
编写一个公共函数usePromise函数需求如下:
results : 返回Promise执行结果
loading: 返回Promise运行状态
PENDING :true
REJECTED : false
RESOLVED: false
error : 返回执行错误
import { ref } from "vue" ;export default function usePromise (fn ) { const results = ref (null ); const loading = ref (false ); const error = ref (null ); const createPromise = async (...args ) => { loading.value = true ; error.value = null ; results.value = null ; try { results.value = await fn (...args); } catch (err) { error.value = err; } finally { loading.value = false ; } }; return { results, loading, error, createPromise }; }
应用
import { ref, watch } from "vue" ;import usePromise from "./usePromise" ;export default { setup ( ) { const searchInput = ref ("" ); function getEventCount ( ) { return new Promise ((resolve ) => { setTimeout (() => resolve (3 ), 1000 ); }); } const getEvents = usePromise ((searchInput ) => getEventCount ()); watch (searchInput, () => { if (searchInput.value !== "" ) { getEvents.createPromise (searchInput); } else { getEvents.results .value = null ; } }); return { searchInput, ...getEvents }; }, };
十、Suspense - 悬念 复杂的Loading实现 我们考虑一下当你加载一个远程数据时,如何显示loading状态
通常我们可以在模板中使用v-if
但是在一个组件树中,其中几个子组件需要远程加载数据,当加载完成前父组件希望处于Loading状态时我们就必须借助全局状态管理来管理这个Loading状态
Suspense基础语法 这个问题在Vue3中有一个全新的解决方法。
这就是Suspense Component,悬念组件。
<template> <div> <div v-if="error">Uh oh .. {{ error }}</div> <Suspense> <template #default> <div> <Event /> <AsyncEvent /> </div> </template> <template #fallback> Loading.... </template> </Suspense> </div> </template> <script> import { ref, onErrorCaptured, defineAsyncComponent } from "vue"; import Event from "./Event.vue"; const AsyncEvent = defineAsyncComponent(() => import("./Event.vue")); export default { components: { Event, AsyncEvent, }, setup() { const error = ref(null); onErrorCaptured((e) => { error.value = e; // 阻止错误继续冒泡 return true; }); return { error }; }, }; </script>
骨架屏实现
十一、Teleport - 传送门 功能
类似React中的Portal, 可以将特定的html模板传送到Dom的任何位置
基础语法 通过选择器QuerySelector配置
示例代码
<template> <div> <teleport to="#end-of-body" :disabled="!showText"> <!-- 【Teleport : This should be at the end 】 --> <div> <video src="../assets/flower.webm" muted controls="controls" autoplay="autoplay" loop="loop"> </video> </div> </teleport> <div>【Teleport : This should be at the top】</div> <button @click="showText = !showText">Toggle showText</button> </div> </template> <script> import { ref } from "vue"; export default { setup() { const showText = ref(false); setInterval(() => { showText.value = !showText.value; }, 1000); return { showText }; }, }; </script>