4种方案带你探索Vue代码复用的前世今生

 更新时间:2023年05月30日 09:30:47   作者:Lvzl  
我们所熟知的Vue.js也在如何提取公共代码复用方面也一直在探索优化,本文小编就来和各位聊聊Vue.js代码复用的前世今生,希望对大家学习Vue有一定的帮助

前言

在我们平时开发中,不论你使用什么语言,当遇到了大量的重复代码,我们可能会去将重复代码提取出来,独立一个模块,在多个地方引用,这是一个好习惯,是值得推荐的!当然也有些同学不感冒,使用到了直接CV,撇开代码规范设计模式这些不谈,往往CV会给你带来更大的工作量(比如用了很多地方,你要去CV很多地方,如果后续有变动,你又要重复CV到很多地方......,当然不推荐CV)。我们所熟知的Vue.js也在如何提取公共代码复用方面也一直在探索优化,本文笔者就来和各位聊聊Vue.js代码复用的前世今生

在Vue.js中我们可通过以下4种方案来实现代码逻辑复用

  • mixin
  • 高阶组件
  • 作用域插槽(scoped slots)
  • Composition API 组合式函数

可能各位常用的是mixin,没关系,其他几种也很好理解。笔者会通过一个实际的案例分别使用以上的方案实现,并分析各种方案的优缺点来带各位掘友体会Vue.js代码逻辑复用方面的优化历程。

案例:就以大家所熟知的 鼠标位置 来吧

Vue.js 代码逻辑复用

我们先不考虑复用,先来看看如何实现鼠标位置这个功能,功能十分简单,大家肯定都会,笔者就不废话了,直接看下代码吧:

基础实现

<script src="https://unpkg.com/vue@next"></script>
<div id="app"></div>
<script>
  const { createApp } = Vue
  const App = {
    template: `{{x}}  {{y}}`,
    data() {
      return {
        x: 0,
        y: 0
      }
    },
    methods: {
      handleMouseMove(e) {
        this.x = e.pageX
        this.y = e.pageY
      }
    },
    mounted() {
      window.addEventListener('mousemove', this.handleMouseMove)
    },
    unmounted() {
      window.removeEventListener('mousemove', this.handleMouseMove)
    }
  }
  createApp(App).mount('#app')
</script>

效果:

接下来,我们尝试将这个功能提取以达到复用的目的,先来看看 mixin 这个方案。

mixin

简单来说,mixin允许我们提供一个或多个像普通实例对象一样包含实例选项的对象,Vue.js会以一定的逻辑自动合并这些对象里面的选项和组件的选项。举例来说,如果你的 mixin 包含了一个 created 钩子,而组件自身也有一个,那么这两个函数都会被调用。本文不再赘述,请参考Vue.js——mixins。以下就是通过mixin实现复用MouseMove的逻辑:

<script>
  const { createApp } = Vue
  const MouseMoveMixin = {
    data() {
      return {
        x: 0,
        y: 0
      }
    },
    methods: {
      handleMouseMove(e) {
        this.x = e.pageX
        this.y = e.pageY
      }
    },
    mounted() {
      window.addEventListener('mousemove', this.handleMouseMove)
    },
    unmounted() {
      window.removeEventListener('mousemove', this.handleMouseMove)
    }
  }
  const App = {
    template: `{{x}}  {{y}}`,
    mixins: [ MouseMoveMixin ]
  }
  createApp(App).mount('#app')
</script>

效果与之前的一致。

我们来分析下mixin的缺点:

  • 当我们的组件有多个mixin,比如:mixins: [ MouseMoveMixin, anthorMixin, fooMixin ],我们就会分不清哪些变量是从MouseMoveMixin来的?哪些变量是从anthorMixin来的?那就出现了第一个缺点:变量来源不清
  • 同样的,当我们的组件有多个mixin,我们不得不去考虑他们注入的变量名会不会存在冲突。那就出现了第二个缺点:命名冲突

高阶组件

所谓高阶组件,就是通过实现一个包装函数,这个包装函数返回像普通实例对象一样包含实例选项的对象,该对象内包含render选项,render用于渲染内部的组件,并将属性通过props注入到内部组件。比如我们可以像下面这样通过高阶组件复用这个鼠标位置的逻辑。

<script>
  const { createApp, h } = Vue
  // 包装函数
  function withMouse(inner) {
    return {
      data() {
        return {
          x: 0,
          y: 0
        }
      },
      methods: {
        handleMouseMove(e) {
          this.x = e.pageX
          this.y = e.pageY
        }
      },
      mounted() {
        window.addEventListener('mousemove', this.handleMouseMove)
      },
      unmounted() {
        window.removeEventListener('mousemove', this.handleMouseMove)
      },
      render() {
        // 注入 x, y
        return h(inner, { x: this.x, y: this.y })
      }
    }
  }
  const App = withMouse({
    template: `{{x}}  {{y}}`,
    props: ['x', 'y']
  })
  createApp(App).mount('#app')
