Skip to content

Instantly share code, notes, and snippets.

@gonzalogmn
Last active November 7, 2019 20:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gonzalogmn/5fd5eb0d2bc60a24bc07ea7f42c1c6c2 to your computer and use it in GitHub Desktop.
Save gonzalogmn/5fd5eb0d2bc60a24bc07ea7f42c1c6c2 to your computer and use it in GitHub Desktop.
curso vue fictizia

15-10-2019

MVVM

  • template -> view
  • data -> model
  • methods -> controller

Objeto Vue

  • created() es un hook: cuando se crea el componente (cuando se monta el HTML es mounted()). No son hooks como React.

  • data se pone como una función, para que, si tenemos varios componentes, no compartan data como objeto.


15-10-2019

Watchers & Filters

Watchers: Hacer algo nosotros cada vez que algo cambie. Para cosas colaterales.

Por ejemplo, observar searchText y hacer una llamada al servidor. Hacer cosas colaterales: paginar en el servidor, llamadas a API, guardar en el localstorage...

watch: {
    searchtext() {
        ...
    }
}

Mientras que computed es para cálculo de valores en base a otros.

El watcher no se ejecuta al principio, sino cuando después cambia.

Si queremos que se ejecute al principio:

watch: {
    searchText: {
        inmmediate: true
        handler() {
            ...
        }
    }
}

Si queremos watchear más de una propiedad, nos creamos una propiedad computada que depende de todas las propiedades que quiera observar, y watcheamos esta propiedad computada.

Reactividad de Vue

Vue no soporta la edición de nuevas propiedades en un objeto del data

Si tenemos este método en methods:

addIceType() {
     // ice no existía como propiedad
     this.typeColor.ice = '#0000FF';
}

Hay que hacerlo así:

addIceType() {
     // ice no existía como propiedad
     this.$set(this.typeColor, 'ice', '#0000FF');
     
     // si quisieras borrar una propiedad
     this.$delete(this.typeColor, 'ice');
}

Lifecycle

  • beforeCreate: antes de la reactividad de Vue.

  • created: se ha inicializado la reactividad de Vue. No tenemos acceso al HTML.

  • mounted: tenemos acceso al HTML.


17-10-2019

Modificadores

Modificadores (se aplican sobre v-model, v-on, v-bind...)

  • @submit.prevent

El prevent es un modificador

  • v-model.lazy

  • v-model.number: hará una conversión del value a un número

  • v-model.trim: elimina espacios

  • @click.stop:

  • @click.once: solo captura el evento una vez

Se pueden poner varios modificadores juntos.

Otras directivas

  • v-text: para setear el texto en una tag. Si no ponemos un interpolador, podemos usar v-text.

  • v-html: para que interprete el texto que le pasamos como HTML.

  • v-show: como v-if pero este sí renderiza. v-if no renderiza.

  • v-pre: para que no interprete los {{}}

  • v-once: para recogerlo una vez, para cualquier evento.

Componentes

  • vue-loader: plugin de webpack que nos permitirá escribir componentes de vue. Serán ficheros .vue

  • vue-cli: línea de comandos que nos permite montar proyectos de Vue (tirará de webpack por detrás).

Ejemplo de componetización directamente con JS

Para que el navegador entienda que hay html dentro del <script>, hay que poner <script type="text/x-template">

Y le añadimos un id <script type="text/x-template" id="pokemon-cart-template">

const PokemonCard = {
    template: '#pokemon-card-template',
    props: ['key','pokemon'],
}

Y en el padre, le añadimos la propiedad

const app = new Vue({
    ...
    components: [PokemonCard]
});

Y en el html del padre:

<pokemon-card
    v-for=""
    :key="pokemon.name"
    :pokemon="pokemon"
/>

Nota

typeColor -> camel case (JS) type-color -> kebab case (HTML) type_color -> snake case

Eventos

this.$emit('remove', this.pokemon)

Y en el HTML, en el <PokemonCard>

@remove="removePokemon"

22-10-2019

Vue CLI

vue serve
vue create project
vue ui

Un plugin de vue cli es como hacer un npm install pero con más cosas.

p.ej: vue add vuetify

Props como Array, Object (type, default, required, validator) Props como eventos, callbacks. Pasar una función como prop, que resulta útil por ejemplo si queremos hacer algún tipo de validación.

Herencia de atributos.

Props

No se puede mutar una prop. Un hijo no puede mutar una prop que recibe del padre.

Eventos

this.$emit.

