onmounted vue3_基于项目时间阐述vue3.0新型状态管理和逻辑复用方式
作者:Mingle
轉發鏈接:https://mp.weixin.qq.com/s/iOq-eeyToDXJ6lvwnC12DQ
前言
背景:2019年2月6號,React 發布 「16.8.0」 版本,vue緊隨其后,發布了「vue3.0 RFC」
Vue3.0受React16.0 推出的hook抄襲啟發(咳咳...),提供了一個全新的邏輯復用方案。使用基于函數的 API,我們可以將相關聯的代碼抽取到一個 "composition function"(組合函數)中 —— 該函數封裝了相關聯的邏輯,并將需要暴露給組件的狀態以相應式的數據源的方式返回出來。
本文目的
本文會介紹Vue3.0「組合api的用法和注意點」。最后會用一個 Todolist 的項目實戰,向大家介紹「Vue3.0的邏輯復用寫法以及借用provide和inject的新型狀態管理方式」
本文提綱:
- 如何新建一個使用vue3.0的項目
- conposition api
- 邏輯復用(hook)和狀態管理(provide+inject)
- 結合項目實戰,做一個todo list
正文
如何新建一個使用vue3.0的項目
接下來向大家簡單介紹下如何嘗鮮 -- 自己創建一個vue3.0的項目。
我這邊使用的是最新版本的vue-cli 4.4.0
npm?install?-g?@vue/cli#?ORyarn?global?add?@vue/cliok了。就這么簡單!
conposition api
#### 目錄
- 基本例子
- setup()
- reactive
- ref
- computed
- watchEffect
- watch
- 生命周期
- 依賴注入
基本例子
??????count?is?{{?count.count?}}????plusOne?is?{{?plusOne?}}????count++??setup
?
該setup功能是新的組件選項。它是組件內部暴露出所有的屬性和方法的統一API。
?
調用時機
創建組件實例,然后初始化 props ,緊接著就調用setup 函數。從生命周期鉤子的視角來看,它會在 beforeCreate 鉤子之前被調用
模板中使用
如果 setup 返回一個對象,則對象的屬性將會被合并到組件模板的渲染上下文
??{{?count?}}?{{?object.foo?}}setup 參數
- 不要在子組件中修改props;如果你嘗試修改,將會給你警告甚至報錯。
- 不要結構props。結構的props會失去響應性。
2.「上下文對象」第二個參數提供了一個上下文對象,從原來 2.x 中 this 選擇性地暴露了一些 property。
const?MyComponent?=?{??setup(props,?context)?{????context.attrs????context.slots????context.emit??},}Tip:
由于vue3.x向下兼容vue2.x,所以我在嘗試之后發現,一個vue文件中你可以同時寫兩個版本的東西。
import?{?reactive,?computed,?watch,?onMounted?}?from?'vue'export?default?{??name:?'HelloWorld',??props:?{????count:?Number,??},??data?()?{????return?{??????msg:?"我是vue2.x中的this"????}??},??methods:?{????test?()?{??????console.log(this.msg)????}??},??mounted?()?{????console.log('vue2.x?mounted')??},??//?eslint-disable-next-line?no-unused-vars??setup?(props,?val)?{????console.log(this,?'this')?//?undefined????onMounted(()?=>?{??????console.log('vue3.x?mounted')????})????return?{??????...props????}??}}當然這邊不推薦你在項目中這么用,但是抱著嘗鮮和探究的態度,我們勢必要弄清如果這么寫要注意哪些?
setup中的先執行。因為setup() 在解析 2.x 選項前被調用;
首先在setup中的this將不再指向vue,而是undefined;所以在setup函數內部自然無法訪問到vue實例上的this。
setup內部定義的變量和外表的變量并無沖突;
但是如果你要將其return 暴露給template,那么就會產生沖突。
reactive
?
接收一個普通對象然后返回該普通對象的響應式代理。等同于 2.x 的 Vue.observable()
?
const?obj?=?reactive({?count:?0?})ref
?
接受一個參數值并返回一個響應式且可改變的 ref 對象。ref 對象擁有一個指向內部值的單一屬性 value。
?
const?count?=?ref(0)console.log(count.value)?//?0count.value++console.log(count.value)?//?1tip:
computed
computed和vue2.x版本保持一致,支持getter和setter
- 傳入一個 getter 函數,返回一個默認不可手動修改的 ref 對象。
- 或者傳入一個擁有 get 和 set 函數的對象,創建一個可手動修改的計算狀態。
watchEffect
?
傳入的一個函數,并且立即執行,響應式追蹤其依賴,并在其依賴變更時重新運行該函數。
?
注冊監聽
import?{watchEffect}from?'vue'?//?導入apiconst?count?=?ref(0)?//?定義響應數據watchEffect(()?=>?console.log(count.value))?//?注冊監聽函數//?->?打印出?0setTimeout(()?=>?{??count.value++??//?->?打印出?1},?100)注銷監聽
- 默認情況下是在**組件卸載**的時候停止監聽;- 也可以顯示**調用返回值**以停止偵聽;
const?stop?=?watchEffect(()?=>?{??/*?...?*/})//?之后stop()清除副作用
> 有時副作用函數會執行一些異步的副作用, 這些響應需要在其失效時清除(即完成之前狀態已改變了)。所以偵聽副作用傳入的函數可以接收一個 onInvalidate 函數作入參, 用來注冊清理失效時的回調。
當以下情況發生時,這個失效回調會被觸發:
- 副作用即將重新執行時
- 偵聽器被停止
副作用刷新時機
> Vue 的響應式系統會緩存副作用函數,并異步地刷新它們,這樣可以避免同一個 tick 中多個狀態改變導致的不必要的重復調用。在核心的具體實現中, 組件的更新函數也是一個被偵聽的副作用。當一個用戶定義的副作用函數進入隊列時, 會在所有的組件更新后執行:
??{{?count?}}在這個例子中:
- count 會在初始運行時同步打印出來
- 更改 count 時,將在組件更新后執行副作用。
如果副作用需要同步或在組件更新之前重新運行,我們可以傳遞一個擁有 flush 屬性的對象作為選項(默認為 'post'):
//?同步運行watchEffect(??()?=>?{????/*?...?*/??},??{????flush:?'sync',??})//?組件更新前執行watchEffect(??()?=>?{????/*?...?*/??},??{????flush:?'pre',??})watch
> watch API 完全等效于 2.x this.$watch (以及 watch 中相應的選項)。watch 需要偵聽特定的數據源,并在回調函數中執行副作用。默認情況是懶執行的,也就是說僅在偵聽的源變更時才執行回調。
- 對比 watchEffect,watch 允許我們:
- 懶執行副作用;
- 更明確哪些狀態的改變會觸發偵聽器重新運行副作用;
- 訪問偵聽狀態變化前后的值。
- 偵聽單個數據源
偵聽器的數據源可以是一個擁有返回值的 getter 函數,也可以是 ref:
//?偵聽一個?getterconst?state?=?reactive({?count:?0?})watch(??()?=>?state.count,??(count,?prevCount)?=>?{????/*?...?*/??})//?直接偵聽一個?refconst?count?=?ref(0)watch(count,?(count,?prevCount)?=>?{??/*?...?*/})- 偵聽多個數據源
- 與 watchEffect 共享的行為
watch 和 watchEffect 在停止偵聽, 清除副作用 (相應地 onInvalidate 會作為回調的第三個參數傳入),副作用刷新時機 和 助聽器調試 等方面行為一致.
生命周期鉤子函數
?
可以直接導入 onXXX 一組的函數來注冊生命周期鉤子,這些生命周期鉤子注冊函數只能在 setup() 期間同步使用,在卸載組件時,在生命周期鉤子內部同步創建的偵聽器和計算狀態也將自動刪除。
?
- 「與 2.x 版本生命周期相對應的組合式 API」
- beforeCreate -> 使用 setup()
- created -> 使用 setup()
- beforeMount -> onBeforeMount
- mounted -> onMounted
- beforeUpdate -> onBeforeUpdate
- updated -> onUpdated
- beforeDestroy -> onBeforeUnmount
- destroyed -> onUnmounted
- errorCaptured -> onErrorCaptured
- 新增的鉤子函數
- onRenderTracked
- onRenderTriggered
兩個鉤子函數都接收一個DebuggerEvent,與 watchEffect 參數選項中的 onTrack 和 onTrigger 類似:
export?default?{??onRenderTriggered(e)?{????debugger????//?檢查哪個依賴性導致組件重新渲染??},}依賴注入
?
provide 和 inject 提供依賴注入,功能類似 2.x 的 provide/inject。兩者都只能在當前活動組件實例的 setup() 中調用。
?
這是本篇文章的重點。結合項目實戰以此來探索一下未來的 Vue 狀態管理模式和邏輯復用模式。
「用法」
?
provide 和 inject 提供依賴注入,功能類似 2.x 的 provide/inject。兩者都只能在當前活動組件實例的 setup() 中調用。
?
import?{?provide,?inject?}?from?'vue'const?ThemeSymbol?=?Symbol()const?Ancestor?=?{??setup()?{????provide(ThemeSymbol,?'dark')??},}const?Descendent?=?{??setup()?{????const?theme?=?inject(ThemeSymbol,?'light'?/*?optional?default?value?*/)????return?{??????theme,????}??},}inject 接受一個可選的的默認值作為第二個參數。如果未提供默認值,并且在 provide 上下文中未找到該屬性,則 inject 返回 undefined。
- 「注入的響應性」
可以使用 ref 來保證 provided 和 injected 時間值的響應:
//?提供者:const?themeRef?=?ref('dark')provide(ThemeSymbol,?themeRef)//?使用者:const?theme?=?inject(ThemeSymbol,?ref('light'))watchEffect(()?=>?{??console.log(`theme?set?to:?${theme.value}`)})如果注入一個響應式對象,則它的狀態變化也可以被偵聽。
邏輯組合與復用
引出問題:
我們通常會基于一堆相同的數據進行花樣呈現,有列表展示、有餅圖占比、有折線圖趨勢、有熱力圖說明頻次等等,這些組件使用的是相同的一些數據和數據處理邏輯。對于數據處理邏輯,目前vue有
- Mixins
- 高階組件 (Higher-order Components, aka HOCs)
- Renderless Components (基于 scoped slots / 作用于插槽封裝邏輯的組件)
但是上面的方案是存在一些弊端:
項目預覽
源碼:https://github.com/961998264/todolist-vue-3.0
項目介紹
項目src目錄
hooks文件夾是專門放hook的
context文件夾以模塊劃分
先來看下context編寫(我這邊是用的ts)
import?{?provide,?ref,?Ref,?inject,?computed,?}?from?'vue'?//vue?apiimport?{?getListApi?}?from?'api/home'?//?mock的api//?以下為定義的ts類型,你也可以單獨建一個專門定義類型的文件。type?list?=?listItem[]interface?listItem?{??title:?string,??context:?string,??id:?number,??status:?number,}interface?ListContext?{??list:?Ref,??getList:?()?=>?{},??changeStatus:?(id:?number,?status:?number)?=>?void,??addList:?(item:?listItem)?=>?void,??delList:?(id:?number)?=>?void,??finished:?Ref,??unFinish:?Ref,??setContext:?(id:?number,?context:?string)?=>?void,??setActiveItem:?()?=>?void,}provide名稱,推薦用Symbol
const?listymbol?=?Symbol()提供provide的函數
export?const?useListProvide?=?()?=>?{??//?全部事件???const?list?=?ref([]);??//?當前查看的事件id??const?activeId?=?ref(null)??//?當前查看的事件??const?activeItem?=?computed(()?=>?{????if?(activeId.value?||?activeId.value?===?0)?{??????const?item?=?list.value.filter((item:?listItem)?=>?item.id?===?activeId.value)??????return?item[0]????}?else?{??????return?null????}??})??//?獲取list??const?getList?=?async?function?()?{????const?res:?any?=?await?getListApi()????console.log("useListProvide?->?res",?res)????if?(res.code?===?0)?{??????list.value?=?res.data????}??}??//?新增list??const?addList?=?(item:?listItem)?=>?{????list.value.push(item)??}??//修改狀態??const?changeStatus?=?(id:?number,?status:?number)?=>?{????console.log('status',?status)????const?removeIndex?=?list.value.findIndex((listItem:?listItem)?=>?listItem.id?===?id)????if?(removeIndex?!==?-1)?{??????list.value[removeIndex].status?=?status????}??};??//?修改事件信息??const?setContext?=?(id:?number,?context:?string)?=>?{????const?Index?=?list.value.findIndex((listItem:?listItem)?=>?listItem.id?===?id)????if?(Index?!==?-1)?{??????list.value[Index].context?=?context????}??}??//?刪除事件??const?delList?=?(id:?number)?=>?{????console.log("delList?->?id",?id)????for?(let?i?=?0;?i??{????return?list.value.filter(item?=>?item.status?===?0)??})??//?已完成事件列表??const?finished?=?computed(()?=>?{????return?list.value.filter(item?=>?item.status?===?1)??})????provide(listymbol,?{????list,????unFinish,????finished,????changeStatus,????getList,????addList,????delList,????setContext,????activeItem,????activeId??})}在這個函數中定義 待辦事件,并且定義一系列增刪改查函數,通過provide暴露出去。
提供inject的函數
export?const?useListInject?=?()?=>?{??const?listContext?=?inject(listymbol);??if?(!listContext)?{????throw?new?Error(`useListInject?must?be?used?after?useListProvide`);??}??return?listContext};全局狀態肯定不止一個模塊,所以在 context/index.ts 下做統一的導出
import?{?useListProvide,?useListInject?}?from?'./home/index'console.log("useListInject",?useListInject)export?{?useListInject?}export?const?useProvider?=?()?=>?{??useListProvide()}然后在 App.vue 的根組件里使用 provide,在最上層的組件中注入全局狀態。
import { useProvider } from './context/index'export default { name: 'App', setup () { useProvider() return { } }}在組件中獲取數據:
import?{?useListInject?}?from?'../../context/home/index'setup?()?{??const?{?list,?changeStatus,?getList,?unFinish,?finished,?addList,?a???ctiveItem,?setContext?}?=?useListInject()}不管是父子組件還是兄弟組件,或者是比關系套更深的組件,我們都可以通過useListInject來獲取到相應式的數據。
作者:Mingle
轉發鏈接:https://mp.weixin.qq.com/s/iOq-eeyToDXJ6lvwnC12DQ
總結
以上是生活随笔為你收集整理的onmounted vue3_基于项目时间阐述vue3.0新型状态管理和逻辑复用方式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python无师自通配套资源_Pytho
- 下一篇: vue如何使用原生js写动画效果_深入理