Introducción a Vue.js: Vuex | Programar Plus

Esta es la cuarta parte de una serie de cinco sobre el marco de JavaScript, Vue.js. En esta parte, cubriremos Vuex para la gestión estatal. Esta no pretende ser una guía completa, sino más bien una descripción general de los conceptos básicos para que pueda comenzar a utilizar Vue.js y comprender lo que el marco tiene para ofrecer.

Serie de artículos:

  1. Representación, directivas y eventos
  2. Componentes, accesorios y ranuras
  3. Vue-cli
  4. Vuex (¡Estás aquí!)
  5. Animaciones

Vuex

Si se perdió las últimas secciones sobre componentes y Vue-cli, es posible que desee revisarlas antes de seguir leyendo. Ahora que conocemos los conceptos básicos sobre cómo los componentes y el estado de transmisión y los accesorios, hablemos de Vuex. Es una herramienta útil para la gestión estatal.

Anteriormente, pasamos el estado de un componente de nivel superior hacia abajo y los hermanos no compartían datos. Si necesitaran hablar entre ellos, tendríamos que impulsar el estado en la aplicación. ¡Esto funciona! Pero una vez que su aplicación alcanza una cierta complejidad, esto ya no tiene sentido. Si ha trabajado con Redux antes, todos estos conceptos y la implementación le resultarán familiares. Vuex es básicamente la versión de Redux de Vue. De hecho, Redux también funcionará con Vue, pero con Vuex, tiene la ventaja de usar una herramienta diseñada para trabajar específicamente con su marco.

Primero, instalaremos Vuex:

npm install vuex

o

yarn add vuex

Lo configuré de esta manera: dentro de mi directorio `/ src`, creo otro directorio llamado store (esta es una preferencia, también puede crear un archivo` store.js` en ese mismo directorio), y un archivo llamado ` store.js`. La configuración inicial en `store.js` se vería así (vstore sublime snippet):

import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

export const store = new Vuex.Store({
  state: {
    key: value
  }
});

key: value es un marcador de posición para cualquier tipo de datos estatales. En otros ejemplos hemos usado counter: 0.

En nuestro archivo `main.js`, realizaríamos las siguientes actualizaciones (líneas actualizadas resaltadas):

import Vue from 'vue';
import App from './App.vue';

import { store } from './store/store';

new Vue({
  el: '#app',
  store: store,
  template: '<App/>',
  components: { App }
});

Después de configurarlo, podemos colocar nuestro data() en el archivo como el estado que hemos hecho anteriormente con los componentes, y luego usaremos este estado o lo actualizaremos con los siguientes tres medios:

  • Getters hará que los valores se puedan mostrar estáticamente en nuestras plantillas. En otras palabras, los captadores pueden leer el valor, pero no mutar el estado.
  • Mutaciones nos permitirá actualizar el estado, pero siempre serán síncronos. Las mutaciones son la única forma de cambiar los datos en el estado de la tienda.
  • Comportamiento nos permitirá actualizar el estado, de forma asincrónica, pero utilizará una mutación existente. Esto puede ser muy útil si necesita realizar algunas mutaciones diferentes a la vez en un orden particular.

A veces es difícil entender por qué podría trabajar con cambios de estado asincrónicos si no lo ha hecho antes, así que primero repasemos cómo sucedería eso en abstracto y luego profundicemos en algo real en la siguiente sección. Digamos que eres Tumblr. Tienes un montón de gifs pesados ​​en una página que no termina por mucho tiempo. Solo desea cargar una cierta cantidad a la vez, digamos 20, hasta que el usuario obtenga 200 píxeles de distancia de la parte inferior de la página original.

Podría tener una mutación que muestre los siguientes 20. Pero todavía no tiene los siguientes 20, ni sabe cuándo llega al final de la página. Entonces, en la propia aplicación, creas un evento que escucha la posición de desplazamiento y desencadena una acción.

Luego, la acción recupera las URL de la base de datos para las siguientes 20 imágenes y envuelve la mutación, que agrega las 20 imágenes al estado y las muestra.

Las acciones, en esencia, crean un marco para solicitar datos. Le ofrecen una forma coherente de aplicar los datos de forma asincrónica.

Ejemplo abstracto más básico

En el siguiente ejemplo, mostramos la implementación más básica de cada uno, para que tenga una idea de la configuración y cómo funcionaría. La carga útil es un parámetro opcional. Puede definir la cantidad por la que está actualizando el componente. No se preocupe, usaremos una demostración real en un momento, es importante obtener los conceptos básicos primero.

En `store.js`:

export const store = new Vuex.Store({
  state: {
    counter: 0
  },
  //showing things, not mutating state
  getters: {
    tripleCounter: state => {
      return state.counter * 3;
    }
  },
  //mutating the state
  //mutations are always synchronous
  mutations: {
    //showing passed with payload, represented as num
    increment: (state, num) => {
      state.counter += num;
    }
  }, 
  //commits the mutation, it's asynchronous
  actions: {
    // showing passed with payload, represented as asynchNum (an object)
    asyncDecrement: ({ commit }, asyncNum) => {
      setTimeout(() => {
        //the asyncNum objects could also just be static amounts
        commit('decrement', asyncNum.by);
      }, asyncNum.duration);
    }
  }
});