</script>

我们再来分析下,用高阶组件来实现逻辑复用,是不是就没有缺点呢?

同样的,我们还是假设我有还多块逻辑要复用,比如把mixins: [ MouseMoveMixin, anthorMixin, fooMixin ]改写成高阶组件,那将变成以下代码:

  function withMouse(inner) {
    // 此处省略
  }
  function withFoo(inner) {
    // 此处省略
  }
  function withAnthor(inner) {
    // 此处省略
  }
  const App = withAnthor(withFoo(withMouse({
    template: `{{x}}  {{y}}`,
    props: ['x', 'y', 'foo', 'anthor']
  })))
  createApp(App).mount('#app')

mixin的问题它都有,props中我们依然看不清哪些属性是由哪个高阶组件注入的,也依然不得不考虑命名冲突的问题。(有些同学可能觉得,如果注入的变量名能够和包裹函数名有联系,那就能够看出来。那确实是的,但是这就需要有很严格的开发规范代码走查来约束开发人员了)显然高阶组件也不是什么”灵丹妙药“,我们接着看如何使用scoped slots来实现这个逻辑复用。

作用域插槽(scoped slots)

作用域插槽(scoped slots)这种方式和高阶组件有点像,区别在于不是通过函数来包裹,而是通过实现一个组件来包裹,我们叫它父组件,在父组件实现需要复用的逻辑,使用作用域插槽,将父组件的状态共享给子组件。代码实现如下:

<script>
  const { createApp } = Vue
  const MouseMove = {
    data() {
      return {
        x: 0,
        y: 0
      }
    },
    methods: {
      handleMouseMove(e) {
        this.x = e.pageX
        this.y = e.pageY
      }
    },
    mounted() {
      window.addEventListener('mousemove', this.handleMouseMove)
    },
    unmounted() {
      window.removeEventListener('mousemove', this.handleMouseMove)
    },
    // 等价于 template: `<slot :x="x" :y="y"></slot>`,
    render() {
      return this.$slots.default && this.$slots.default({
        x: this.x,
        y: this.y
      })
    }
  }
  const App = {
    template: `<MouseMove v-slot="{x, y}">{{x}}  {{y}}</MouseMove>`,
    components: { MouseMove }
  }
  createApp(App).mount('#app')
</script>

我们还是来分析下这种方式的优缺点,还是通过假设我们需要重用多个逻辑,把mixins: [ MouseMoveMixin, anthorMixin, fooMixin ]改写为使用作用域插槽:

  const MouseMove = {
  }
  const Foo = {
  }
  const Anthor = {
  }
  const App = {
    template: `
    <MouseMove v-slot="{ x, y }">
      <Foo v-slot="{ foo }">
        <Anthor v-slot="{ anthor }">
          {{x}} {{y}} {{foo}} {{anthor}}
        </Anthor>
      </Foo>
    </MouseMove>`,
    components: { MouseMove, Foo, Anthor }
  }
  createApp(App).mount('#app')

看上去是解决了上面两个问题了,我们能够很明显的看到每个属性是从哪个组件注入的,来源清晰了,即使有命名的问题,我们在解构的时候是可以重命名避免的,比如Foo注入的也叫x,那我们可以这么写<Foo v-slot="{ x: foo }">

那是不是这样就完美了呢?并没有,细心的同学可能发现了,我们为了复用逻辑导致了更多的组件实例创建,是不是有点鱼和熊掌不可兼得的感觉,我们接下来看Vue.js的终极大招——Composition API 组合式函数

Composition API 组合式函数

先简单介绍下Composition API

组合式 API (Composition API) 是一系列 API 的集合,使我们可以使用函数而不是声明选项的方式书写 Vue 组件。它包含了这些API:

  • 响应式API —— ref、reactive computed、watch......
  • 生命周期钩子 —— onMounted、onUnmounted......
  • 依赖注入 —— provide、inject......

接着我们用Composition API来实现一下:

<script>
  const { createApp, ref, onMounted, onUnmounted } = Vue
  function useMouseMove() {
    const x = ref(0)
    const y = ref(0)
    const handleMouseMove = e => {
      x.value = e.pageX
      y.value = e.pageY
    }
    onMounted(() => {
      window.addEventListener('mousemove', handleMouseMove)
    })
    onUnmounted(() => {
      window.removeEventListener('mousemove', handleMouseMove)
    })
    return { x, y }
  }
  const App = {
    setup() {
      const { x, y } = useMouseMove()
      return { x, y }
    },
    template: `{{x}} {{y}}`,
  }
  createApp(App).mount('#app')
