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 中我们不太清楚我们合并过来的到底是什么格式,但是在这里我们可以很容易做到这些。

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

相关推荐
她似晚风般温柔7891 小时前
Uniapp + Vue3 + Vite +Uview + Pinia 分商家实现购物车功能(最新附源码保姆级)
开发语言·javascript·uni-app
Jiaberrr2 小时前
前端实战:使用JS和Canvas实现运算图形验证码(uniapp、微信小程序同样可用)
前端·javascript·vue.js·微信小程序·uni-app
everyStudy2 小时前
JS中判断字符串中是否包含指定字符
开发语言·前端·javascript
城南云小白2 小时前
web基础+http协议+httpd详细配置
前端·网络协议·http
前端小趴菜、2 小时前
Web Worker 简单使用
前端
web_learning_3213 小时前
信息收集常用指令
前端·搜索引擎
Ylucius3 小时前
动态语言? 静态语言? ------区别何在?java,js,c,c++,python分给是静态or动态语言?
java·c语言·javascript·c++·python·学习
tabzzz3 小时前
Webpack 概念速通:从入门到掌握构建工具的精髓
前端·webpack
LvManBa3 小时前
Vue学习记录之六(组件实战及BEM框架了解)
vue.js·学习·rust
200不是二百3 小时前
Vuex详解
前端·javascript·vue.js