Vue3 逻辑复用

225 阅读4分钟

Vue2 逻辑复用

mixins混入实现代码复用

合并策略(同Vue.Extend):数据对象递归合并,以组件优先;值为对象(methods, computed等)的选项以组件优先;同名钩子合并为数组,混入对象的钩子调用在前。

合并策略的类型

  • 叠加形:通过原型链进行层层的叠加,componentdirectivesfilters
  • 队列形:存入一个数组,正序遍历依次执行,生命周期函数watch
  • 合并形:调用set合并、重新赋值,data
  • 替换形:后者替代前者,propsmethodsinjectcomputed

对于想要使用自定义的选项合并逻辑,使用 'Vue.config.optionMergeStrategies'

全局混入使用

应尽量避免全局混入使用,这会对每个组件实例造成影响,大多数情况下作为注册插件使用来全局注册一些组件选项

// 以注册日期时间格式化插件为例(使用全局混入)
const FormatDateMixin = {
  install(Vue) {
    const options = {
      data() {
        return {};
      },
      methods: {
        getFormatDate(v, format = 'time') {
          return dayjs.format(v, format);
        },
      },
    };
    Vue.mixin(options);
  },
};

mixins实现的弊端

  • 数据来源不清晰,不利于追溯组件行为
  • 多个数据源可能造成的命名冲突问题
  • 多个 mixin 需要依赖共享的属性名来进行相互作用,增加了耦合性

mixins与组件区别的理解

组件在引用之后相当于在父组件内开辟了一块单独的空间,根据props传参进行通信,单本质上两者还是泾渭分明,相对独立。

而mixins则是在引入组件之后,则是将组件内部的选项与父组件相应内容进行合并。相当于在引入后,父组件被扩充了


Vue3逻辑复用

组合式函数

利用组合式api封装和复用有状态逻辑的函数

// 抽离列表公用逻辑
import { reactive, ref } from 'vue'  
  
/**  
 * @description usePage 接收一个 opts 参数,返回列表数据  
 * @param {Object} opts.searchForm - 查询参数  
 * @param {Function} opts.getListApi - 获取数据  
 * @param {Function} opts.exportListApi - 导出数据  
 * @param {Function} opts.resetFunc - 重置查询条件
 * @param {Function} opts.sizeChangeFunc 
 * @param {Function} opts.currentChangeFunc 
 */  
export const usePage = (opts) => {  
  const {  
    searchParams,  
    getListApi,  
    exportListApi,  
    resetFunc = () => {},  
    sizeChangeFunc = () => {},  
    currentChangeFunc = () => {},
    // ... 一些其他的选项,例如,请求异常处理
  } = opts  
  
  const reset = () => {  
    handleCurrentChange(1)
    // ...
  }  
  
  const page = reactive({  
    pageSize10,  
    pageNum1,  
    total0  
  })  
  
  const tableData = ref([]);
  const loadingRef = ref(false);
  const loadData = () => {  
    const opts = {  
      ...page,  
      ...searchParams 
    }  
  
    getListApi(opts).then((res) => {   
        // ... 
    })  
  }
  
  const exportFile = () => {
      exportListApi({ ...searchParams }).then((res) => {   
        // ... 
      }) 
  }
  
  const handleSizeChange = (size) => {  
    // ... 
    loadData();
  }  
  
  const handleCurrentChange = (cur) => {  
    // ...
    loadData()
  }  
  
  // 避免直接返回响应性对象,这会导致对象解构过程中丢失响应性
  // 或使用toRefs()保证解构时不丢失响应性
  return {   
    reset,  
    page,  // toRefs(page)
    tableData,  
    handleSizeChange,  
    handleCurrentChange,
    loadData,
    exportFile
  }  
}

复用示例: 以逻辑关注点进行抽离,例如常见的列表增删改查等

组合式函数:约定和最佳实践


插件使用

Vue.use(), 相同的插件仅注册一次

插件可以添加全局功能的类型

  1. 使用过滤器、指令添加全局资源。如添加滚动指令;图片预览指令(实际开发场景常使用utl+token进行预览)。
  2. 添加vue实例方法(通过添加到Vue.prototype上)。如添加全局loading, message弹窗功能。
  3. 使用全局混入添加一些组件选项。如添加返回列表/回退功能,添加全局的日期时间格式化处理器等。
  4. 一个库,提供自己的 API,同时提供上面提到的一个或多个功能。如 vue-router。
// 以注册自定义滚动指令为例
ScrollBarPlugin.install = function(Vue) {
  Vue.directive('scrollbar', {
    inserted(el) {
      const rules = ['fixed', 'absolute', 'relative'];
      if (!rules.includes(window.getComputedStyle(el).position)) {
        .....
      }
      el.ps = new PerfectScrollbar(el, ...);
    },
    componentUpdated(el, binding, vnode) {
      if (!el.ps) return;
      vnode.context.$nextTick(() => {
        el.ps.update();
      });
    },
    unbind(el) {
      if (!el.ps) return;
      el.ps.destroy();
      el.ps = null;
    },
  });
}

Vue.use()插件注册原理

  • 获取Vue对象的已注册插件列表,判断是否已注册,不允许重复注册
  • 根据接受的参数类型座不同处理,若接受的plugin为一个对象且该对象拥有install方法,则调用install方法,如果plugin是一个函数,它会被作为 install 方法被调用,最后给这个插件添加至已经添加过的插件数组中,标示已经注册过

核心代码实现:

Vue.use = function (plugin: Function | Object) {
    const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
    if (installedPlugins.indexOf(plugin) > -1) {
        return this
    }
    // additional parameters
    const args = toArray(arguments, 1)
    args.unshift(this)
    if (typeof plugin.install === 'function') {
        plugin.install.apply(plugin, args)
    } else if (typeof plugin === 'function') {
        plugin.apply(null, args)
    }
    installedPlugins.push(plugin)
    return this
}

综上得出插件开发的两种方式:

1. 将这个插件的逻辑封装成一个对象,最后将在 install 编写业务代码暴露给 Vue 对象。推荐这种做法,可拓展性比高

2. 将所有逻辑都编写成一个函数暴露给 Vue