1. 程式人生 > 其它 >vue3的composition-api實踐總結

vue3的composition-api實踐總結

因為嚮往已久vue3的開發方式,但是組內有很多歷史專案,並且我們受制於ie的支援,所以我們決定在vue2中引入composition-api,來使用他的新特性。在使用過程中,我們遇到了很多問題,也積累了一些經驗,所以記錄下。

composition-api

首先給大家介紹一下composition-api,他是通過函式的形式,將vue的功能特性暴露給我們使用

雖然一下子看上去很多,但是隻需要先掌握加粗的這一部分,就可以體驗到組合式api的魅力了,其他的可以先做了解等到你真的需要使用它們的時候在做深入。

reactive 用來將物件轉變為響應式的,與vue2的observable類似,ref用來獲得單獨或者為基礎資料型別獲得響應性。為什會會有兩個獲得響應性的api呢稍後我們將具體說明。computed、watch,provide、inject不用懷疑和vue2中做的是一樣的事情。

你一定注意到下面這些加了on開頭的生命週期鉤子函式,沒錯在組合式api中,這就是他們註冊的方式。但是為什麼不見了beforeCreate和created呢?因為setup就是在這個階段執行的,而setup就是開啟組合式api世界的大門。你可以把setup理解為class的constructor,在vue元件的建立階段,把我們的相關邏輯執行,並且註冊相關的副作用函式。

現在我們說回ref和reactive。

  • reactive在官網中的說明,接受一個物件,返回物件的響應式副本。ref在官網中的描述"接受一個內部值並返回一個響應式且可變的 ref 物件。

  • ref 物件具有指向內部值的單個 property.value"。

聽著很繞口,簡單來講就是reactive可以為物件建立響應式而ref除了物件,還可以接收基礎資料型別,比如string、boolean等。
那為什麼會有這種差異呢?在vue3當中響應式是基於proxy實現的,而proxy的target必須是複雜資料型別,也就是存放在堆記憶體中,通過指標引用的物件。其實也很好理解,因為基礎資料型別,每一次賦值都是全新的物件,所以根本無法代理。那麼如果我們想取得簡單型別的響應式怎麼辦呢?這時候就需要用到ref。

`classRefImpl<T> {  
private_value:T  
  
publicreadonly__v_isRef=true  
  
constructor(private_rawValue:T,publicreadonly_shallow=false){  
this._value=_shallow?_rawValue:convert(_rawValue)  
}  
  
getvalue(){  
track(toRaw(this),TrackOpTypes.GET,'value')  
returnthis._value  
}  
  
setvalue(newVal){  
if(hasChanged(toRaw(newVal),this._rawValue)){  
this._rawValue=newVal  
this._value=this._shallow?newVal:convert(newVal)  
trigger(toRaw(this),TriggerOpTypes.SET,'value',newVal)  
}  
}  
}  
...  
constconvert=<Textendsunknown>(val:T):T=>  
isObject(val)?reactive(val):val  
...  
`

ref通過建立內部狀態,將值掛在value上,所以ref生成的物件,要通過value使用。重寫get/set獲得的監聽,同時對物件的處理,也依賴了reactive的實現。

由此,ref並不只是具有對基本資料型別的響應式處理能力,他也是可以處理物件的。所以我認為ref和reactive的區分並不應該只是簡單/複雜物件的區分,而是應該用程式設計思想區分的。我們應該避免,把reactive 當作data在頂部將所有變數宣告的想法,而是應該結合具體的邏輯功能,比如一個控制灰度的Flag那他就應該是一個ref,而分頁當中的頁碼,pageSize,total等就應該是一個reactive宣告的物件。也就是說一個setup當中可以有多出響應變數的宣告,而且他們應當是與邏輯緊密結合的.

接下來我先用一個分頁的功能,用選項式和組合式api給大家對比一下。

一些例子