</script>

看完这个实现,首先它肯定是没有以上的各种问题的,同时Composition API也是Vue3的一个重大更新,能够让我们更轻松的组织我们的逻辑代码,更轻松的达到逻辑复用,可谓是完美方案!

可能你还有点小问题,比如setup为啥要先解构,再返回 { x, y }

能直接返回useMouseMove()吗

  const App = {
    setup() {
      return useMouseMove()
    },
    template: `{{x}} {{y}}`,
  }

答:如果你没有其他变量需要暴露出去,你当然可以直接返回useMouseMove()。但是直接返回useMouseMove(),那又回到了之前的问题,又不能清晰地看出哪个变量是哪个组合式函数注入的。

我能不能在return的对象里解构

  const App = {
    setup() {
      return {
        ...useMouseMove()
      }
    },
    template: `{{x}} {{y}}`,
  }

答:可以,但不推荐,这么写还是又回到了之前的问题。

最佳实践

  const App = {
    setup() {
      const { x, y } = useMouseMove()
      return { x, y }
    },
    template: `{{x}} {{y}}`,
  }

总结

本文用Vue.js四种逻辑复用的方案实现了 鼠标位置 的例子,并且分析了每种方案的优缺点。

  • mixin —— 存在 命名冲突、变量来源不清
  • 高阶组件 —— 存在 命名冲突、变量来源不清
  • 作用域插槽(scoped slots)—— 为了逻辑复用导致更多组件实例创建,得不偿失
  • Composition API 组合式函数 —— 完美方案

相信读完本文,你一定学到了在Vue.js搭建的应用中实现代码逻辑复用的最佳姿势!

以上就是4种方案带你探索Vue代码复用的前世今生的详细内容,更多关于Vue代码复用的资料请关注脚本之家其它相关文章!

相关文章

  • 详解Vue.js基于$.ajax获取数据并与组件的data绑定

    详解Vue.js基于$.ajax获取数据并与组件的data绑定

    这篇文章主要介绍了详解Vue.js基于$.ajax获取数据并与组件的data绑定,非常具有实用价值,需要的朋友可以参考下
    2017-05-05
  • vue动态子组件的两种实现方式

    vue动态子组件的两种实现方式

    这篇文章主要介绍了vue动态子组件的两种实现方式,非常不错,具有一定的参考借鉴价值,需要的朋友可以参考下
    2019-09-09
  • Vue打包后不同版本详细解析

    Vue打包后不同版本详细解析

    vue项目打包是我们日常经常遇到的,下面这篇文章主要给大家介绍了关于Vue打包后不同版本详细解析的相关资料,文中通过图文介绍的非常详细,需要的朋友可以参考下
    2022-12-12
  • Vue.js watch监视属性知识点总结

    Vue.js watch监视属性知识点总结

    在本篇文章里小编给大家分享的是关于Vue.js watch监视属性的相关知识点内容,需要的朋友们学习下。
    2019-11-11
  • Vue组件如何设置Props实例详解

    Vue组件如何设置Props实例详解

    props主要用于组件的传值,他的工作就是为了接收外面传过来的数据,与data、el、ref是一个级别的配置项,下面这篇文章主要给大家介绍了关于Vue组件如何设置Props的相关资料,需要的朋友可以参考下
    2022-06-06
  • vue页面切换过渡transition效果

    vue页面切换过渡transition效果

    这篇文章主要介绍了vue页面切换过渡transition效果,本文通过实例代码给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2018-10-10
  • Vue一次性简洁明了引入所有公共组件的方法

    Vue一次性简洁明了引入所有公共组件的方法

    这篇文章主要介绍了Vue一次性简洁明了引入所有公共组件的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-11-11
  • vue移动端项目中如何实现页面缓存的示例代码

    vue移动端项目中如何实现页面缓存的示例代码

    这篇文章主要介绍了vue移动端项目中如何实现页面缓存的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-03-03
  • vuex 实现getter值赋值给vue组件里的data示例

    vuex 实现getter值赋值给vue组件里的data示例

    今天小编就为大家分享一篇vuex 实现getter值赋值给vue组件里的data示例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2019-11-11
  • 小白教程|一小时上手最流行的前端框架vue(推荐)

    小白教程|一小时上手最流行的前端框架vue(推荐)

    这篇文章主要介绍了前端框架vue,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-04-04

最新评论