关于vue组件封装的实例和思考

4,072 阅读2分钟

1. 项目全局弹窗 toast 插件封装

当前项目中有场景需要制作h5底部的弹窗,该弹窗可以分享链接、清空数据、内容举报、内容收藏等高频操作,基于此我们可以做成全局组件。官网提供的用法特别简单,写好的组件直接在main.js里面导入然后使用Vue.component('my-component-name', {  // ... 选项 ... })就可以全局通用了,但是存在的问题是:所有内容都是在 #app 下渲染,注册组件都是在当前位置渲染。如果我要实现一个模态框的提示组件,就比较鸡肋了。这时候,Vue.extend + vm.$mount 组合就派上用场了。

<template>
  <div class="operate-more" v-show="showPoup" @click="hide">
    <div class="wrapper" @click.stop>
      <div class="operates">
        <div class="item-info border-bottom-1px" v-for="(item, index) in options" :key="index" @click="onClick(item)">
          {{ item.name }}
        </div>
      </div>
      <div class="cancel" @click="hide">取消</div>
    </div>
  </div>
</template>
<script>
export default {
  data() {
    return {
      showPoup: false,
      options: [] // 传参数据
    };
  },
  methods: {
    init(options) {
      this.options = options;
    },
    // 展示
    show() {
      this.showPoup = true;
    },
    // 隐藏
    hide() {
      this.showPoup = false;
    },
    // 执行回调函数
    onClick(item) {
      if (item.onClick && typeof item.onClick === 'function') {
        this.hide();
        item.onClick();
      }
    }
  }
};
</script>
<style lang="less" scoped>
@keyframes showPoup {
  0% {
    transform: translate3d(-50%, 100%, 0);
  }
  100% {
    transform: translate3d(-50%, 0, 0);
  }
}
.operate-more {
  position: fixed;
  left: 0;
  right: 0;
  top: 0;
  bottom: 0;
  z-index: 999;
  background: rgba(0, 0, 0, 0.5);
  .wrapper {
    position: absolute;
    left: 50%;
    transform: translate3d(-50%, 0, 0);
    bottom: 0;
    width: 3.59rem;
    animation: showPoup ease 300ms;
    .operates {
      .item-info {
        display: flex;
        align-items: center;
        height: 0.56rem;
        font-size: 0.16rem;
        font-weight: normal;
        color: #fc4245;
        line-height: 0.22rem;
        background: #ffffff;
        justify-content: center;
        &:first-child {
          border-radius: 0.12rem 0.12rem 0 0;
        }
        &:last-child {
          border-radius: 0 0 0.12rem 0.12rem;
          &::after {
            border: none;
          }
        }
        &:only-child {
          border-radius: 0.12rem;
        }
      }
    }
    .cancel {
      display: flex;
      align-items: center;
      justify-content: center;
      height: 0.48rem;
      font-size: 0.16rem;
      margin: 0.08rem 0;
      border-radius: 0.12rem;
      color: #1f2126;
      background: #ffffff;
      color: #2972ec;
    }
  }
}
</style>

在此处通过install注册全局插件,然后再main.js中通过 Vue.use(ClearHistory) 来调用install方法,

import ClearHistory from './ClearHistory';
export default {
  install(Vue) {
    const P = Vue.extend(ClearHistory);
    let instance;
    Vue.prototype.$clearhistory = options => {
      if (!instance) {
        // $mount()不带参数,会把组件在内存中渲染完毕, instance 表示创造了一个新的vue实例
        instance = new P().$mount();
        // instance.$el 拿到的就是组件对应的dom元素,可以直接操作dom
        document.body.appendChild(instance.$el);
      }
      Vue.nextTick(() => {
        // 直接调用 vue实例的 init 方法,实例中的options 值取到传过去的参数值
        instance.init(options);
        // 调用实例的show 方法可以展示组件
        instance.show();
      });
    };
  }
};

在具体业务中调用该组件:

const paramObj = [
  {name: 'test001', onClick: () => {}},
  {name: 'test002', onClick: () => {}},
];
this.$clearhistory(paramObj);

2. 关于 vue 组件封装的思考

我们在使用Vue做项目开发之余,都想着如何造轮子,给项目提供更加便捷的插件和更好的优化方式。基于vue的组件开发包含以下几个方面:

2.1 Vue.extend创建实例后挂载

使用vue构造器创建一个子类,参数是包含组件选项的对象。可以构造一个全局的组件并且挂在到指定的dom元素上:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
  <script src="https://cdn.bootcss.com/vue/2.5.17-beta.0/vue.min.js"></script>
</head>

<body>
  <div id="app-extend"></div>
</body>
<script>
  // 创建构造器
  const Profile = Vue.extend({
    template: '<div><p>{{extendData}}</p><p>实例传入的数据为:{{propsExtend}}</p></div>',
    data: function () {
      return {
        extendData: '这是extend扩展的数据',
      }
    },
    props: {
      propsExtend: String
    }
  })
  // 创建 Profile 实例,并挂载到一个元素上。携带外来数据 propsExtend
  const profile = new Profile({ propsData: { propsExtend: '我是实例传入的数据' } });
  profile.$mount('#app-extend');
</script>

</html>

2.2 Vue.component 注册实例

最简单也是最常用的组件注册方式:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
  <script src="https://cdn.bootcss.com/vue/2.5.17-beta.0/vue.min.js"></script>
</head>

<body>
  <div id="app">
    <div class="component-one">
      下面是组件一数据:
      <component-one></component-one>
    </div>
  </div>
</body>
<script> 
  //1.创建组件构造器
  const obj = {
    props: [],
    template: '<div><p>{{extendData}}</p></div>',//最外层只能有一个大盒子,这个和<tempalte>对应规则一致
    data: function () {
      return {
        extendData: '这是Vue.component传入Vue.extend注册的组件',
      }
    },
  };
  
  // 下面两种方法都可以注册实例:
  Vue.component('component-one', obj);
  Vue.component('component-one', Vue.extend(obj))

  //3.挂载
  new Vue({
    el: '#app'
  });
</script>

</html>

2.3 mixins 混入

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
  <script src="https://cdn.bootcss.com/vue/2.5.17-beta.0/vue.min.js"></script>
</head>

<body>
  <div id="app">
    <p>mixin的data值为:{{mixinData}}</p>
    <button @click="getSum()">点我触发getSum方法</button>
  </div>
</body>
<script>
  const mixin = {
    data: { mixinData: '我是mixin的data' },
    created: function () {
      console.log('这是mixin的created');
    },
    methods: {
      getSum: function () {
        console.log('这是mixin的getSum里面的方法');
      }
    }
  }

  const mixinTwo = {
    data: { mixinData: '我是mixinTwo的data' },
    created: function () {
      console.log('这是mixinTwo的created');
    },
    methods: {
      getSum: function () {
        console.log('这是mixinTwo的getSum里面的方法');
      }
    }
  }

  const vm = new Vue({
    el: '#app',
    data: { mixinData: '我是vue实例的data' },
    created: function () {
      console.log('这是vue实例的created');
    },
    methods: {
      getSum: function () {
        console.log('这是vue实例里面getSum的方法');
      }
    },
    mixins: [mixin, mixinTwo]
  })
</script>

</html>

2.4 install 和 Vue.use 构造和注册vue插件

这是我们日常造轮子的主要的方式,这些轮子都可以称之为插件,它的功能范围没有严格的限制,一般包含如下几种:

  • 添加全局方法或者属性。如: vue-custom-element
  • 添加全局资源:指令/过滤器/过渡/组件等。如 vue-touch
  • 通过全局混入来添加一些组件选项。如 vue-router
  • 添加 Vue 实例方法,通过把它们添加到 Vue.prototype 上实现。
  • 一个库,提供自己的 API,同时提供上面提到的一个或多个功能。如 vue-router 无论大小,插件要实现的功能无非就是上述这几种。但是,这并不妨碍我们创造出复杂的插件,不过我们还是希望给用户提供一个简单的使用方法,他不需要关注插件内部做了些什么。固Vue提供了use方法,专门来在new Vue()之前使用插件。

Vue.js 的插件应该暴露一个 install 方法。这个方法的第一个参数是 Vue 构造器,第二个参数是一个可选的选项对象,用于传入插件的配置:

MyPlugin.install = function (Vue, options) {
    // 1. 添加全局方法或属性
    Vue.myGlobalMethod = function () {

    }

    // 2. 添加全局资源
    Vue.directive('my-directive', {
        bind(el, binding, vnode, oldVnode) {
            
        }
    })

    // 3. 注入组件选项
    Vue.mixin({
        created: function () {

        }
    })

    // 4. 添加实例方法
    Vue.prototype.$myMethod = function (methodOptions) {
        
    }
    
    // 5. 注册全局组件
    Vue.component('myButton', {
        
    })
}

Vue.use(MyPlugin,{ });

下面关于全局插件注册的实例参考:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
  <script src="https://cdn.bootcss.com/vue/2.5.17-beta.0/vue.min.js"></script>
</head>

<body>
  <div id="app">
    <button v-initdirective>点击调用myGlobalMethod</button>
    <button @click="$myMethod">点击调用$myMethod</button>
  </div>
</body>
<script>
  const MyPlugin = {};
  MyPlugin.install = function (Vue, options) {
    // 1. 添加全局资源,第二个参数传一个值默认是update对应的值
    Vue.directive('initdirective', {
      //做绑定的准备工作,添加时间监听
      bind(el, binding, vnode, oldVnode) {
        console.log('指令my-directive的bind执行啦');
      },

      //获取绑定的元素
      inserted: function (el) {
        console.log('指令my-directive的inserted执行啦');
      },

      //根据获得的新值执行对应的更新,对于初始值也会调用一次
      update: function () {
        console.log('指令my-directive的update执行啦');
      },

      componentUpdated: function () {
        console.log('指令my-directive的componentUpdated执行啦');
      },

      //做清理操作比如移除bind时绑定的事件监听器
      unbind: function () {
        console.log('指令my-directive的unbind执行啦');
      }
    })

    // 2. 注入组件
    Vue.mixin({
      created: function () {
        console.log('注入组件的created被调用啦');
        console.log('options的值为', options)
      }
    })

    // 3. 添加实例方法
    Vue.prototype.$myMethod = function (methodOptions) {
      console.log('实例方法myMethod被调用啦');
    }
  }

  // 调用MyPlugin
  Vue.use(MyPlugin, { someOption: true })

  // 4.挂载
  new Vue({
    el: '#app'
  });

</script>

</html>