Vue.js 3, la programación orientada al futuro que no hay que perderse!

by Luigi Nori Date: 08-06-2020 vue hooks javascript react mixin

Si estás interesado en Vue.js, probablemente conozcas la 3ª versión de este framework, que se publicará en breve. La nueva versión de momento está en desarrollo, pero todas las características posibles se pueden encontrar en un repositorio RFC separado: https://github.com/vuejs/rfcs. Una de esas, function-api, puede cambiar dramáticamente el estilo de desarrollo de las aplicaciones Vue.

Este artículo está dirigido a personas que tienen al menos algún tipo de experiencia en JavaScript y Vue.

¿Qué tiene de malo la API actual de VUE 2?

La mejor manera es mostrarlo todo en un ejemplo. Por lo tanto, imaginemos que necesitamos implementar un componente que debería obtener los datos de algunos usuarios, mostrar el estado de carga y la barra superior dependiendo de la desviación de desplazamiento. Aquí está el resultado final:

Es una buena práctica extraer algo de lógica para reutilizarla en múltiples componentes. Con la API actual de Vue 2.x, hay una serie de patrones comunes, los más conocidos son:

  • Mixins (usando la opción mixins )
  • Componentes Higher-order (HOCs)

Por lo tanto, movamos la lógica de seguimiento de desplazamiento a un mixin, y busquemos la lógica en un componente de orden superior. La implementación típica con Vue se puede ver a continuación.

Scroll mixin:

 
const scrollMixin = {
data() {
return {
pageOffset: 0
}
},
mounted() {
window.addEventListener('scroll', this.update)
},
destroyed() {
window.removeEventListener('scroll', this.update)
},
methods: {
update() {
this.pageOffset = window.pageYOffset
}
}
}
 

Aquí añadimos la escucha de evento scroll (event listener), rastrear el desplazamiento de página y guardarlo en la propiedad pageOffset .

El componente de orden superior tendrá este aspecto::

 
import { fetchUserPosts } from '@/api'
const withPostsHOC = WrappedComponent => ({
props: WrappedComponent.props,
data() {
return {
postsIsLoading: false,
fetchedPosts: []
}
},
watch: {
id: {
handler: 'fetchPosts',
immediate: true
}
},
methods: {
async fetchPosts() {
this.postsIsLoading = true
this.fetchedPosts = await fetchUserPosts(this.id)
this.postsIsLoading = false
}
},
computed: {
postsCount() {
return this.fetchedPosts.length
}
},
render(h) {
return h(WrappedComponent, {
props: {
...this.$props,
isLoading: this.postsIsLoading,
posts: this.fetchedPosts,
count: this.postsCount
}
})
}
})
 

Aquí se inicializan las propiedades isLoading , posts para el estado de carga y posts data respectivamente. El método fetchPosts se invocará después de crear una instancia y cada vez que props.id cambia, para poder obtener datos para nuevas id .

No es una implementación completa de la COA, pero para este ejemplo, será suficiente. Aquí sólo tenemos que envolver el componente de destino y pasar las propriedades originales junto con las propriedades relacionadas con el fetch.

El componente de destino tiene este aspecto:

 
// ...
<script>
export default {
name: 'PostsPage',
mixins: [scrollMixin],
props: {
id: Number,
isLoading: Boolean,
posts: Array,
count: Number
}
}
</script>
// ...
 

Para obtener las propriedades especificadas debe ser envuelto en HOC creado:

 
_const_ PostsPage = **withPostsHOC(**PostsPage**)**
 

El componente completo con plantilla y estilos se puede encontrar en este enlace.

¡Genial! Acabamos de implementar nuestra tarea utilizando mixin y HOC, para que puedan ser utilizados por otros componentes. Pero no todo es tan prometedor, hay varios problemas con estos enfoques.

 

1. Choque de Namespace

 

