15-10-2019
- template -> view
- data -> model
- methods -> controller
-
created()
es un hook: cuando se crea el componente (cuando se monta el HTML esmounted()
). No son hooks como React. -
data
se pone como una función, para que, si tenemos varios componentes, no compartandata
como objeto.
15-10-2019
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.
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');
}
-
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 (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.
-
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.
-
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).
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"
/>
typeColor -> camel case (JS) type-color -> kebab case (HTML) type_color -> snake case
this.$emit('remove', this.pokemon)
Y en el HTML, en el <PokemonCard>
@remove="removePokemon"
22-10-2019
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.
No se puede mutar una prop. Un hijo no puede mutar una prop que recibe del padre.
this.$emit.
No hacen bubbling, solo se quedan en el padre.
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.
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.
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.
¿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
Con el Vue CLI puedo hacer los imports así:
import NiceInput from '@/components/NiceInput';
Donde el @ hace referencia a la carpeta src.
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.
<router-link :to="...">
o por JS..
router.push(...)
ó
this.$route.push(...)
En la definición de la ruta:
{ path:'/', component: MyProfile, name: 'profile'}
Y en el link:
<router-link :to="{name: 'profile'}"></router-link>
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>
¿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.
El router pone la clase router-link-active
al link que esté activo.
-
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.
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
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}},
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
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.
- 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
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',
}),
}
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.
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
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.
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.