`<template>  
<div>  
<ulclass="article-list">  
<liv-for="iteminarticleList":key="item.id">  
<div>  
<divclass="title">{{item.title}}</div>  
<divclass="content">{{item.content}}</div>  
</div>  
</li>  
</ul>  
<el-pagination  
@size-change="handleSizeChange"  
@current-change="handleCurrentChange"  
:current-page="currentPage"  
:page-sizes="pageSizes"  
:page-size="pageSize"  
layout="total,sizes,prev,pager,next,jumper"  
:total="total"  
>  
</el-pagination>  
</div>  
</template>  
  
<script>  
import{getArticleList}from'@/mock/index';  
exportdefault{  
data(){  
return{  
articleList:[],  
currentPage:1,  
pageSizes:[5,10,20],  
pageSize:5,  
total:0,  
};  
},  
created(){  
this.getList();  
},  
methods:{  
getList(){  
constparam={  
currentPage:this.currentPage,  
pageSizes:this.pageSizes,  
pageSize:this.pageSize,  
};  
getArticleList(param).then((res)=>{  
this.articleList=res.data;  
this.total=res.total;  
});  
},  
handleSizeChange(val){  
this.pageSize=val;  
this.getList();  
},  
handleCurrentChange(val){  
this.currentPage=val;  
this.getList();  
},  
},  
};  
</script>  
`

這還是我們熟悉到不能在熟悉的分頁流程,在data中宣告資料,在method中提供修分頁的方法。當我們用composition-api實現的時候他就成了下面的樣子。

`<script>  
import{defineComponent,reactive,ref,toRefs}from"@vue/composition-api";  
import{getArticleList}from"@/mock/index";  
exportdefaultdefineComponent({  
setup(){  
constpage=reactive({  
currentPage:1,  
pageSizes:[5,10,20],  
pageSize:5,  
total:0,  
});  
functionhandleSizeChange(val) {  
page.pageSize=val;  
getList();  
}  
functionhandleCurrentChange(val) {  
page.currentPage=val;  
getList();  
}  
  
constarticleList=ref([]);  
functiongetList() {  
getArticleList(page).then((res)=>{  
articleList.value=res.data;  
page.total=res.total;  
});  
}  
getList();  
return{  
...toRefs(page),  
articleList,  
getList,  
handleSizeChange,  
handleCurrentChange,  
};  
},  
});  
</script>  
`

這是以composition-api的方式實現的分頁,你會發現原本的data,method,還有宣告週期等選項都不見了,所有的邏輯都放到了setup當中。通過這一個簡單的例子,我們可以發現原本分散在各個選項中的邏輯,在這裡得到了聚合。這種變化在複雜場景下更為明顯。在複雜元件中,這種情況更加明顯。而且當邏輯完全聚集在一起,這時候,將他們抽離出來,而且抽離邏輯的可以在別處複用,至此hook就形成了。

hook形態的分頁元件

`//hooks/useArticleList.js  
import{ref}from"@vue/composition-api";  
import{getArticleList}from"@/mock/index";//mockajax請求  
  
functionuseArticleList() {  
constarticleList=ref([]);  
functiongetList(page) {  
getArticleList(page).then((res)=>{  
articleList.value=res.data;  
page.total=res.total;  
});  
}  
return{  
articleList,  
getList,  
};  
}  
exportdefaultuseArticleList;  
  
//hooks/usePage.js  
import{reactive}from"@vue/composition-api";  
  
functionusePage(changeFn) {  
constpage=reactive({  
currentPage:1,  
pageSizes:[5,10,20],  
pageSize:5,  
total:0,  
});  
functionhandleSizeChange(val) {  
page.pageSize=val;  
changeFn(page);  
}  
functionhandleCurrentChange(val) {  
page.currentPage=val;  
changeFn(page);  
}  
return{  
page,  
handleSizeChange,  
handleCurrentChange,  
};  
}  
exportdefaultusePage;  
  
//views/List.vue  
import{defineComponent,toRefs}from"@vue/composition-api";  
importusePagefrom"@/hooks/usePage";  
importuseArticleListfrom"@/hooks/useArticleList";  
exportdefaultdefineComponent({  
setup(){  
const{articleList,getList}=useArticleList();  
const{page,handleSizeChange,handleCurrentChange}=usePage(getList);  
getList(page);  
return{  
...toRefs(page),  
articleList,  
getList,  
handleSizeChange,  
handleCurrentChange,  
};  
},  
});  
`

在hook使用過程中我們也踩過很多坑

1. hook中的非同步問題

因為hook本質上就是函式,所以靈活度非常高,尤其是在涉及非同步的邏輯中,考慮不全面就很有可能造成很多問題。hook是可以覆蓋非同步情況的,但是必須在setup當中執行時返回有效物件不能被阻塞。

