composition API重构mixin实践

composition API重构mixin实践_第1张图片

写在前面

为什么要用composition API

  • 业务项目中的 mixin 代码逻辑繁杂,开发维护成本高,亟待重构, vue3 composition API 是 解决 mixin 现有问题的方案之一;

  • 出于长远考虑,vue3 稳定后,项目也会逐步迁移到 vue3 版本(毕竟咱们都是技术的弄潮儿 ???? ),提前迁移部分功能也是在为后续的迁移做准备。

本文重点关注 composition API 改造 vue2 项目中的mixin,API的使用请参考 vue3官方文档-高阶指南-组合式API(https://v3.cn.vuejs.org/guide/composition-api-introduction.html)。

1.composition API 简介

composition API,也叫做组合式API,它可以将同一个逻辑关注点相关的代码配置在一起,能够解决大型组件因选项分离导致的碎片化问题、降低代码复杂度和定位单一逻辑问题的难度。

做了一夜动画,就为让大家更好的理解Vue3的Composition Api。

composition API重构mixin实践_第2张图片
为了形象理解 composition API,在这里推荐大帅老师的文章
(https://juejin.cn/post/6890545920883032071)

2.composition API 对比 mixin

mixin缺点:
  • 渲染上下文中使用的属性来源不清晰。(例如在阅读一个运用了多个 mixin 的模板时,很难看出某个属性是从哪个 mixin 中注入的)

  • 命名空间冲突。(mixin 之间的属性和方法可能有冲突)

composition API优点:
  • 暴露给模板的属性来源十分清晰,因为它们都是被组合逻辑函数返回的值。

  • 不存在命名空间冲突,可以通过解构任意命名

  • 不需要为逻辑复用创建组件实例

  • 仅依赖它的参数和 Vue 全局导出的 API,而不依赖 this 上下文

3.如何在vue2项目中使用composition API

这里使用提供了 composition API 的vue2的插件, @vue/composition-api

  • 项目中安装  @vue/composition-api

npm install @vue/composition-api
# or
yarn add @vue/composition-api
  • 项目中使用

import Vue from 'vue'
import VueCompositionAPI from '@vue/composition-api'

Vue.use(VueCompositionAPI)

4.尝试改造mixin---失败案例

4.1 踩坑之旅开启

这里先是选择了一段自认为比较简单的 mixin 进行改造,事实证明,实践才是检验真理的唯一标准,哈哈哈。。。

// 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
          )
        })
    }
  }
}
4.2 开始改造

将上面 mixin 代码改造,抽离到 composables 文件夹下 placeOrderPlan.js文件中

// 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组件中引用

import placeOrderPlan from '@/app/help-sale/composables/placeOrderPlan.js'

export default {
  setup(props, context) {
    const { placeOrderPlan } = placeOrderPlan()
    return {
      placeOrderPlan
    }
  }
}
composition API重构mixin实践_第3张图片

保存运行,这里会看到浏览器控制台报错,因为 setup 里面是访问不到 this 的,也就是说我们无法通过 this 访问到 router 和 vuex 中的属性和方法,于是疑问点来了 ????

4.3 setup里面要怎样访问到route、router、vuex

google了一下,基本上就是需要升级 vue-router@4 ,vuex,升级会有哪些坑点,感觉可以再花费一段时间来研究下了。

// 升级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

composition API重构mixin实践_第4张图片

但是这个 context 貌似提供的属性跟我在官网上看到的不太一样啊,官网上明明说的三个 property,并没有提到parent 和 root 这两个属性,为什么这里特殊提到 parent 和 root 属性,因为从打印结果看 root 完全就相当于暴露了 this,我可以通过 root/parent 属性,去访问到组件现有的 mixin 等数据

composition API重构mixin实践_第5张图片

为了确认官网文档是不是少写了,也是出于好奇心,我初始化了个 vue3 的项目(当然这里正确的打开方式应当去扒源码,我偷个懒 ???? ),打印后,果然官网骗了我,暴露的不止3个属性,但是确实也没有 root 和 parent 属性。

composition API重构mixin实践_第6张图片

也就是说我们当前引入@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方法该如何访问呢?问题又来了????

4.4 怎么在setup中访问到全局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
      }
  }
}

5 尝试改造mixin---成功案例

不抛弃,不放弃???? ,还是选了个真正简单的例子改造了一下。

有多简单

  • 没有使用 vue-router,vuex 的场景

  • 没有嵌套 mixin 或调用全局 mixin 的场景

  • 也没有 watch,computed 的场景

待改造mixin代码如下:
// 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)
      
   // 此处代码省略...

  }
}

保存代码,完美运行????????????

6 总结

composition API 重构 vue2 mixin

  • 可以在不升级vue3的条件下,使用 @vue/composition-api,但是跟官方 vue3 正式包的 compositon API 提供的能力有出入(root,parent),强行使用不利于后续的 vue3 升级;

  • 改造的代码涉及 vue-router,vuex 的相关操作需要升级 vue-router,vuex,升级带来的风险和踩坑点,有待尝试;

  • 获取 query 通过 props 注入的方式也可以实现,但是让所用到的组件都传入 query,改造成本较高;

  • mixin 的逻辑面向组件,使用 composition API 需要改成面向功能,可能需要剥离 mixin 中功能+工具方法;

  • mixin 的改造,拆入到 setup 中的功能逻辑相对简单,但是其他绑定在this上的偏工具类的逻辑方法,如果不放到 setup 中(绑定到 this上),就需要单独抽离成业务工具方法,需要通过传参替代原来的 this.参数 的获取,带来的是相应调用地方的改造成本,尤其是用到的全局 mixin

  • composition APIvuex 对比,有点像是一个个拆出的小 store,那么 composition API 会替代 vuex 吗?参考 《你是否应该使用Composition API替代Vuex》?(https://zhuanlan.zhihu.com/p/320445941);

  • compostion API 的缺点:面条代码,可以查看《 简明扼要聊聊 Vue3.0 的 Composition API 是啥东东》(https://zhuanlan.zhihu.com/p/320445941);

  • 思考:什么样的代码适合改造成(使用) composition API

感谢你的阅读,有任何问题,欢迎评论区留言讨论,互相学习。

composition API重构mixin实践_第7张图片

你可能感兴趣的:(vue,java,html,javascript,js)