Imagina que tenemos que añadir el método update a nuestro componente:

 
// ...
<script>
export default {
name: 'PostsPage',
mixins: [scrollMixin],
props: {
id: Number,
isLoading: Boolean,
posts: Array,
count: Number
},
methods: {
update() {
console.log('some update logic here')
}
}
}
</script>
// ...
 

Si vuelves a abrir la página y la desplaza, la barra superior ya no se mostrará. Esto se debe a la sobreescritura del método de mixin update . Lo mismo sucede con las COA. Si modificas el campo de datos fetchedPosts a posts :

 
const withPostsHOC = WrappedComponent => ({
props: WrappedComponent.props, // ['posts', ...]
data() {
return {
postsIsLoading: false,
posts: [] // fetchedPosts -> posts
}
},
// ...
 

…obtendrás errores como este:

La razón de esto es que el componente envuelto ya especificó la propiedad con el nombre posts .

 

2. Fuentes poco claras

 

¿Que pasa si después de un tiempo decidieras usar otro mixin en tu componente?

 
// ...
<script>
export default {
name: 'PostsPage',
mixins: [scrollMixin, mouseMixin],
// ...
 

¿Se puede saber exactamente de qué mixin se inyectó una propiedad pageOffset ? O en otro escenario, ambos mixins pueden tener, por ejemplo, la propiedad yOffset , por lo que el ultimo mixin anulará la propiedad de lo anterior. Eso no es bueno y puede causar muchos errores inesperados.

 

3. Rendimiento

 

Otro problema con las HOC es que necesitamos instancias de componentes separadas creadas sólo para fines de reutilización lógica que tienen un coste de rendimiento.

Vamos a hacer un “setup” de VUE 3

Veamos qué alternativa puede ofrecer la próxima versión de Vue.js y cómo podemos resolver el mismo problema usando la API basada en funciones.

Como Vue 3 aún no está disponible, se ha creado el plugin de ayuda — vue-function-api. Proporciona la función api de Vue3.x a Vue2.x para el desarrollo de aplicaciones Vue de nueva generación.

En primer lugar, es necesario instalarlo:

 
$ **npm** install vue-function-api
 

e instalar explícitamente a través de Vue.use() :

 
import Vue from 'vue'
import { plugin } from 'vue-function-api'
Vue.use(plugin)
 

La principal adición que proporciona la API basada en funciones es una nueva opción de componente - setup() . Como su nombre indica, este es el lugar donde utilizamos las funciones de la nueva API para configurar la lógica de nuestro componente. Por lo tanto, vamos a implementar una característica para mostrar la barra superior en función de la desviación de desplazamiento. Ejemplo de componente básico:

 
// ...
<script>
export default {
setup(props) {
const pageOffset = 0
return {
pageOffset
}
}
}
</script>
// ...
 

Ten en cuenta que la función de configuración setup recibe el objeto de puntales resueltos como su primer argumento y este objeto props es reactivo. También devolvemos un objeto que contiene la propiedad pageOffset para ser expuesto al contexto de render de la plantilla. Esta propiedad también se vuelve reactiva, pero sólo en el contexto del render. Podemos usarlo en la plantilla como de costumbre:

 
<div class="topbar" :class="{ open: pageOffset > 120 }">...</div>
 

Pero esta propiedad debería mutar en cada evento de desplazamiento. Para implementar esto, necesitamos agregar el listener de eventos de desplazamiento cuando el componente será montado y remover el listener - cuando sea desmontado. Para estos fines, existen funciones de API value , onMounted , onUnmounted :

 
// ...
<script>
import { value, onMounted, onUnmounted } from 'vue-function-api'
export default {
setup(props) {
const pageOffset = value(0)
const update = () => {
pageOffset.value = window.pageYOffset
}
onMounted(() => window.addEventListener('scroll', update))
onUnmounted(() => window.removeEventListener('scroll', update))
return {
pageOffset
}
}
}
</script>
// ...
 

Tenga en cuenta que todos los gancho(hooks)s de ciclo de vida en la versión 2.x de Vue tienen una función equivalente onXXX que puede ser usada dentro de setup() .

Probablemente también has notado que la variable pageOffset contiene una sola propiedad reactiva: .value . Necesitamos usar esta propiedad porque los valores primitivos en JavaScript como los números y las cadenas no se pasan por referencia. Las envolturas de valor proporcionan una forma de pasar referencias mutables y reactivas para tipos de valores arbitrarios.

Así es como se ve el objeto pageOffset :

El siguiente paso es implementar la obtención de datos del usuario. Al igual que cuando se utiliza la API basada en opciones, se pueden declarar valores calculados y observadores utilizando la API basada en funciones:

 
// ...
<script>
import {
value,
watch,
computed,
onMounted,
onUnmounted
} from 'vue-function-api'
import { fetchUserPosts } from '@/api'
export default {
setup(props) {
const pageOffset = value(0)
const isLoading = value(false)
const posts = value([])
const count = computed(() => posts.value.length)
const update = () => {
pageOffset.value = window.pageYOffset
}
onMounted(() => window.addEventListener('scroll', update))
onUnmounted(() => window.removeEventListener('scroll', update))
watch(
() => props.id,
async id => {
isLoading.value = true
posts.value = await fetchUserPosts(id)
isLoading.value = false
}
)
return {
isLoading,
pageOffset,
posts,
count
}
}
}
</script>
// ...
 

Un valor calculado se comporta como una propiedad calculada 2.x: rastrea sus dependencias y sólo reevalúa cuando éstas han cambiado. El primer argumento que se pasa a watch se llama "source", que puede ser uno de los siguientes:

  • una función getter
  • un valor wrapper
  • un array containing que contiene los dos tipos anteriores

El segundo argumento es una llamada de retorno que sólo será llamada cuando el valor devuelto por el getter o la envoltura de valor haya cambiado.

Acabamos de implementar el componente de destino utilizando una API basada en funciones.

El siguiente paso es hacer que toda esta lógica sea reutilizable..

Decomposición

Esta es la parte más interesante, para reutilizar código relacionado con un trozo de lógica sólo podemos extraerlo a lo que llamamos una “composition function” y devolverlo en estado reactivo:

 
// ...
<script>
import {
value,
watch,
computed,
onMounted,
onUnmounted
} from 'vue-function-api'
import { fetchUserPosts } from '@/api'
function useScroll() {
const pageOffset = value(0)
const update = () => {
pageOffset.value = window.pageYOffset
}
onMounted(() => window.addEventListener('scroll', update))
onUnmounted(() => window.removeEventListener('scroll', update))
return { pageOffset }
}
function useFetchPosts(props) {
const isLoading = value(false)
const posts = value([])
watch(
() => props.id,
async id => {
isLoading.value = true
posts.value = await fetchUserPosts(id)
isLoading.value = false
}
)
return { isLoading, posts }
}
export default {
props: {
id: Number
},
setup(props) {
const { isLoading, posts } = useFetchPosts(props)
const count = computed(() => posts.value.length)
return {
...useScroll(),
isLoading,
posts,
count
}
}
}
</script>
// ...
 

Observe cómo usamos useFetchPosts y useScroll para devolver las propiedades reactivas. Estas funciones pueden almacenarse en archivos separados y utilizarse en cualquier otro componente. En comparación con la solución basada en opciones:

  • Las propiedades expuestas a la plantilla tienen fuentes claras ya que son valores devueltos por las funciones de composición;
  • Devuelve los valores de las funciones de composición nombradas arbitrariamente para que no haya colisión de espacios de nombres;
  • No hay instancias de componentes innecesarias creadas sólo con fines de reutilización lógica.

Hay muchos otros beneficios que se pueden encontrar en la página oficial de RFC.

Todos los ejemplos de código utilizados en este artículo se pueden encontrar aquí.

Ejemplo en vivo del componente que puede ver aquí.

 

Conclusión

 

Como puedes ver, la API basada en funciones de Vue presenta una forma limpia y flexible de componer la lógica dentro y entre componentes sin ninguna de las desventajas de la API basada en opciones. Imagína lo poderosas que podrían ser las funciones de composición para cualquier tipo de proyecto, desde aplicaciones web pequeñas hasta grandes y complejas.

Espero que este post haya sido de utilidad.

 
by Luigi Nori Date: 08-06-2020 vue hooks javascript react mixin visitas : 13934  
 
Luigi Nori

Luigi Nori

He has been working on the Internet since 1994 (practically a mummy), specializing in Web technologies makes his customers happy by juggling large scale and high availability applications, php and js frameworks, web design, data exchange, security, e-commerce, database and server administration, ethical hacking. He happily lives with @salvietta150x40, in his (little) free time he tries to tame a little wild dwarf with a passion for stars.

 
 
 

Artículos relacionados

Crear PDF con Javascript y jsPDF

El formato PDF es muy útil para descargar datos de forma masiva en una aplicación web. Ayuda a los usuarios a descargar contenido dinámico en forma de archivo para que…

Como hacer tu propio cursor personalizado para tu web

Cuando empecé a ojear webs distintas y originales para aprender de ellas, de las primeras cosas que me llamaron la atención fue que algunas de ellas tenían sus propios cursores,…

Explorando la API de CSS Paint: Redondeo de formas parte 1

Añadir bordes a las formas complejas es un auténtico rollo (a veces), pero redondear las esquinas de las formas complejas es un suplicio jejeje. Por suerte, la API de pintura…

Cómo enviar un correo electrónico desde un formulario de contacto HTML

En el artículo de hoy vamos a escribir sobre cómo hacer un formulario que funcione y que al pulsar ese botón de envío sea funcional y envíe el correo electrónico…

Cómo hacer un sitio web multilingüe sin redireccionamiento

Hoy, vamos a hablar de cómo implementar un simple selector de idioma en el sitio web estático o básico, sin necesidad de ningún backend o llamadas a la base de…

Comenzando con Bootstrap-Vue paso a paso

Hoy te mostraremos cómo usar BootstrapVue, describiremos el proceso de instalación y mostraremos la funcionalidad básica. El proyecto está basado en el framework CSS más popular del mundo - Bootstrap, para…

Por qué los desarrolladores de JavaScript deberían preferir Axios a Fetch

Por qué los desarrolladores de JavaScript deberían preferir Axios a Fetch En mi artículo anterior, "Usando la Api Fetch Para Hacer Llamadas Ajax", hablé de los fundamentos de la API Fetch.…

Creación de un sencillo spinner-loader CSS

En el artículo de hoy mostraremos cómo animar un loader básico que gira cuando se define alguna acción predefinida, como cargar una imagen. Eso se puede utilizar en un sitio…

Los mejores selectores de fechas para Bootstrap y tu aplicación

Los selectores de fecha son widgets que permiten a los usuarios elegir una sola fecha o rango de fechas y horas. Es un elemento habitual para todo usuario de Internet,…

Validación de formularios HTML usando BULMA y vanilla JavaScript

Hoy vamos a escribir sobre los formularios de contacto y cómo validarlos usando JavaScript. El formulario de contacto parece ser una de las características principales de toda página web básica. Es…

Cómo usar el efecto Parallax.Js en tu sitio web

Hoy vamos a escribir sobre el efecto de parallax, similar al desplazamiento de parallax, y cómo implementarlo para mejorar su página de aterrizaje. En webdev, dicen que primero el móvil…

Usando la API FETCH para hacer llamadas AJAX - Una promesa cumplida

En este artículo hablamos sobre lo que son las llamadas AJAX y cómo utilizarlas de forma tradicional, utilizando el objeto XMLHttpRequest (XHR). En resumen, gracias a las llamadas AJAX una…

Clicky