Introducción a los complementos de Vue | Programar Plus

En los últimos meses, he aprendido mucho sobre Vue. Desde la creación de SPA compatibles con SEO hasta la creación de blogs fabulosos o jugando con transiciones y animaciones, he experimentado con el marco a fondo.

Pero ha faltado una pieza a lo largo de mi aprendizaje: complementos.

La mayoría de las personas que trabajan con Vue han llegado a depender de los complementos como parte de su flujo de trabajo o ciertamente se cruzarán con los complementos en algún lugar del camino. Cualquiera que sea el caso, son una excelente manera de aprovechar el código existente sin tener que escribir constantemente desde cero.

Es probable que muchos de ustedes hayan usado jQuery y estén acostumbrados a usar (¡o hacer!) Complementos para crear cualquier cosa, desde carruseles y modales hasta videos receptivos y tipografía. Básicamente, estamos hablando de lo mismo aquí con los complementos de Vue.

Entonces, ¿quieres hacer uno? Asumiré que asiente con la cabeza para que podamos ensuciarnos las manos con una guía paso a paso para escribir un complemento de Vue personalizado.

Primero, un poco de contexto …

Los complementos no son algo específico de Vue y, al igual que jQuery, encontrará que hay una amplia variedad de complementos que hacen muchas cosas diferentes. Por definición, indican que se proporciona una interfaz para permitir la extensibilidad.

Tachuelas de latón: son una forma de conectar funciones globales en una aplicación y extenderlas para su uso.

La documentación de Vue cubre los complementos con gran detalle y proporciona una excelente lista de categorías amplias en las que generalmente se incluyen los complementos:

  1. Agregue algunos métodos o propiedades globales.
  2. Agregue uno o más activos globales: directivas / filtros / transiciones, etc.
  3. Agregue algunas opciones de componentes por mezcla global.
  4. Agregue algunos métodos de instancia de Vue adjuntándolos a Vue.prototype.
  5. Una biblioteca que proporciona una API propia, mientras que al mismo tiempo inyecta alguna combinación de lo anterior.

BIEN BIEN. Basta de preludio. ¡Escribamos un código!

Lo que estamos haciendo

En Spektrum, la agencia madre de Snipcart, nuestros diseños pasan por un proceso de aprobación, como estoy seguro de que es típico en la mayoría de las otras tiendas y empresas. Permitimos que un cliente comente y haga sugerencias sobre los diseños a medida que los revisa para que, en última instancia, obtengamos luz verde para proceder y construir la cosa.

Generalmente usamos InVision para todo esto. El sistema de comentarios es un componente central de InVision. Permite a las personas hacer clic en cualquier parte del diseño y dejar un comentario para los colaboradores directamente donde esa retroalimentación tiene sentido. Es bastante genial.

A pesar de lo genial que es InVision, creo que podemos hacer lo mismo nosotros mismos con un poco de magia Vue y crear un complemento que cualquiera pueda usar también.

La buena noticia aquí es que no son tan intimidantes. Un conocimiento básico de Vue es todo lo que necesita para comenzar a manipular complementos de inmediato.

Paso 1. Prepare la base de código

Un complemento de Vue debe contener un install método que toma dos parámetros:

  1. Lo global Vue objeto
  2. Un objeto que incorpora opciones definidas por el usuario.

Iniciar un proyecto de Vue es muy simple, gracias a Vue CLI 3. Una vez que lo tengas instalado, ejecuta lo siguiente en tu línea de comando:

$ vue create vue-comments-overlay
# Answer the few questions
$ cd vue-comments-overlay
$ npm run serve

Esto nos da el comienzo clásico de “Hola mundo” que necesitamos para poner en marcha una aplicación de prueba que pondrá en uso nuestro complemento.

Paso 2. Crea el directorio de complementos

Nuestro complemento tiene que vivir en algún lugar del proyecto, así que creemos un directorio donde podamos meter todo nuestro trabajo, luego naveguemos por nuestra línea de comando hasta el nuevo directorio:

$ mkdir src/plugins
$ mkdir src/plugins/CommentsOverlay
$ cd src/plugins/CommentsOverlay

Paso 3: conecte el cableado básico

Un complemento de Vue es básicamente un objeto con un install función que se ejecuta siempre que la aplicación que la usa la incluye con Vue.use().

