Vue.js

núria soriano

Facts & Opinions

FACTS

¿Que es Vue?

Framework progresivo

No opinionado

Rápido

Vue Single File Component

Plantilla, lógica y estilo

Usando HTML + Javascript + CSS

Se importa como un módulo ES2015

Hot-reload, scoped CSS…


    <template>
      <my-component></my-component>
    </template>
    
    <script>
      import 'myComponent' from './MyComponent'

      export default {
        name: 'app',
        components: {
          myComponent
        }
      }
    </script>

    <style lang="scss" scoped>
      div {
        color: blue
      }
    </style>

  <template>
    <div>    
        <div>
          {{ item.name }}
        </div>
        
        <img :src="url"/>
        
        <button @click="someMethod"></button>
        
        <div v-for="item in items">{{ item.name }}</div>
        
        <div v-if="someCondition">
          print this
        </div>
        <div v-else>
          print that
        </div>
        
        <div v-show="someOtherCondition">
          display this
        </div>
    </div>
  </template>

Template

Estado


    data() {
      return {
        collection: [1, 2, 3, 4]
      }
    }

Métodos


    methods: {
      addNumbers() {
        var moreNumbers = [5,6,7,8]
        this.collection.push(...moreNumbers)
      }
    }

Propiedades computadas

Reactivas


    computed: {
      evenNumbers() {
        return this.collection.filter(number => number %2 === 0)
      }
    }

Solo se recalculan cuando es necesario

Lifecycle hooks

Ejecutar lógica en determinados momentos


    created() {
      http.get('http://jsonplaceholder.typicode.com/posts')
        .then(response => {
          this.posts = response.data
        })
        .catch(e => {
          this.errors.push(e)
        })
    }

Comunicación entre componentes

Child


    export default {
      props: [ 'propName' ],
      // ...
    }

Parent

    
    <child-component :propName="someData">
    </child-component>

    export default {
      props: {
        propName: {
          type: Boolean,
          default: true
        },
        otherProp: {
          type: String,
          required: true
        }
      },
      // ...
    }

Comunicación entre componentes

Child

    
    this.$emit('eventName', data)

Parent

    
    <child-component @eventName="method">
    </child-component>

Router

    
    import VueRouter from 'vue-router';
    import User from 'src/components/User';

    const router = new VueRouter({
      routes: [
        {
          path: '/user/:userId',
          name: 'user',
          component: User
        }
      ]
    })
    
    <div id="app">
      <router-view></router-view>
    </div>
    
    <router-link to="/foo">Foo</router-link>

    <router-link :to="{
      name: 'foo',
      params: {
        id: bar
      }
    }">
      Foo
    </router-link>

Vuex

Varios componentes son dependientes del mismo estado

Sincronizar estado sin necesidad de eventos

Solución escalable para aplicaciones complejas

Fácil de debuggar

El estado

    
    const store = new Vuex.Store({
      state: {
        movies: [
          { id: 1, title: '...', saved: true },
          { id: 2, title: '...', saved: false }
        ]
      }
    })

    const store = new Vuex.Store({
      state: {
        movies: [
          { id: 1, title: '...', saved: true },
          { id: 2, title: '...', saved: false }
        ]
      },
      getters: {
        savedMovies: state => {
          return state.movies.filter(movie => movie.saved)
        }
      }
    })

Objeto

No mutable directamente

Usar el estado en nuestros componentes


    computed: {
      movies () {
        return this.$store.state.movies
      },
      savedMovies () {
        return this.$store.getters.savedMovies
      }
    }

    import { mapState, mapGetters } from 'vuex'
    
    export default {
      computed: {
        ...mapState({
          'movies'
        }),
        ...mapGetters([
          'savedMovies'
        ])
      }
    }

Definir mutaciones


    const store = new Vuex.Store({
      state: {
        movies: []
      },
      mutations: {
        addMovie (state, movie) {
          state.movies.push(movie)
        }
      }
    })

Mutaciones asíncronas: Acciones


    const store = new Vuex.Store({
      state: { ... },
      mutations: { ... },
      actions: {
        getMovies ({ commit }) {
         return api.getMovies()
            .then( data => {
              return data.forEach(movie => commit('addMovie', movie))
            })          
        }
      }
    })

Usar mutaciones y acciones en componentes


    methods: {
      addMovie (movie) {
        return this.$store.commit('addMovie')
      },
      getMovies () {
        return this.$store.dispatch('getMovies')
      }
    }

    import { mapMutations, mapActions } from 'vuex'
    
    export default {
      methods: {
        ...mapMutations({
          'addMovie'
        }),
        ...mapActions([
          'getMovies'
        ])
      }
    }

OPINIONS

¿Por qué Vue?

Curva de aprendizaje

HTML, Javascript, CSS

Templates vs render functions vs JSX

Flexible

O Pug, Typescript, SASS…

Dev Friendly

Devtools

Dev Friendly

CLI

Dev Friendly

Configurar webpack totalmente sin necesidad de eject

Dev Friendly

    
    // vue.config.js
    module.exports = {
      configureWebpack: {
        plugins: [
          new MyAwesomeWebpackPlugin()
        ]
      }
    }

¿Cuando usar Vue?

TL;DR:

Equipo con experiencia diversa

Prototipos, MVP…

Desarrollo rápido y flexible

Tips & Tricks

Convention over configuration

Seguir la style guide de Vue

Prettier, ESLint vue/recommended

Nuxt

Best practices

Evitar lógica en el template

    
    <template>
      <div>
        {{ numbers.sort() }}
      </div>
    </template>
    
    <template>
      <div>
        {{ sortedNumbers }}
      </div>
    </template>

Best practices

Evitar side effects en propiedades computadas

    
    <template>
      <div>
        {{ sortedNumbers }}
      </div>
    </template>

    <script>
    export default {
      data() {
        return {
          numbers: [4, 5, 2, 7, 1]
        };
      },
      computed: {
        sortedNumbers() {
          return this.numbers.sort()
        }
      }
    };
    </script>
    
    <template>
      <div>
        {{ sortedNumbers }}
      </div>
    </template>

    <script>
    export default {
      data() {
        return {
          numbers: [4, 5, 2, 7, 1]
        };
      },
      computed: {
        sortedNumbers() {
          return [...this.numbers].sort()
        }
      }
    };
    </script>

Best practices

Separar lógica de negocio de componentes


    export default {
      data() {
        return {
          movies: null
        };
      },
      created: {
        axios.get('https://api.example.com/movies')
          .then(function (response) {
            this.movies = response
          })
      }
    }

    export default {
      data() {
        return {
          movies: null
        };
      },
      created: {
        someService.getMovies()
          .then(movies => {
            this.movies = movies
          })
      }
    }

    export default {
      ...mapGetters([
        'movies'
      ]),
      ...mapActions([
        'getMovies'
      ]),
      created: {
        this.getMovies()
      }
    }

Unit Testing

Unit Testing

Snapshots para los componentes presentacionales

Testear servicios y store es más fácil

No sobretestear: Component contract

Ready?

Empieza con un proyecto pequeño

Prefiere soluciones framework-agnostic

Documentación

"El progreso se debe a personas vagas en busca de formas más fáciles de hacer las cosas"

Robert A. Heinlein

¡Gracias!

nuria-fl

soriano.nuria@gmail.com

pincfloit

Made with Slides.com