No hacen bubbling, solo se quedan en el padre.

v-model

v-model = :value & @input

El padre: v-model="searchText" Y en el hijo

// JS
props: ['value']

// HTML
:value="value"
@input="emitSearchText"

// JS
emitSearchText() { this.$emit('input', ...)}

Así en el padre puedo poner el v-model directamente.

Modificador .sync

Si el anterior v-model ya está ocupado, pero nos gustaría tenerlo:

Lo puedo pasar como atributo

<nice-input
:search-Text="searchText"
@updateSearchText="newVal => searchText = newVal"
/>

Y en el hijo:


// HTML
<input
    :value="seatchText"
    @input="updateText"
/>

// JS

updateText(event) {
    this.$emit('updateSearchText', event.target.value)
}

Si queremos hacer doble data-binding de más de una prop, no podemos hacerlo con v-model, pero podemos hacer con un .sync.

this.$emit('update:searchText', ...)

Y en el padre:

<nice-input
:seatch-text="searchText"
@update:searchText="newVal => searchText = newVal"
/>

Y esto es igual que hacer:

<nice-input
:search-text.sync="searchText"
/>

Por lo que v-model y .sync son lo mismo en el fondo.


24-10-2019

¿Por qué utilizar componentes? Sobre todo por la encapsulación.

Slots

Es HTML que renderiza el padre, y que se lo pasa al hijo.

<slot>Texto alternativo en caso de que no se pase ningún html...</slot>

Se pueden pasar varios slots, usando el atributo name:

<slot name="footer"></slot>

En este caso, el HTML que le pasemos, tiene que ir dentro de un template:

<template v-slot:footer>
...html...
</template>

O también:

<template #footer>
...html...
</template>

Si pones por ejemplo un @input en un slot, ya no hace falta un emit desde el hijo ni nada, porque está ya en el padre. Puede llamar directamente a un método del padre sin necesidad emitir un evento desde el hijo.

Pero... ojo que viene un problema.

Tengo en el padre esto

<my-pokemons :pokemons="pokemons">

</my-pokemons>

Y en el hijo:

Pinta una tabla con
 <tr v-for="pokemon in pokemons" :key="pokemon.id">
    <td>{{ pokemon.name }}</td>
 </tr>

¿Como puedo arreglar esto con un slot? Porque desde el padre no puedo acceder a cada fila del hijo.

Tengo que utilizar un scoped-slot.

En el padre:

<my-pokemons :pokemons="pokemons">
    <template #row="slotProps">
        <td>{{ slotProps.pokemon.name }}</td>
    </template>
</my-pokemons>

(o haciendo destructuring)

<my-pokemons :pokemons="pokemons">
    <template #row="{pokemon}">
        <td>{{ pokemon.name }}</td>
    </template>
</my-pokemons>

Y en el hijo:

<tr v-for="pokemon in pokemons" :key="pokemon.id">
    <slot name="row" :pokemon="pokemon">
</tr>

De esta forma el hijo le puede pasar al padre la propiedad para que la trate el padre.

Al final, la relación de un componente con otro es mediante props, eventos y slots.

NOTA: los estilos de lo que vaya en el slot tiene que ir en el padre.

Vue Router

¿Por qué utilizar un router? Para ir de una vista a otra. Además de cambiar lo que se ve, te actualiza la URL. Une lo que estás viendo con la ruta en la que estás. También nos va a permitir avanzar o retroceder, o que al refrescar te quedes en la vista en la que estabas.

<router-link to="/foo">Go to Foo</router-link>

...

<router-view></router-view>  // Aquí renderiza

El router asocia una ruta a un componente.

const routes = [
    {path: '/foo', component: Foo}
];

Vue.use(VueRouter);
...

const router = new VueRouter({
    routes
});

...

new Vue({
    router
}).$mount('#app');
    ...
})

29-10-2019

Vue Router (II)

Con el Vue CLI puedo hacer los imports así:

import NiceInput from '@/components/NiceInput';

Donde el @ hace referencia a la carpeta src.

Modo history

http://localhost:8080/#/my-profile

¿Por qué usamos # en la URL?

El servidor no recibe nada de lo que hay desde el ancla a la derecha. Y el Router de Vue utiliza la parte derecha de la URL.

Si activamos el modo history:

const router = new VueRouter({
    mode: 'history',
    routes
});