El install la función recibe el global Vue objeto como parámetro y objeto de opciones:

// src/plugins/CommentsOverlay/index.js
// 
export default {
  install(vue, opts){   
    console.log('Installing the CommentsOverlay plugin!')
    // Fun will happen here
  }
}

Ahora, conectemos esto en nuestra aplicación de prueba “Hello World”:

// src/main.js
import Vue from 'vue'
import App from './App.vue'
import CommentsOverlay from './plugins/CommentsOverlay' // import the plugin

Vue.use(CommentsOverlay) // put the plugin to use!

Vue.config.productionTip = false

new Vue({ render: createElement => createElement(App)}).$mount('#app')

Paso 4: proporcione soporte para las opciones

Queremos que el complemento sea configurable. Esto permitirá que cualquiera que lo use en su propia aplicación pueda modificar las cosas. También hace que nuestro complemento sea más versátil.

Haremos de las opciones el segundo argumento de la install función. Creemos las opciones predeterminadas que representarán el comportamiento base del complemento, es decir, cómo funciona cuando no se especifica una opción personalizada:

// src/plugins/CommentsOverlay/index.js

const optionsDefaults = {
  // Retrieves the current logged in user that is posting a comment
  commenterSelector() {
    return {
      id: null,
      fullName: 'Anonymous',
      initials: '--',
      email: null
    }
  },
  data: {
    // Hash object of all elements that can be commented on
    targets: {},
    onCreate(created) {
      this.targets[created.targetId].comments.push(created)
    },
    onEdit(editted) {
      // This is obviously not necessary
      // It's there to illustrate what could be done in the callback of a remote call
      let comments = this.targets[editted.targetId].comments
      comments.splice(comments.indexOf(editted), 1, editted);
    },
    onRemove(removed) {
      let comments = this.targets[removed.targetId].comments
      comments.splice(comments.indexOf(removed), 1);
    }
  }
}

Luego, podemos fusionar las opciones que se pasan al install función además de estos valores predeterminados:

// src/plugins/CommentsOverlay/index.js

export default {
  install(vue, opts){
    // Merge options argument into options defaults
    const options = { ...optionsDefaults, ...opts }
    // ...
  }
}

Paso 5: crea una instancia para la capa de comentarios

Una cosa que debe evitar con este complemento es que su DOM y sus estilos interfieran con la aplicación en la que está instalado. Para minimizar las posibilidades de que esto suceda, una forma de hacerlo es hacer que el complemento viva en otra instancia raíz de Vue, fuera del árbol de componentes de la aplicación principal.

Agregue lo siguiente al install función:

// src/plugins/CommentsOverlay/index.js

export default {
  install(vue, opts){
    ...
  // Create plugin's root Vue instance
      const root = new Vue({
        data: { targets: options.data.targets },
        render: createElement => createElement(CommentsRootContainer)
      })

      // Mount root Vue instance on new div element added to body
      root.$mount(document.body.appendChild(document.createElement('div')))

      // Register data mutation handlers on root instance
      root.$on('create', options.data.onCreate)
      root.$on('edit', options.data.onEdit)
      root.$on('remove', options.data.onRemove)

      // Make the root instance available in all components
      vue.prototype.$commentsOverlay = root
      ...
  }
}

Bits esenciales en el fragmento de arriba:

  1. La aplicación vive en una nueva div al final de body.
  2. Los controladores de eventos definidos en el options los objetos están conectados a los eventos coincidentes en la instancia raíz. Esto tendrá sentido al final del tutorial, lo prometo.
  3. El $commentsOverlay La propiedad agregada al prototipo de Vue expone la instancia raíz a todos los componentes de Vue en la aplicación.

Paso 6: crea una directiva personalizada

Finalmente, necesitamos una forma para que las aplicaciones que usan el complemento le digan qué elemento tendrá habilitada la funcionalidad de comentarios. Este es un caso para una directiva de Vue personalizada. Dado que los complementos tienen acceso a la Vue objeto, pueden definir nuevas directivas.

El nuestro será nombrado comments-enabled, y dice así:

// src/plugins/CommentsOverlay/index.js