Una característica realmente interesante aquí es que podemos devolver el objeto de estado completo en las mutaciones, pero no tenemos que hacerlo, solo podemos usar lo que necesitamos. La depuración de viajes en el tiempo (caminar a través de las mutaciones para encontrar errores) seguirá funcionando de cualquier manera.

En el componente en sí, usaríamos computed por captadores (esto tiene sentido porque el valor ya está calculado para nosotros), y methods con dispatch para acceder al mutaciones y acciones:

En `app.vue`:

computed: {
  value() {
    return this.$store.getters.value;
  }
},
methods: {
  increment() {
    this.$store.dispatch('increment', 2)
  }
}

O puede utilizar un operador de propagación. Encuentro esto útil cuando tienes que trabajar con muchas mutaciones / acciones:

export default {
  // ...
  methods: {
    ...mapActions([
      'increment', // map this.increment() to this.$store.commit('increment')
      'decrement',
      'asyncIncrement'
    ])
  }
}

Ejemplo real simple

Veamos nuevamente la aplicación Weather Notifier, con una cantidad de estado muy pequeña y simple en la tienda Vuex. Aquí está el repositorio.

Consulte el notificador meteorológico Pen Vue de Sarah Drasner (@sdras) en CodePen.

En `store.js`:

import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

export const store = new Vuex.Store({
  state: {
    showWeather: false,
    template: 0
  },
    mutations: {
      toggle: state => state.showWeather = !state.showWeather,
      updateTemplate: (state) => {
        state.showWeather = !state.showWeather;
        state.template = (state.template + 1) % 4;
      }
  }
});

Aquí, estamos estableciendo el estado de showWeather, esto se establece en falso al principio porque no queremos que ninguna de las animaciones se active de inmediato, no hasta que el usuario presione el botón del teléfono. En mutaciones, hemos configurado un interruptor para el estado de showWeather.

También estamos estableciendo el template a 0 en el estado. Usaremos este número para recorrer cada uno de los componentes meteorológicos uno por uno. Entonces, en mutaciones, hemos creado un método llamado updateTemplate. Esto cambia el estado de showWeathery actualiza el template al siguiente número, pero volverá a cero cuando llegue al número 4.

En App.vue:

<template>
  <div id="app">
    ...
    <g id="phonebutton" @click="updateTemplate" v-if="!showWeather">
       ...
    </g>

    <transition 
        @leave="leaveDroparea"
        :css="false">
      <g v-if="showWeather">
        <app-droparea v-if="template === 1"></app-droparea>
        <app-windarea v-else-if="template === 2"></app-windarea>
        <app-rainbowarea v-else-if="template === 3"></app-rainbowarea>
        <app-tornadoarea v-else></app-tornadoarea>
      </g>
    </transition>
    ...

  </div>
</template>
<script>
  import Dialog from './components/Dialog.vue';
  ...
  export default {
    computed: {
      showWeather() {
        return this.$store.state.showWeather;
      },
      template() {
        return this.$store.state.template;
      }
    },
    methods: {
      updateTemplate() {
        this.$store.commit('updateTemplate');
      }
    },
    ...
    components: {
      appDialog: Dialog,
      ...
    }
}
</script>

En `dialog.vue`:

<script>
export default {
  computed: {
    template() {
      return this.$store.state.template;
    }
  },
  methods: {
    toggle() {
      this.$store.commit('toggle');
    }
  },
  mounted () {
  	//enter weather
  	const tl = new TimelineMax();
    ...
  }
}
</script>

En el código anterior, la aplicación usa showWeather para hacer avanzar la plantilla, mientras que Dialog simplemente alterna la visibilidad del componente. También puede ver que en App.vue, mostramos y ocultamos diferentes componentes secundarios según el valor de la plantilla en la aplicación. <template> con esa elegante representación condicional que aprendimos en el primer artículo. En la aplicación, ambos estamos escuchando los cambios de estado en la tienda con el computed valores, y usando toggle() y updateTemplate() en los métodos para comprometerse con las mutaciones de la tienda.

Este es un ejemplo básico, pero puede ver cómo con una aplicación compleja con toneladas de estado, sería útil administrar el estado en un solo lugar, en lugar de moverlo hacia arriba y hacia abajo en nuestros componentes. Especialmente cuando los hermanos necesitan hablar con hermanos.

Si está interesado en profundizar en Vuex, aquí hay excelentes documentos. Es posible que haya notado que usamos algunos <transition> componentes en esta última demostración, así como muchas animaciones. ¡Hablemos de eso a continuación!

Serie de artículos:

  1. Representación, directivas y eventos
  2. Componentes, accesorios y ranuras
  3. Vue-cli
  4. Vuex (¡Estás aquí!)
  5. Animaciones