Entonces ya no va a usar el ancla (#).

Si ponemos el modo history, hay que añadir una ruta como esta:

{path: '*', component: NotFoundComponent}

El servidor nunca nos va a devolver un NOT FOUND, sino que siempre devuelve el index.html y luego Vue se encarga de redirigir.

En el modo history hay que hacer configuración en el servidor, para que no devuelva un 404 y siempre devuelva el index.html.

Declaración

Declarativa o programática

<router-link :to="...">

o por JS..

router.push(...)

ó

this.$route.push(...)

Nombrado de rutas

En la definición de la ruta:

{ path:'/', component: MyProfile, name: 'profile'}

Y en el link:

<router-link :to="{name: 'profile'}"></router-link>

Paso de parámetros

Para pasar un parámetro por URL, lo definimos con ':':

{ path:'/pokemon:name', component: PokemonDetail, name: 'pokemon'}

Lo capturamos con:

$route.params.pokemonName

Pero, para no depender de que se tenga un router, mejor lo pasamos como prop. Para eso, hacemos:

{ path:'/pokemon:name', props: true, component: PokemonDetail, name: 'pokemon'}

Esto hará que el componente reciba el parámetro como una prop.

  • Si hacemos referencia a la ruta por el name, podemos pasarle parámetro así:
<router-link :to="{ name: 'pokemon-detail', params: {name: pokemon.name}}"></router-link>

Rutas anidadas

¿Y si tengo subnavegación? Rutas dentro de otras rutas.

/user/foo/profile ----> /user/foo/posts

Las rutas anidadas las ponemos en otro array llamado children, en la definición de las rutas.

CSS

El router pone la clase router-link-active al link que esté activo.

Reacción ante parámetros

  • Watch $route: es un data que tienen todos los componentes. Si hacemos un watch de esta property, podemos actuar ante cambios en la ruta.

  • beforeRouteUpdate: es un hook, sobre el que podemos tener un callback que se ejecute cada vez que se actualice la ruta.

Navigation guards

Funciones que se ejecutan cuando pasa algo en la ruta.

  • "Global before guards"

    • router.beforeEach((to, from, next) => ...)
    router.beforeEach((to, from, next) => {
        next();
    });
    
    • next(), next(false), next({name: 'not-found'}), next('route'). La última es una redirect a esa ruta. Este método solo está disponible dentro del método anterior.

Esto es útil por ejemplo para manejar un login. Si no estás autenticado, te mando al login.

  • "Per-Route Guard"

    • beforeEnter: (to, from, next) => ...
  • "In-Component Guards"

    • beforeRouteEnter
    • beforeRouteUpdate
    • beforeRouteLeave

Propiedad 'meta'

En la definición de una ruta se puede añadir una propiedad meta. En el siguiente caso, en meta hemos metido una property llamada public. Podemos meter lo que queramos.

{ path:'', component: MyComponent, meta: { public: true}},

Lazy loading routes

Si queremos cargar una ruta de forma lazy:

{
    path: '/about',
    name: 'about',
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
}

Webpack nos creará un chunk como este: about.[hash].js.


31-10-2019

VueX

MVC - Modelo, Vista, Controlador (data-props, template, methods)

Event Handler -> Modelo (data, props) -> Change DOM

En el Modelo vive un Estado.

Con el modelo de componentes, cada componente es un MVC. Es decir, cada componente tiene su modelo. Esto está bien porque es acorde al DOM, encapsula responsabilidad, permite reutilizar, etc.

En la comunicación padre-hijo, se hace mediante props y eventos.

¿Y entre hermanos? Podría ser hijo-padre-hijo.

¿Y entre abuelo nieto?

...

Una solución que se ofrece es el bus de eventos, pero cuando tienes muchas comunicaciones, esto se convierte una fiesta de eventos.

El problema es que el estado se encuentra distribuido. Y nuestro árbol de estados no tiene por qué ser igual al DOM.

  • En VueX, existe un único estado global, y este estdo es de solo lectura. Para cambiar el estado, se hace mediante mutaciones o mutations. Los componentes podrán hacer 'commits' de una mutación.
STATE --- reactivity --> COMPONENTS
  ^
  |
  |
mutate
  |
  |
MUTATIONS <-- commit -- COMPONENTS
  • Las mutaciones son síncronas, para poder tener un histórico ordenado de cambios. Pero nuestras aplicaciones pueden ser asíncronas. ¿Cómo resolvemos esto?. Con Acciones
STATE ----------------- reactivity ------------> COMPONENTS
  ^
  |
  |
mutate
  |
  |
MUTATIONS <-- commit --  [ACTIONS] <-- dispatch -- COMPONENTS
  • Las acciones se pueden componer. En las Actions podemos tener la lógica de negocio. Y en las Mutations, la lógica de datos.

  • Además tenemos Getters, para leer. Son como variables computadas en un Store. Son estados derivados. Los Getters están cacheados.

    STATE --- reactivity --> [GETTERS] -- reactivity ---> COMPONENTS
    ^
    |
    |
    mutate
    |
    |
    MUTATIONS <-- commit --  [ACTIONS] <-- dispatch ----- COMPONENTS
(Lógica de datos)        (Lógica de negocio)
  • Todo esto es la store de VueX.

    • Un único etado global de solo lectura
    • El estado solo cambia por mutaciones síncronas
    • Las acciones generan mutaciones y pueden ser asíncronas.
  • Las stores son modulables de forma fractal. Podemos dividir la store en diferentes ficheros.

  • Permite HMR (Hot Module Replacement) con Webpack.

  • Permite Time-traveling con Vue devtools.

Buenas prácticas

  • El estado debe modelarse pensando sólo en los datos y evitando la redundancia, los getters pueden servirlo de forma más cómoda.
  • Puede seguir existiendo estado local y comunicación padre-hijo, cosas puramente UI suelen ser locales (en la store solo guardar el estado global).
  • Los componentes solo deberían "dispatchear" acciones, no "commitear" mutaciones.
  • Las mutaciones deben centrarse solo en manipular los datos, las acciones pueden llevar la lógica de negocio.
  • Leerse los change detection caveats de Vue. Que si nosotros a un objeto queremos añadirle nuevos atributos, tenemos que decírselo de una forma especial.

05-11-2019

VueX (II)

Lectura del store

Para consultar del store puedo hacer:

computed: {
    pokemonsList() {
        return this.$store.state.pokemons;
    }
}

O de forma más elegante con mapState:

import { mapState } from 'vuex'; 

...

computed: {
    ...mapState({
        pokemonsList: 'pokemons',
    }),
}

Actions

Y las acciones, lo mismo:

      this.$store.dispatch('attackPokemon',{src: pokemonSrc, target: pokemonTarget});

O de forma más elegante:

import { mapActions } from 'vuex'; 

...


methods: {
    ...mapActions(['attackPokemon']),
    (otros métodos),
}

Y luego llamo al action como si llamara a un método normal:

this.attackPokemon({src: pokemonSrc, target: pokemonTarget});

Y luego definiría la action, que recibe los siguientes parámetros:

const actions = {
    attackPokemon({state, commit, dispatch},{src, target}) {
        // el segundo parámetro es lo que yo defina como payload

        const name = target.name;
        const damage = src.attack;
        commit('ATTACK_POKEMON',{name, damage});

    }
};

En un mutation, el nombre se suele escribir en mayúsculas, por convención.

Mutations

Definimos la mutación:

const mutations = {
    ATTACK_POKEMON(state, {name, damage}) {
        
    }
}

Y dentro modificamos el state:

const mutations = {
    ATTACK_POKEMON(state, {name, damage}) {
        const pokemon = state.pokemons.find(poke => poke.name === name);
        pokemon.life = pokemon.life - damage;
    }
}

07-11-2019

Transitions

Las transiciones las metemos en una etiqueta <transition>...</transition>.

Nos hace transición cuando metemos o sacamos un elemento (v-if, routing, o si filtramos en una lista...).

Cuando va a entrar, aplica en la etiqueta estas clases:

  • v-enter
  • v-enter-active
  • v-enter-to

Y al salir:

  • v-leave
  • v-leave-active
  • v-leave-to

Ejemplo:

<transition name="fade">
    <button v-if="user" @click="logout">Logout</button>
</transition>

Y luego los estilos:

.fade-enter-active, .fade-leave-active {
  transition: opacity .5s
}

.fade-enter, .fade-leave-to {
  opacity: 0
}

Si no definimos un 'name', las clases son v-enter, v-enter-active, etc.

Transition Group

Para las listas se utiliza un Transition Group.

Aquí le podemos definir un name, y un tag. El tag indica con qué tag de html va a quedar el <transition> una vez termine.

Por ejemplo:

<transition-group name="fade" tag="ul">
    <li v-for="message in messages" :key="message.id">
        {{ message.text }}
        <button @click="remove(message)">remove</button>
    </li>
</transition-group>

v-move: para aplicar cuando se mueva, no cuando aparezca o desaparezca.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment