封装一个简单的$message

156 阅读1分钟

在使用Vue开发过程中使用一个组件很简单,只需要把组件引入然后使用即可,这一类组件也是比较好开发的。但是如果要开发一个组件,这个组件在其他地方以命令的方式调用、渲染,这个会比较难一些,需要对vue组件化开发有更深入的理解才行。

下面是我自己封装的一个$message的思考过程;

1、先开发一个组件

开发命令式组件之前,我先把正常编写组件需要怎么写先写了一遍。

  • data中需要定义一个消息队列,通过v-for的方式渲染消息队列,这样可以让多条消息的时候会上下排列,我们不再需要去关注消息的先后顺序。

      <div class="alert">
        <div class="alert-main" v-for="item in notices" :key="item.name">
          <div class="alert-content">{{ item.content }}</div>
        </div>
      </div>
    
  • 这个组件需要有往消息队列里新增消息的方法。

  • 新增的消息在规定时间会自动移除。

    addMessage(msg){
        const id = uuidv4();
        const notice = Object.assign({id},msg);
        this.notices.push(notice);
        const duration = notice?.duration ? notice.duration:1000;
        setTimeout(()=>{
            this.remove(id);
        },duration);
    }
    remove(id){
        for(let i = this.notices.length-1;i>=0;i--){
            if(this.notices[i].id == id){
                this.notices.splice(i,1);
                return;
            }
        }
    }
    

    以上,一个消息组件基本盘就搞定了。

    2、组件命令化

​ 创建一个js中间文件对组件进行管控,而不再是通过在vue组件中管控。

​ 那么接下来无非就是创建该组件,挂载该组件实例,然后把该组件的方法进行全局化。这样的好处:

​ 1)所有组件在使用到该组件的新增消息的方法的时候都是对同一个消息组件进行操作。

​ 2)并且只能通过我们对外暴露的方法去影响该控件,那么组件的使用逻辑能有较好的保障。

组件化核心就是获取组件实例:只要获取到了组件实例,那么我们可以在它身上拿到$el,data,methods等....一切的一切我们都可以操作了

message.js

import Vue from 'vue';
import message from 'message';

message.newInstance = (props)=>{
    const prop = props || {};
    //返回的是一个Vue实例,该实例自带子组件message
    const componentVm = new Vue({
        data:prop,
        render(h){
            return h(message,{
                props:prop
            })
        }});
    //获取$el
    componentVm.$mount();
    document.querySelector('body').appendChild(componentVm.$el);
    //获取message组件实例
    const messageVue = componentVm.$children[0];
      return {
        $message:messageVue.addMessage
    }
}

export default message;
  • 通过引入我们先前开发好的组件,然后利用Vue的render去构造该组件,获得vue组件实例对象,但是此时我们render重写了,所以渲染出的Vue组件实例自带message组件的信息。

  • 组件实例对象在没有调用$mount的时候我们是获取不到$el的,在获取完$el后手动进行插入到body中。

  • vue组件实例的$children[0]是真正的message组件实例,该实例上能获取到我们的addMessage方法、remove方法等。

    ·组件化难点:

在使用 Vue.js 的时候,通常会有两种情况需要考虑runtime和编译器:

  1. 编译器版本:当使用完整版的 Vue.js(包含编译器)时,在实例化组件时可以直接使用模板字符串进行渲染
  2. 运行时版本(runtime):当使用运行时版本的 Vue.js 时,无法直接使用模板字符串进行渲染,需要通过 Render 函数手动创建 VNode 来描述组件结构和属性。

下面是一个示例代码,展示了在runtime版本下使用 Render 函数和编译器版本下使用模板字符串进行组件实例化的区别:

// Runtime 版本,使用 Render 函数
const AlertRuntime = Vue.component('alert-runtime', {
  render(h) {
    return h('div', 'This is an alert component (Runtime version)');
  }
});
new Vue({
  el: '#app-runtime',
  render: h => h(AlertRuntime)
});
// 编译器版本,使用模板字符串
const AlertCompiler = Vue.component('alert-compiler', {
  template: '<div>This is an alert component (Compiler version)</div>'
});

new Vue({
  el: '#app-compiler',
  components: { AlertCompiler },
  template: '<alert-compiler/>'
});

所以使用render函数去构造组件是最稳妥的方法。

·使用extend创建组件实例,替代render

除了使用render去创建runtime下的vue组件,还可以通过Vue.extend去创建一个组件实例。

import Vue from 'vue';
import message from './message.vue';

message.newInstance = (props)=>{
    //把这一步的返回结果理解成一个Vue一样的类。
    const component = Vue.extend(message)
    //把这一步理解成new Vue,但是我需要我们再去重写render了,并且返回的直接就是组件实例,而不是Vue实例
    const componentVm = new component();
    componentVm.$mount();
    document.querySelector('body').appendChild(componentVm.$el);
    return {
        $message:componentVm.addMessage
    }
}

export default message;

​ 使用Vue.extend创造出的的Vue组件子类可以像new Vue()一样去实例化成组件实例。此时构造出的组件实例就是message的,而不是Vue的。通过该方法能快速拿到组件实例,不需要从vue组件实例下的$children[0]获取。

extend是创建组件类,component是注册公共组件。在runtime中无法使用template,但是结合extend还是可以组合全局组件

Vue.extend( options )

  • 参数

    • {Object} options
  • 用法

    使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。

    data 选项是特例,需要注意 - 在 Vue.extend() 中它必须是函数

    Vue.component( id, [definition] )

  • 参数

    • {string} id
    • {Function | Object} [definition]
  • 用法

    注册或获取全局组件。注册还会自动使用给定的 id 设置组件的名称

// 注册组件,传入一个扩展过的构造器 
Vue.component('my-component', Vue.extend({ /* ... */ }))  // 注册组件,传入一个选项对象 (自动调用 Vue.extend) 
Vue.component('my-component', { /* ... */ })  // 获取注册的组件 (始终返回构造器) 
var MyComponent = Vue.component('my-component')

Vue.component 的方式无法直接用于创建组件实例。Vue.component 方法主要用于全局注册组件,它接收两个参数:组件名称和一个包含组件选项的对象。

runtime版本下,使用 Vue.component 注册的组件将成为全局可用的组件,可以在模板中直接使用该组件的标签。但是,通过 Vue.component 方法注册的组件并不能直接创建组件实例,因为它仅用于组件的全局注册。

如果想要在 runtime 版本下创建组件实例,可以使用 Vue.extend 方法创建组件子类,然后实例化该子类来获得组件实例。这种方式更适合在 runtime 版本下按需地创建和使用组件实例。

下面是一个示例代码,展示了在 runtime 版本下无法直接使用 Vue.component 方法创建组件实例:

在 runtime 版本下无法直接使用 Vue.component 创建组件实例
Vue.component('alert-component', {
template: '<div>This is an alert component created with Vue.component</div>'
});

// 尝试创建组件实例会报错
const alertInstance = new Vue({
el: '#app-runtime',
template: '<alert-component/>'
});

在上述代码中,我们尝试使用 Vue.component 全局注册了一个名为 alert-component 的组件,并在模板中使用 <alert-component/> 标签来创建组件实例。但是这样的方式在runtime版本下会报错,因为 Vue.component 方法只用于全局注册组件,并不能直接用于创建组件实例。

因此,在runtime版本下,如果需要创建组件实例,最好使用Vue.extend方法来创建组件子类,并通过实例化子类来得到组件实例。

3、全局注册

通过上面的操作,已经把一个带有实例化组件方法的对象对外抛出了。我们只需要在vue的入口文件main.js中引入即可。

import message from './components/message'
...
Vue.prototype.$myMessage = message.newInstance();