Введение в Vue.js: Vuex

03.06.2017

Это четвертая часть в серии из пяти статей о JavaScript фреймворке Vue.js. В этой части мы расскажем о Vuex библиотеке для управления состоянием Vue приложений. Это не является полным руководством, а скорее обзором основ, которые помогут вам начать работу, чтобы вы могли узнать Vue.js и понять, что может предложить вам этот фреймворк.

 

Серия статей

  1. Рендеринг, директивы и события
  2. Components, Props и Slots
  3. Vue-cli
  4. Vuex (Вы здесь)
  5. Анимации

Vuex

Если вы пропустили последние несколько разделов о компонентах и ​​Vue-cli, возможно, вам захочется пересмотреть их перед тем, как продолжить чтение. Теперь, когда мы знакомы с базовыми принципами работы компонентов и передачи состояния и props, давайте поговорим о Vuex. Полезном инструменте для управления состоянием вашего приложения.

Раньше мы передавали структуру от компонента верхнего уровня вниз и наследователи не делились данными. Но если бы им нужно было передавать данные друг другу, то нам пришлось бы менять состояние. Да это работает. Но как только ваше приложение достигает определенной сложности, больше не имеет смысла так делать. Если вы раньше работали с Redux, все эти концепции и реализация будут вам знакомы. Vuex — это в основном Vue версия Redux. На самом деле, Redux также будет работать с Vue, но с Vuex у вас есть преимущество использования инструмента, предназначенного специально для работы с вашим фреймворком.

Давайте установим Vuex:

Npm install vuex

или

yarn add vuex

Я установила его так: в каталоге `/ src` я создаю другой каталог store (мне нравится так, но вы можете просто создать файл store.js в этом же каталоге) и в нём файл с именем  store.js. Первоначальная настройка в `store.js` будет выглядеть примерно так:

1
2
3
4
5
6
7
8
9
10
import Vue from 'vue';
import Vuex from 'vuex';
 
Vue.use(Vuex);
 
export const store = new Vuex.Store({
  state: {
    key: value
  }
});

key: value это заполнитель для любых состояний. В других примерах мы использовали counter: 0.

В файле `main.js` мы выполним следующие изменения (измененные строки выделены):

1
2
3
4
5
6
7
8
9
10
11
import Vue from 'vue';
import App from './App.vue';
 import { store } from './store/store';
 
new Vue({
  el: '#app',  store: store,
  template: '<App/>',
  components: { App }
});

После того, как мы всё настроили мы можем поместить data() в файл как состояние, как мы ранее делали с компонентами, а затем мы либо используем это состояние, либо обновляем его следующими тремя способами:

  • Getters получают значения, способные статически отображаться в наших шаблонах. Другими словами, они могут считывать значение, но не изменять состояние.
  • Mutations позволят нам обновлять состояние, но они всегда будут синхронными. Мутации — единственный способ изменения данных состояния в памяти.
  • Actions позволят нам обновлять состояние асинхронно, но будут использовать существующую мутацию. Это может быть очень полезно, если вам нужно выполнить несколько разных мутаций одновременно в определенном порядке.

Иногда бывает трудно понять, почему у вас есть возможность работать с асинхронными изменениями состояния, хотя по идее не должно быть, так что давайте сначала рассмотрим, как это будет происходить в абстрактном примере, а затем погрузимся в нечто реальное в следующей главе.

Допустим, вы зашли на Tumblr. У вас есть тонна тяжелых гифок на странице, которая не заканчивается в течение длительного времени. И вы хотите загрузить за раз еще 20 гифок, пока пользователь не получит 200 пикселей от нижней части исходной страницы.

У вас может быть мутация, которая отобразит следующие 20. Но у вас еще нет этих 20, и вы не знаете, когда попадете в нижнюю часть страницы. Поэтому вы создаете событие, которое проверяет позицию прокрутки, а затем вы запускаете действие.

Далее действие извлекает URL-адреса из базы данных для следующих 20 изображений и переносит мутацию, которая добавляет и отображает их.

Эти действия, по сути, создают основу для запроса данных. Они дают вам последовательный способ применения данных в асинхронном режиме.

Базовый абстрактный пример

В приведенном ниже примере мы показываем базовую реализацию каждого из способов, поэтому вы получите представление о том как это будет работать. Полезная нагрузка — необязательный параметр. Вы можете установить то количество, которое вы обновляете в компоненте. Не волнуйтесь, мы будем использовать реальную демонстрацию через минуту, просто нужно сначала ознакомиться с базовыми концепциями.

В файле store.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
export const store = new Vuex.Store({
  state: {
    counter: 0
  },
  //показывает значения, не состояние мутации
  getters: {
    tripleCounter: state => {
      return state.counter * 3;
    }
  },
  //Мутирует состояние
  //Мутации всегда синхронны
  mutations: {
    //Показатели пройдены с полезной нагрузкой, представленной как num
    increment: (state, num) => {
      state.counter += num;
    }
  }, 
  //Фиксирует мутацию, это асинхронно
  actions: {
    //Показатель переданный с полезной нагрузкой представлен как asynchNum (объект)
    asyncDecrement: ({ commit }, asyncNum) => {
      setTimeout(() => {
        //Объекты asyncNum также могут быть просто статическими суммами
        commit('decrement', asyncNum.by);
      }, asyncNum.duration);
    }
  }
});

Приятная особенность — можно вернуть весь объект состояния в мутации, но зачем если мы можем использовать его и так. Отладка путешествия во времени (прохождение мутаций, чтобы найти ошибки) будет работать в любом случае.

В самом компоненте мы будем использовать computed для геттеров (потому что они у нас уже вычислены) и methods с dispatch для доступа к мутациям и действиям:

В `app.vue`:

1
2
3
4
5
6
7
8
9
10
computed: {
  value() {
    return this.$store.getters.value;
  }
},
methods: {
  increment() {
    this.$store.dispatch('increment', 2)
  }
}

Или вы можете использовать spread оператор. Это полезно, когда приходится работать с большим количеством мутаций / действий:

1
2
3
4
5
6
7
8
9
10
export default {
  // ...
  methods: {
    ...mapActions([
      'increment', // Сопоставьте this.increment() с этим. $ Store.commit('increment')
      'decrement',
      'asyncIncrement'
    ])
  }
}

Простой реальный пример

Давайте еще раз посмотрим приложение Weather Notifier с очень небольшим и простым количеством состояний в памяти Vuex. Вот репозиторий.

See the Pen Vue Weather Notifier by RinaMina (@FurkMe) on CodePen.0

В файле store.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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;
      }
  }
});

 

Здесь мы устанавливаем состояние showWeather, вначале оно установлено на false, потому что мы не хотим, чтобы анимация срабатывала сразу, пока пользователь не нажмет кнопку телефона. В мутациях мы настроили переключатель для состояния showWeather.

Мы также устанавливаем template в состояние 0. Мы будем использовать это число для поочередного прохода каждого погодного компонента. Поэтому в мутациях мы создали метод с именем updateTemplate. Это и переключает состояние showWeather, и обновляет template до следующего числа, но оно будет обнуляться до нуля, когда достигнет числа 4.

В App.vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<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>

В `dialog.vue`:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<script>
export default {
  computed: {
    template() {
      return this.$store.state.template;
    }
  },
  methods: {
    toggle() {
      this.$store.commit('toggle');
    }
  },
  mounted () {
    //enter weather
    const tl = new TimelineMax();
    ...
  }
}
</script>

В приведенном выше коде приложение использует showWeather для продвижения template, а Dialog просто переключает видимость компонента. Вы также можете увидеть, что в App.vue мы показываем и скрываем разные дочерние компоненты на основе значения шаблона в приложении template с тем шикарным условным рендерингом, о котором мы узнали в первой статье. В приложении мы оба слушаем изменения состояния в хранилище с вычисленными значениями и используем методы toggle() и updateTemplate() в методах, чтобы зафиксировать мутации хранилища.

Это базовый пример, но вы можете видеть, как с помощью сложного приложения с множеством состояний удобно управлять состоянием всего в одном месте, а не перемещать его вверх и вниз по нашим компонентам. Особенно, когда дочерние компоненты должны разговаривать с дочерними компонентами.

Если вам интересно углубиться в Vuex, есть отличная документация здесь. Вы могли заметить, что в последнем демо мы использовали некоторые компоненты transition, а также много анимации. Поговорим об этом в следующей статье!

Серия статей

  1. Рендеринг, директивы и события
  2. Components, Props и Slots
  3. Vue-cli
  4. Vuex (Вы здесь)
  5. Анимации
Оригинальная статья: https://css-tricks.com/intro-to-vue-4-vuex/

Комментарии

  • Оставьте первый комментарий - автор старался!

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: