爲何要用composition API?javascript
本文重點關注 composition API 改造 vue2 項目 mixin,API的使用還請參考 vue3官方文檔-高階指南-組合式APIhtml
composition API,也叫作組合式API,它能夠將同一個邏輯關注點相關的代碼配置在一塊兒,從而解決大型組件因選項分離致使的碎片化問題,下降代碼複雜度和定位某個單一邏輯問題的難度。vue
爲了形象理解 composition API,在這裏推薦強烈推薦大帥老師的文章
這裏使用提供了 composition API 的vue2的插件, @vue/composition-api。vue-router
npm install @vue/composition-api
# or
yarn add @vue/composition-api
複製代碼
import Vue from 'vue'
import VueCompositionAPI from '@vue/composition-api'
Vue.use(VueCompositionAPI)
複製代碼
這裏先是選擇了一段自認爲比較簡單的 mixin 進行改造,事實證實,實踐纔是檢驗真理的惟一標準,哈哈哈。。。vuex
// placeOrderPlanMixin.js
import { getPreShuntTestAjax } from '@/apiData/helpsale'
export default {
data() {
return {
placeOrderPlan: 'D'
}
},
created() {
this.fetchPreShunt()
},
methods: {
// 獲取訂單預分流方案
fetchPreShunt() {
const { cateId } = this.$route.query || {}
getPreShuntTestAjax()
.then((res) => {
this.placeOrderPlan = res
})
.catch((e) => {
console.log(e)
})
.finally(() => {
this.$lego(
{
actiontype: 'bm-h2-place-order-preshunt',
cateId,
orderPlanType: this.placeOrderPlan
},
false
)
})
}
}
}
複製代碼
將上面 mixin 代碼改造,抽離到 composables 文件夾下 placeOrderPlan.js文件中npm
// composables/placeOrderPlan.js
import { getPreShuntTestAjax } from '@/apiData/helpsale'
import { ref, onMounted } from '@vue/composition-api'
export default function() {
let placeOrderPlan = ref('D')
const fetchPreShunt = () => {
const { cateId } = this.$route.query || {}
getPreShuntTestAjax()
.then((res) => {
placeOrderPlan.value = res
})
.catch((e) => {
console.log(e)
})
.finally(() => {
this.$lego(
{
actiontype: 'bm-h2-place-order-preshunt',
cateId,
orderPlanType: placeOrderPlan.value
},
false
)
})
}
onMounted(() => {
fetchPreShunt()
})
return {
placeOrderPlan
}
}
複製代碼
vue組件中引用api
import placeOrderPlan from '@/app/help-sale/composables/placeOrderPlan.js'
export default {
setup(props, context) {
const { placeOrderPlan } = placeOrderPlan()
return {
placeOrderPlan
}
}
}
複製代碼
保存運行,這裏會看到瀏覽器中報錯,由於 setup 裏面是訪問不到 this 的,也就是咱們沒法經過 this 訪問到 router 和 vuex 中的屬性和方法,因而疑問點來了 🤔瀏覽器
google了一下,基本上就是須要升級 vue-router@4 ,vuex,升級會有哪些坑點,感受能夠再花費一段時間來研究下了。bash
// 升級vue-router@4, vuex後的用法,舉個栗子
import {useRouter} from 'vue-router'
import {useRoute} from 'vue-router'
import {useStore} from 'vuex'
export default {
setup() {
const router = useRouter()
const route = userRoute()
const store = userStore()
onMounted(() => {
store.dispatch('changeName', route.query.name)
})
const jumpUrl = () => {
router.push({
path:'/index',
query:route.query
})
}
return {
jumpUrl
}
}
}
複製代碼
固然也能夠經過setup的props來獲取 query 參數,可是須要在使用到setup的組件中都傳入 query 屬性,改形成本高了很多。
// 組件中
import placeOrderPlan from '@/app/help-sale/composables/placeOrderPlan.js'
export default {
props: {
query:{}, // 這裏傳入query
},
setup(props) {
const { placeOrderPlan } = placeOrderPlan(props) // 這裏傳入props
return {
placeOrderPlan
}
}
}
複製代碼
// composables/placeOrderPlan.js
import { getPreShuntTestAjax } from '@/apiData/helpsale'
import { ref, onMounted } from '@vue/composition-api'
export default function(props) {
let placeOrderPlan = ref('D')
const fetchPreShunt = () => {
// const { cateId } = this.$route.query || {}
// 這裏就能夠訪問到組件的props參數
const { cateId } = props.query || {}
// ... 此處省略一萬行代碼
}
onMounted(() => {
fetchPreShunt()
})
return {
placeOrderPlan
}
}
複製代碼
這裏出於好奇,在組件中打印了下 props,context,
// 組件中
import placeOrderPlan from '@/app/help-sale/composables/placeOrderPlan.js'
export default {
props: {
query:{},
},
setup(props, context) {
console.log('props: ', props)
console.log('context: ', context)
const { placeOrderPlan } = placeOrderPlan(props) // 這裏傳入props
return {
placeOrderPlan
}
}
}
複製代碼
能夠看到,props 裏確實能夠取到組件傳入的屬性 query
可是這個 context 貌似提供的屬性跟我看到的官網不太同樣啊,官網上明明說的三個 property,並無提到有 parent 和 root 這兩個屬性,爲何這裏特殊提到 parent 和 root 屬性,由於從打印結果看 root 徹底就至關於暴露了 this,我能夠經過 root/parent 屬性,去訪問到組件現有的 mixin 等一些數據了
爲了確認官網文檔是否是少寫了,也是出於好奇心,我初始化了個 vue3 的項目(固然這裏正確的打開方式應當去扒源碼,我偷個懶 😏 ),打印後,果真官網騙了我,暴露的不止3個屬性,可是也確實沒有 root 和 parent 屬性。
也就是說咱們當前引入的包,@vue/composition-api,未來遷移 vue3 是要考慮直接換成官方正式包 vue@3 以上,經過 root 和 parent 調用方法是不可取的,直接替換就會有報錯。
而 @vue/composition-api 文檔上提到的能夠直接遷移 vue@3 包仍是有風險的,除非咱們嚴格不使用 root 和 parent 屬性
咱們暫且忽略root和parent屬性的坑點,保存執行,會看到瀏覽器裏還在報錯,
// composables/placeOrderPlan.js
import { getPreShuntTestAjax } from '@/apiData/helpsale'
import { ref, onMounted } from '@vue/composition-api'
export default function(props) {
let placeOrderPlan = ref('D')
const fetchPreShunt = () => {
const { cateId } = props.query || {}
getPreShuntTestAjax()
.then((res) => {
placeOrderPlan.value = res
})
.catch((e) => {
console.log(e)
})
.finally(() => {
this.$lego( // 這裏報錯
{
actiontype: 'bm-h2-place-order-preshunt',
cateId,
orderPlanType: placeOrderPlan.value
},
false
)
})
}
onMounted(() => {
fetchPreShunt()
})
return {
placeOrderPlan
}
}
複製代碼
咱們再定位到 $lego 所在的全局 mixin 方法,由於setup中不支持訪問this,掛在在this上的全局mixin方法該如何訪問,因而,問題又來了,🤔
網上你們都是在講 composition API 如何替代 mixin,難道全局的 mixin 也要替換成 composition API,而後在全部組件中引入?顯然這種狀況已經不適於改形成 compostion API,若是改形成工具函數呢,再看看咱們的 $setCommonBackup 方法裏的邏輯,獲取埋點基礎參數,這麼多個綁定在 this 上的參數,須要經過傳參的方式進行傳入,改形成本巨大,至此,改造當前 mixin 的過程就此終止了 🙃
// utils/mixin.js 全局mixin
export default {
methods: {
$lego({ actiontype, ...rest }, isClick = true) {
const type = isClick ? '__CLICK' : '__SHOW'
actiontype = actiontype.toUpperCase() + type
const urlRouterName = (this.$route && this.$route.name) || 'u-bmmain'
const backup = { ...rest, ...this.$setCommonBackup() } // 這裏調用$setCommonBackup獲取基礎參數
lego.send({
actiontype,
backup
})
},
$setCommonBackup() {
const { name = '', query = {} } = this.$route || {}
// 此處省略一萬行...
const logsMark = `U_BM-Main_${name}`
const urlRouterName = name || 'u-bmmain'
// 這裏訪問this上掛在的$route.query
const uFrom = this.$route.query.uFrom || ''
const cateId = this.$route.query.cateId || ''
const servicefrom = this.$route.query.servicefrom || ''
// 這裏訪問this上的方法
let planType = this.$ABPlanType()
const params = {
logsMark,
urlRouterName,
channel,
channnelSouce,
uFrom,
pageCateId: cateId,
planType,
servicefrom
}
// 這裏訪問this上的屬性
if (this.cateId) {
Object.assign(params, { cateId: this.cateId })
}
// 這裏訪問this上的方法
if (this.$bmFrom()) {
Object.assign(params, { bmFrom: this.$bmFrom() })
}
return params
}
}
}
複製代碼
不拋棄,不放棄💪 ,仍是選了個真正簡單的例子改造了一下。
有多簡單
// correlate-mixin/jumpEvaPlan.js
import { getCommonABTestAjax } from '@/apiData/common.js'
export default {
data() {
return {
evaluateType: ''
}
},
created() {
this.getEvaABTestChannel()
},
methods: {
// 獲取估價頁AB測跳轉具體頁面
getEvaABTestChannel() {
getCommonABTestAjax({
testId: 10171
})
.then((res) => {
this.evaluateType = res
})
.catch((e) => {
console.log('e: ', e)
})
},
getEvaRouteName(type) {
const evaluateType = type || this.evaluateType
let routeName = ''
switch (evaluateType) {
case 'D':
routeName = 'helpsale-evaluate-Dplan'
break
case 'B':
routeName = 'helpsale-evaluate-Bplan'
break
case 'C':
routeName = 'helpsale-evaluate-Cplan'
break
default:
routeName = 'helpsale-evaluate'
break
}
return routeName
}
}
}
複製代碼
功能抽象到 composables/getCommonABTest.js
// composables/getCommonABTest.js
import { getCommonABTestAjax } from '@/apiData/common.js'
import { ref, onMounted } from '@vue/composition-api'
export default function getABTest(testId) {
let planType = ref('')
const getCommonABTest = () => {
getCommonABTestAjax({
testId
})
.then((res) => {
planType.value = res
})
.catch((e) => {
console.log('e: ', e)
})
}
onMounted(() => {
getCommonABTest() // 接口請求
})
return {
planType // 對外暴露的響應式屬性
}
}
複製代碼
原 mixin 引入的組件,都須要加上 setup
// fastType/index.vue
import getABTest from '@/app/help-sale/composables/getCommonABTest.js'
export default {
setup(props) {
const { planType } = getABTest(10171)
// 假如一個頁面有多個AB測
const res = getABTest(123)
const res2 = getABTest(456)
return {
evaluateType: planType,
test: res.planType,
test2: res2.planType
}
}
}
複製代碼
可能細心的童鞋會發現原 mixin 中👇 這坨代碼去哪裏了
getEvaRouteName(type) {
const evaluateType = type || this.evaluateType
let routeName = ''
switch (evaluateType) {
case 'D':
routeName = 'helpsale-evaluate-Dplan'
break
case 'B':
routeName = 'helpsale-evaluate-Bplan'
break
case 'C':
routeName = 'helpsale-evaluate-Cplan'
break
default:
routeName = 'helpsale-evaluate' // 默認估價A方案
break
}
return routeName
}
複製代碼
它被改形成公共函數了(實際上這裏想一想,確實這個方法沒有必要掛載在 this 上的,可是經過 mixin 方式掛載到 this 上的方式,對於兜底的 this.evaluateType 就不用傳入了,改造後的就須要各個調用的地方傳入 this.evaluateType)
// utils/getEvaRouteName.js
const getEvaRouteName = (type) => {
// 👇 原來這裏兜底的this.evaluateType也變成必傳的了
// const evaluateType = type || this.evaluateType 此行廢棄
let routeName = ''
switch (type) {
case 'D':
routeName = 'helpsale-evaluate-Dplan'
break
case 'B':
routeName = 'helpsale-evaluate-Bplan'
break
case 'C':
routeName = 'helpsale-evaluate-Cplan'
break
default:
routeName = 'helpsale-evaluate' // 默認估價A方案
break
}
return routeName
}
export default getEvaRouteName
複製代碼
// fastType/index.vue
import getEvaRouteName from '@/app/help-sale/utils/getEvaRouteName.js'
export default {
methods: {
navEvaluatePage() {
// 此處代碼省略...
// const routename = this.getEvaRouteName() 以前的調用方式
const routename = getEvaRouteName(this.evaluateType)
// 此處代碼省略...
}
}
複製代碼
保存代碼,完美運行👏👏👏
composition API 重構 vue2 mixin: