Vue中代码复用最佳实践的探索历程

写在前面

在编程世界中,代码复用一直是我们追求的目标之一,因为它可以帮助我们提高开发效率,减少重复劳动,同时也使得代码更加清晰和易于维护。而在 Vue.js 这个优秀的前端框架中,如何实现代码复用,又如何找到最佳实践,是我一直在探索的问题。

今天,我将以一个简单功能的实现为例,和大家分享一下我的探索历程,希望能够为大家提供一些有益的启示和参考。

基于 Options API 实现

js 复制代码
<script>
  const { createApp } = Vue;

  const App = {
    template: `{{x}}, {{y}}`,
    data() {
      return { x: 0, y: 0 };
    },
    mounted() {
      window.addEventListener('mousemove', this.update);
    },
    beforeDestory() {
      window.removeEventListener('mousemove', this.update);
    },
    methods: {
      update({ pageX: x, pageY: y }) {
        this.x = x;
        this.y = y;
      }
    }
  };

  createApp(App).mount('#app');
 </script>

这个功能会在页面上展示当前鼠标位置的坐标信息。 接下来,我将通过不同的方式实现代码复用,将此功能代码抽离出来。

Mixins

js 复制代码
<script>
  const { createApp } = Vue;

  const mouseMixin = {
    data() {
      return { x: 0, y: 0 };
    },
    mounted() {
      window.addEventListener('mousemove', this.update);
    },
    beforeDestory() {
      window.removeEventListener('mousemove', this.update);
    },
    methods: {
      update({ pageX: x, pageY: y }) {
        this.x = x;
        this.y = y;
      }
    }
  };

  const App = {
    mixins: [mouseMixin],
    template: `{{x}}, {{y}}, {{selfZ}}`,
    data() {
      return { selfZ: 'self' };
    }
  };

  createApp(App).mount('#app');
</script>

mixin已经实现了逻辑的复用,但是它有以下缺点:

  1. 没有自己的命名空间,容易造成命名冲突。
js 复制代码
<script>
  const { createApp } = Vue;

  const mouseMixin = {
    data() {
      return { x: 0, y: 0 };
    },
    methods: {
      update({ pageX: x, pageY: y }) {
        this.x = x;
        this.y = y;
      }
    }
  };

  const App = {
    mixins: [mouseMixin],
    template: `{{x}}, {{y}}`,
    methods: {
      // 来自mouseMixin的update和组件自身的update冲突
      update(){}
    }
  };

  createApp(App).mount('#app');
</script>

我们其实只需要 x y 这两个 props,其他的不需要暴露出来。

  1. 当有多个 mixins 应用到同一个组件时,我们无法直观的看到哪一个数据来自于哪一个 mixin 或者组件本身。
js 复制代码
<script>
  const { createApp } = Vue;

  const mouseMixin = {
    data() {
      return { x: 0, y: 0 };
    }
  };
  
  const anotherMixin = {
    data() {
      return { z: 0 };
    }
  };

  const App = {
    mixins: [mouseMixin, anotherMixin],
    // 无法清晰知道哪些props来自于哪些mixin或者组件本身,不利于团队协作开发和维护。
    template: `{{x}}, {{y}}, {{z}}`,
    data() {
      return {  };
    }
  };

  createApp(App).mount('#app');
</script>

使用 React 的高阶组件概念来实现

js 复制代码
<script>
  const { createApp, h } = Vue;

  const useMouse = function (Inner) {
    return {
      data() {
        return { x: 0, y: 0 };
      },
      mounted() {
        window.addEventListener('mousemove', this.update);
      },
      beforeDestory() {
        window.removeEventListener('mousemove', this.update);
      },
      methods: {
        update({ pageX: x, pageY: y }) {
          this.x = x;
          this.y = y;
        }
      },
      render() {
        return h(Inner, {
          x: this.x,
          y: this.y
        });
      }
    };
  };

  const App = useMouse({
    template: `{{x}}, {{y}}`,
    props: ['x', 'y']
  });

  createApp(App).mount('#app');
</script>

高阶组件内部有自己的命名空间,所以内部没有暴露出来的数据不存在命名冲突。不过假如有多个高阶组件同时应用的话,还是会存在下面的问题:

  1. 暴露出来的 props 还是会有命名冲突的风险
  2. 依然存在上面的问题,无法直观地看到数据来源