我們總結了兩種非同步的風格,通過一個簡單的hook為例

  • 外部沒有其他依賴,只是交付渲染的響應變數 對於這種情況,可以通過宣告、對外暴露響應變數,在hook中非同步修改的方式
`//hooks/useWarehouse.js  
import{reactive,toRefs}from'@vue/composition-api';  
import{queryWarehouse}from'@/mock/index';//查詢倉庫的請求  
importgetParamfrom'@/utils/getParam';//獲得一些引數的方法  
functionuseWarehouse(admin) {  
constwarehouse=reactive({warehouseList:[]});  
constparam={id:admin.id,...getParam()};  
constqueryList=async()=>{  
const{list}=awaitqueryWarehouse(param);  
list.forEach(goods=>{  
//一些邏輯...  
returngoods  
})  
warehouse.warehouseList=list;  
};  
return{...toRefs(warehouse),queryList};  
}  
`
`exportdefaultuseWarehouse;//components/Warehouse.vue  
<template>  
<div>  
<button@click="queryList">queryList</button>  
<ul>  
<liv-for="goodsinwarehouseList":key="goods.id">  
{{goods}}  
</li>  
</ul>  
</div>  
</template>  
  
<script>  
import{defineComponent}from'@vue/composition-api';  
importuseWarehousefrom'@/hooks/useWarehouse';  
exportdefaultdefineComponent({  
setup(){  
//倉庫保管員  
constadmin={  
id:'1234',  
name:'張三',  
age:28,  
sex:'men',  
};  
const{warehouseList,queryList}=useWarehouse(admin);  
return{warehouseList,queryList};  
},  
});  
</script>  
`
  • 外部具有依賴,需要在使用側進行加工的 可以通過對外暴露Promise的方式,使外部獲得同步操作的能力 在原有例子上拓展,增加一個需要處理的更新時間屬性
`//hooks/useWarehouse.js  
functionuseWarehouse(admin) {  
constwarehouse=reactive({warehouseList:[]});  
constparam={id:admin.id,...getParam()};  
constqueryList=async()=>{  
const{list,updateTime}=awaitqueryWarehouse(param);  
list.forEach(goods=>{  
//一些邏輯...  
returngoods  
})  
warehouse.warehouseList=list;  
returnupdateTime;  
};  
return{...toRefs(warehouse),queryList};  
}  
`
`exportdefaultuseWarehouse;//components/Warehouse.vue  
<template>  
<div>  
...  
<span>nextUpdateTime:{{nextUpdateTime}}</span>  
</div>  
</template>  
  
<script>  
...  
importdayjsfrom'dayjs';  
exportdefaultdefineComponent({  
setup(){  
...  
//倉庫保管員  
constadmin={  
id:'1234',  
name:'張三',  
age:28,  
sex:'men',  
};  
const{warehouseList,queryList}=useWarehouse(admin);  
constnextUpdateTime=ref('');  
constinterval=7;//假設更新倉庫的時間間隔是7天  
constqueryHandler=async()=>{  
constupdateTime=awaitqueryList();  
nextUpdateTime.value=dayjs(updateTime).add(interval,'day');  
};  
return{warehouseList,nextUpdateTime,queryHandler};  
},  
});  
</script>  
`

2. this的問題

因為setup是beforecreate階段,不能獲取到this,雖然通過setup的第二個引數context可以獲得一部分的能力。是我們想要操作諸如路由,vuex這樣的能力就收到了限制,最新的router@4、vuex@4都提供了組合式的api。

但是由於vue2的底層限制我們沒有辦法使用這些hook,但是我們可以通過引用例項的方式獲得一定的操縱能力,也可以通過getCurrentInstance獲得元件例項,上面掛載的物件。

由於composition-api中的響應式雖然底層原理與vue相同都是通過object.defineproperty改寫屬性實現的,但是具體實現方式存在差異,所以在setup當中與vue原生的響應式並不互通。這也導致即使我們拿到了相應的例項,也沒有辦法監聽它們的響應式。如果有這方面的需求,只能在選項配置中使用。

總結

通過vue3組合式、與hook的能力。我們的程式碼風格有了很大的轉變,邏輯更加聚合、純粹。複用效能力得到了提升。專案整體的維護性有了顯著的提高。這也是我們即便在vue2的專案中,也要使用composition-api引入vue3新特性的原因。