export default {
  install(vue, opts){

    ...

    // Register custom directive tha enables commenting on any element
    vue.directive('comments-enabled', {
      bind(el, binding) {

        // Add this target entry in root instance's data
        root.$set(
          root.targets,
          binding.value,
          {
            id: binding.value,
            comments: [],
            getRect: () => el.getBoundingClientRect(),
          });

        el.addEventListener('click', (evt) => {
          root.$emit(`commentTargetClicked__${binding.value}`, {
            id: uuid(),
            commenter: options.commenterSelector(),
            clientX: evt.clientX,
            clientY: evt.clientY
          })
        })
      }
    })
  }
}

La directiva hace dos cosas:

  1. Agrega su destino a los datos de la instancia raíz. La clave definida para ello es binding.value. Permite a los consumidores especificar su propia ID para los elementos de destino, así: <img v-comments-enabled="imgFromDb.id" src="https://css-tricks.com/getting-started-with-vue-plugins/imgFromDb.src" />.
  2. Registra un click controlador de eventos en el elemento de destino que, a su vez, emite un evento en la instancia raíz para este destino en particular. Volveremos a cómo manejarlo más adelante.

El install ¡La función ahora está completa! Ahora podemos pasar a la funcionalidad de comentarios y los componentes para renderizar.

Paso 7: Establecer un componente de “Contenedor raíz de comentarios”

Vamos a crear un CommentsRootContainer y utilícelo como el componente raíz de la interfaz de usuario del complemento. Echemos un vistazo a esto:

<!-- 
 src/plugins/CommentsOverlay/CommentsRootContainer.vue -->

<template>
  <div>
    <comments-overlay
        v-for="target in targets"
        :target="target"
        :key="target.id">
    </comments-overlay>
  </div>
</template>

<script>
import CommentsOverlay from "./CommentsOverlay";

export default {
  components: { CommentsOverlay },
  computed: {
    targets() {
      return this.$root.targets;
    }
  }
};
</script>

¿Qué está haciendo esto? Básicamente, hemos creado un contenedor que contiene otro componente que aún no hemos creado: CommentsOverlay. Puede ver dónde se está importando ese componente en el script y los valores que se solicitan dentro de la plantilla contenedora (target y target.id). Note como el target La propiedad calculada se deriva de los datos del componente raíz.

Ahora, el componente de superposición es donde ocurre toda la magia. ¡Hagámoslo!

Paso 8: Haga magia con un componente de “Superposición de comentarios”

Bien, estoy a punto de lanzarte un montón de código, pero nos aseguraremos de revisarlo:

<!--  src/plugins/CommentsOverlay/CommentsRootContainer.vue -->

<template>
  <div class="comments-overlay">

    <div class="comments-overlay__container" v-for="comment in target.comments" :key="comment.id" :style="getCommentPostition(comment)">
      <button class="comments-overlay__indicator" v-if="editing != comment" @click="onIndicatorClick(comment)">
        {{ comment.commenter.initials }}
      </button>
      <div v-else class="comments-overlay__form">
        <p>{{ getCommentMetaString(comment) }}</p>
        <textarea ref="text" v-model="text" />        
        <button @click="edit" :disabled="!text">Save</button>
        <button @click="cancel">Cancel</button>
        <button @click="remove">Remove</button>
      </div>
    </div>

    <div class="comments-overlay__form" v-if="this.creating" :style="getCommentPostition(this.creating)">
      <textarea ref="text" v-model="text" />
      <button @click="create" :disabled="!text">Save</button>
      <button @click="cancel">Cancel</button>
    </div>

  </div>
</template>