js 复制代码
<script>
  const { createApp, h } = Vue;

  const useMouse = Inner => {
    return {
      data() {
        return { x: 0, y: 0 };
      },
      mounted() {
        window.addEventListener('mousemove', this.update);
      },
      beforeDestory() {
        window.removeEventListener('mousemove', this.update);
      },
      methods: {
        update({ pageX: x, pageY: y }) {
          this.x = x;
          this.y = y;
        }
      },
      render() {
        return h(Inner, {
          ...this.props,
          x: this.x,
          y: this.y
        });
      }
    };
  };

  const useOther = Inner => {
    return {
      data() {
        return { x: 'x' };
      },
      render() {
        return h(Inner, {
          ...this.props,
          x: this.x
        });
      }
    };
  };

  const App = useOther(
    useMouse({
      template: `{{x}}, {{y}}`,
      props: ['x', 'y']
    })
  );

  createApp(App).mount('#app');
</script>

如上, props 中的 x 被覆盖,且无法确定数据来源。

使用slot来实现

js 复制代码
<script>
  const { createApp, h } = Vue;

  const Mouse = {
    data() {
      return { x: 0, y: 0 };
    },
    mounted() {
      window.addEventListener('mousemove', this.update);
    },
    beforeDestory() {
      window.removeEventListener('mousemove', this.update);
    },
    methods: {
      update({ pageX: x, pageY: y }) {
        this.x = x;
        this.y = y;
      }
    },
    render() {
      return this.$slots.default ? this.$slots.default({ x: this.x, y: this.y }) : '';
    }
  };

  const Other = {
    data() {
      return { x: 'x' };
    },
    render() {
      return this.$slots.default ? this.$slots.default({ x: this.x }) : '';
    }
  };

  const App = {
    template: `<Mouse v-slot="{x, y}">
                    <Other v-slot="{x: otherX}">
                        {{x}}, {{y}}, {{otherX}}
                    </Other>
                </Mouse>`,
    components: { Mouse, Other }
  };

  createApp(App).mount('#app');
</script>

到此为止,我们已经解决了上面暴露的所有问题。使用 slot 我们可以清晰的看到数据来自于哪一个组件,并且加入有命名冲突,我们可以向上述的处理一样使用别名。

但是,这里又有了新的思考,我们这里为了数据的复用,创建了很多组件,性能方面是否会变差?

毕竟,维护一个组件也是需要很多性能开销的。

使用 Composition API 来实现

js 复制代码
<script>
  const { createApp, ref, onMounted, onUnmounted } = Vue;

  const useMouse = () => {
    const x = ref(0);
    const y = ref(0);

    const update = e => {
      x.value = e.pageX;
      y.value = e.pageY;
    };

    onMounted(() => {
      window.addEventListener('mousemove', update);
    });

    onUnmounted(() => {
      window.removeEventListener('mousemove', update);
    });

    return {
      x,
      y
    };
  };

  const useOther = () => {
    const x = ref('otherX');

    return {
      x
    };
  };

  const App = {
    setup() {
      const { x, y } = useMouse();
      const { x: otherX } = useOther();

      return { x, y, otherX };
    },
    template: `{{x}}, {{y}}, {{otherX}}`
  };

  createApp(App).mount('#app');
</script>

我们好像又回到了js最初的模样。 使用 Composition API 使得我们不再需要考虑 mixins 中的命名空间等问题,也不需要考虑使用 scope slot 方案时造成的额外组件实例维护开销。我们可以轻松地将这些逻辑应用在任何我们想用的地方。

还有一个额外的好处就是,如果我们使用 TS 开发时,我们不需要再为类型推导头疼,在 mixin 中我们不太清楚我们合并过来的到底是什么格式,但是在这里我们可以很容易做到这些。

这些心得主要来自于尤大的讲解视频,从中受益良多,整理出来分享给大家,欢迎大家一起讨论,一起进步。

相关推荐
华科易迅6 分钟前
Vue如何集成封装Axios
前端·javascript·vue.js
康一夏7 分钟前
Next.js 13变化有多大?
前端·react·nextjs
糖炒栗子03268 分钟前
前端项目标准环境搭建与启动
前端
不是az8 分钟前
CSS知识点记录
前端·javascript·css
爱分享的阿Q17 分钟前
GPT6-Spud-AGI前夜的豪赌
前端·easyui·agi
昵称暂无11 小时前
.NET 高级开发 | i18n 原理、实现一个 i18n 框架
javascript·c#·.net
西西小飞龙1 小时前
Less/Sass Mixins vs. Extend
前端·less·sass
syjy21 小时前
(含下载)BeTheme WordPress主题使用教程
前端·wordpress·wordpress建站
Misnice1 小时前
shadcn如何使用
前端·reactjs
h_jQuery1 小时前
vue使用gm-crypto对数据进行sm4加密处理
前端·javascript·vue.js