<script>
export default {
  props: ['target'],

  data() {
    return {
      text: null,
      editing: null,
      creating: null
    };
  },

  methods: {
    onTargetClick(payload) {
      this._resetState();
      const rect = this.target.getRect();

      this.creating = {
        id: payload.id,
        targetId: this.target.id,
        commenter: payload.commenter,
        ratioX: (payload.clientX - rect.left) / rect.width,
        ratioY: (payload.clientY - rect.top) / rect.height
      };
    },
    onIndicatorClick(comment) {
      this._resetState();
      this.text = comment.text;
      this.editing = comment;
    },
    getCommentPostition(comment) {
      const rect = this.target.getRect();
      const x = comment.ratioX  <em> rect.width + rect.left;
      const y = comment.ratioY  <em> rect.height + rect.top;
      return { left: `${x}px`>, top: `${y}px` };
    },
    getCommentMetaString(comment) {
      return `${
        comment.commenter.fullName
      } - ${comment.timestamp.getMonth()}/${comment.timestamp.getDate()}/${comment.timestamp.getFullYear()}`;
    },
    edit() {
      this.editing.text = this.text;
      this.editing.timestamp = new Date();
      this._emit("edit", this.editing);
      this._resetState();
    },
    create() {
      this.creating.text = this.text;
      this.creating.timestamp = new Date();
      this._emit("create", this.creating);
      this._resetState();
    },
    cancel() {
      this._resetState();
    },
    remove() {
      this._emit("remove", this.editing);
      this._resetState();
    },
    _emit(evt, data) {
      this.$root.$emit(evt, data);
    },
    _resetState() {
      this.text = null;
      this.editing = null;
      this.creating = null;
    }
  },

  mounted() {
    this.$root.$on(`commentTargetClicked__${this.target.id}`, this.onTargetClick
    );
  },

  beforeDestroy() {
    this.$root.$off(`commentTargetClicked__${this.target.id}`, this.onTargetClick
    );
  }
};
</script>

Sé que sé. Un poco abrumador. Pero básicamente solo está haciendo algunas cosas clave.

En primer lugar, toda la primera parte contenida en el <template> etiqueta establece el marcado para un comentario emergente que se mostrará en la pantalla con un formulario para enviar un comentario. En otras palabras, este es el marcado HTML que muestra nuestros comentarios.

A continuación, escribimos los scripts que impulsan la forma en que se comportan nuestros comentarios. El componente recibe la totalidad target objeto como prop. Aquí es donde se almacena la matriz de comentarios y la información de posicionamiento.

Luego, la magia. Hemos definido varios métodos que hacen cosas importantes cuando se activan:

  • Escucha un clic
  • Genera un cuadro de comentarios y lo coloca donde se ejecutó el clic
  • Captura los datos enviados por el usuario, incluido el nombre del usuario y el comentario.
  • Proporciona posibilidades para crear, editar, eliminar y cancelar un comentario.

Por último, el controlador de commentTargetClicked Los eventos que vimos anteriormente se gestionan dentro del mounted y beforeDestroy manos.

Vale la pena señalar que la instancia raíz se utiliza como bus de eventos. Incluso si este enfoque a menudo se desaconseja, lo consideré razonable en este contexto, ya que los componentes no están expuestos públicamente y pueden verse como una unidad monolítica.

¡Aaaaaaand, estamos listos! Después de un poco de estilo (no ampliaré mis dudosas habilidades de CSS), ¡nuestro complemento está listo para recibir comentarios de los usuarios sobre los elementos de destino!

¡Hora de demostración!

Demo en vivo

Repositorio de GitHub

Familiarizarse con más complementos de Vue

Pasamos la mayor parte de esta publicación creando un complemento de Vue, pero quiero llevar este círculo completo a la razón por la que usamos complementos. He compilado una breve lista de complementos de Vue extremadamente populares para mostrar todas las cosas maravillosas a las que obtiene acceso cuando utiliza complementos.

  • Vue-enrutador – Si está creando aplicaciones de una sola página, sin duda necesitará Vue-router. Como enrutador oficial de Vue, se integra profundamente con su núcleo para realizar tareas como mapear componentes y anidar rutas.
  • Vuex – Sirviendo como una tienda centralizada para todos los componentes de una aplicación, Vuex es una obviedad si desea crear aplicaciones grandes con un alto mantenimiento.
  • Vee-validar – Al crear aplicaciones de línea de negocio típicas, la validación de formularios puede volverse inmanejable rápidamente si no se maneja con cuidado. Vee-validate se encarga de todo de una manera elegante. Utiliza directivas y está construido teniendo en cuenta la localización.

Me limitaré a estos complementos, pero sepa que hay muchos otros esperando para ayudar a los desarrolladores de Vue, ¡como usted!

Y, ¡oye! Si no puede encontrar un complemento que satisfaga sus necesidades exactas, ahora tiene experiencia práctica en la creación de un complemento personalizado. 😀