Vuex – глубокое погружение

Spread the love

Эта статья посвящена Vuex, библиотеке управления состоянием Vue.js. Мы рассмотрим проблемы, которые решает эта библиотека, основные концепции ее архитектуры, как ее настроить и использовать.

Vuex – это библиотека управления состоянием, созданная командой Vue для управления данными в приложениях Vue.js. Она обеспечивает централизованный способ управления данными, которые используются в приложении, и позволяет легко ими манипулировать.

Почему Vuex?

Vue способствует разделению представлений на компоненты. Эти компоненты могут многократно использоваться экземплярами Vue, которые принимают данные, методы и т. д. В данных хранится состояние представления, а методы позволяют манипулировать этим состоянием на основе взаимодействий пользователя с представлением.

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

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

В зависимости от положения этого второго компонента, вы можете решить эту задачу использовав либо props, либо this.$parent для прямого доступа к данным или методам второго компонента. Но что, если вам нужно сделать это для большого количества компонентов?

По мере того как проект увеличивается в размерах, вы обнаруживаете, что передаете очень большое количество props и начинается в большом количестве непосредственно манипулировать DOM для доступа к различным компонентам.

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

Он также делает наш код более структурированным, облегчает отладку, поскольку мы можем использовать DevTools для отслеживания возникающих ошибок, и, конечно, обеспечивает реактивность, на которой и построен Vue. Вы можете представить аналогию с Vuex как о глобальным объекте window в JavaScript – к которому каждый компонент имеет доступ.

Инсталяция Vuex

Чтобы установить Vuex в проект, запустите код ниже.

npm install vuex --save

Эта команда установит последнюю версию Vuex. Как только это будет сделано, нам нужно инициализировать Vuex в приложении Vue, создав наш файл store.js с кодом ниже;

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

Vue.use(Vuex)

Теперь мы можем приступить к созданию хранилища (store). Хранилище по сути является реактивным объектом, который содержит состояние приложения (state), геттеры (getters), мутации (mutations) и действия (actions).

Хранилище (Store)

Хранилище – это, по сути, централизованное состояние, в котором есть некоторые основные концепции, которые позволяют нам добиться этой централизации. Эти концепции:

  1. State (состояние)
  2. Getters (геттеры)
  3. Mutations (мутации)
  4. Actions (действия)

State

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

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

// Install the Vuex plugin on vue
Vue.use(Vuex);

// create a Vuex store instance
export const store = new Vuex.Store({
    state: {
        cart: ''
    }
})

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

Теперь давайте импортируем хранилище. Есть два основных способа сделать это:

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

template>
    <main>
        <h1>Cart Content</h1>
        <p>{{cartValue}}</p>
    </main>
</template>

<script>
// Import Vuex Store into Component
import store from 'store.js';
export default {
    computed: {
        cartValue() {
            // Return Vuex state from store
            return store.state.cart;
        }
    }
}
</script>

2. Внедрение глобального хранилища Vuex в Vue Instance, которое автоматически дает нам доступ к хранилищу из всех компонентов Vue в приложении с использованием синтаксиса this.$store:

import Vue from 'vue';
import store from './store.js';

new Vue({
    // Adding the Vuex store to the Vue instance
    store,
}).$mount('#app');
<template>
    <main>
        <h1>Cart Content</h1>
        <p>{{cartValue}}</p>
    </main>
</template>

<script>
export default {
    computed: {
        cartValue() {
            // Accessing the Vuex state
            return this.$store.state.cart;
        }
    }
}
</script>

Getters

Геттеры (Getters) являются в значительной степени вычисляемыми свойствами для хранилища Vuex. Они позволяют нам генерировать новое состояние на основе текущего состояния – например, вычисляя, сколько товаров у нас в корзине.

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

Чтобы создать геттер, мы делаем следующее:

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

// Install the Vuex plugin on vue
Vue.use(Vuex);

// create a Vuex store instance
export const store = new Vuex.Store({
    state: {
        cart: ["bread", "rice", "beans", "turkey"]
    },
  
    getters: {
        // Fetch the total number of items in the cart
        totalNumberOfCartItems: state => {
            return state.cart.length;
        },
    },
})

Далее мы получаем доступ к геттеру из нашего компонента Vue, выполнив следующие действия:

<template>
    <main>
        <h1>Cart Content</h1>
        <p>Total Number of Items: {{totalNumberOfCartItems}}</p>
    </main>
</template>

<script>
export default {
    computed: {
        totalNumberOfCartItems() {
            // Accessing the Vuex state
            return this.$store.getters.totalNumberOfCartItems;
        }
    }
}
</script>

Теперь, когда товар добавляется в корзину, общее количество товаров в корзине обновляется автоматически.

Mutations

Мутации – единственный способ, которым мы можем обновить наше состояние Vuex. Они выполняют одну и только одну задачу: установить состояние. Это функция, которая принимает два аргумента, состояние (state) и полезную нагрузку (payload), где полезная нагрузка не обязательна.

Полезная нагрузка (payload) – это просто данные, которые будут использоваться для обновления состояния. Мутации синхронны, и поэтому мы не можем выполнять асинхронные задачи в них.

Теперь давайте добавим мутацию в наш код:

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

// Install the Vuex plugin on vue
Vue.use(Vuex);

// create a Vuex store instance
export const store = new Vuex.Store({
    state: {
        cart: ["bread", "rice", "beans", "turkey"]
    },
  
    getters: {
        // Fetch the total number of items in the cart
        totalNumberOfCartItems: state => {
            return state.cart.length;
        },
    },
    
    mutations: {
        // Add item to cart
        addItemToCart (state, payload) {
            state.cart.push(payload);
        },
    },
})

Далее нам нужно обновить состояние из нашего компонента Vue, и для этого нам нужно будет зафиксировать (commit) мутацию.

<template>
    <main>
        <h1>Cart Content</h1>
        <p>Total Number of Items: {{totalNumberOfCartItems}}</p>
        <form @submit.prevent="addItemToCart">
            <input type="text" v-model="item" required>
            <button type="submit">Add to cart</button>
        </form>
    </main>
</template>

<script>
export default {
    data() {
        return {
            item: ''
        }
    },
    computed: {
        totalNumberOfCartItems() {
            // Accessing the Vuex state
            return this.$store.getters.totalNumberOfCartItems;
        }
    },
    methods: {
        addItemToCart() {
            // Check that the input field isn't empty
            if(this.item !== '') {
                // commiting the additemtocart mutation with the payload
                this.$store.commit('addItemToCart', this.item)
            }
        }
    }
}
</script>

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

Actions

Действия (actions) похожи на мутации, но вместо того, чтобы мутировать состояние, они совершают (commit) мутации. Главное отличие действий от мутаций это то что действия асинхронны, то есть предназначены для выполнения асинхронных задач; когда эти задачи выполнены, нам нужно фиксировать (commit) мутацию, которая, в свою очередь, обновляет состояние.

Чтобы продемонстрировать действия, мы рассмотрим отправку элементов корзины в API.

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

// Install the Vuex plugin on vue
Vue.use(Vuex);

// create a Vuex store instance
export const store = new Vuex.Store({
    state: {
        cart: ["bread", "rice", "beans", "turkey"]
    },
  
    getters: {
        // Fetch the total number of items in the cart
        totalNumberOfCartItems: state => {
            return state.cart.length;
        },
    },
    
    mutations: {
        // Add item to cart
        addItemToCart (state, payload) {
            state.cart.push(payload);
        },
        // Clear items in the cart
        emtpyCart (state) {
            state.cart = [];
        }
    },
    
    actions: {
        checkout({commit}, requestObject) {
            // API Call to submit the items in the cart
            Vue.http.post('submit', requestObject).then((response) => {
                // log success
                console.log(response);
                // Clear Cart by mutating the state
                commit('emptyCart');
            }).catch((error) => {
                // log error
                console.log(error);
            }
        }
    }
})

Глядя на приведенный выше код, мы создали действие с именем checkout, которое принимает две вещи:

  1. commit: что позволит нам вызвать метод commit внутри наших действий
  2. requestObject: что позволит нам передать данные в действие

Внутри действия, мы сделали асинхронный вызов API, а затем передали requestObject в API. В случае успеха мы записали ответ, а затем приступили к очистке состояния корзины, но сначала нам нужно было создать мутацию emptyCart, единственная задача которой – очистить состояние корзины.

Теперь, когда мы рассмотрели, как создавать действия, мы рассмотрим запуск этого действия. Чтобы вызвать действие, Vuex предоставляет нам команду отправки (dispatch).

this.$store.dispatch('actionName', payload);

Давайте добавим действие в наш код и отправим его из представления:

<template>
    <main>
        <h1>Cart Content</h1>
        <p>Total Number of Items: {{totalNumberOfCartItems}}</p>
        <form @submit.prevent="addItemToCart">
            <input type="text" v-model="item" required>
            <button type="submit">Add to cart</button>
        </form>
        
        <button type="button" @click="checkout">Checkout</button>
    </main>
</template>

<script>
export default {
    data() {
        return {
            item: ''
        }
    },
    computed: {
        totalNumberOfCartItems() {
            // Accessing the Vuex state
            return this.$store.getters.totalNumberOfCartItems;
        }
    },
    methods: {
        addItemToCart() {
            // Check that the input field isn't empty
            if(this.item !== '') {
                // commiting the additemtocart mutation with the payload
                this.$store.commit('addItemToCart', this.item)
            }
        },
        
        checkout() {
            // Make sure cart is not empty
            if(this.totalNumberOfCartItems > 0 ) {
                // create request
                let requestPayload = { cart: this.$store.state.cart };
                // Dispatch the action
                this.$store.dispatch('checkout', requestPayload);
            }
            else {
                alert('Cart is empty');
            }
        }
    }
}
</script>

На основе приведенного выше кода мы создали кнопку извлечения в представлении и создали метод извлечения, который проверяет, не пуста ли корзина, перед попыткой отправки действия, которое отправляет элементы.

Это работает, но чего-то не хватает. Вы можете спросить, что? Нам удалось отправить действие, но мы не знаем, было ли это действие успешным или нет.

Возможно вызов API не удался? Как я могу получить эту информацию, чтобы я мог уведомить пользователя? Действия могут обрабатывать Promises (Обещания), а также могут возвращать Promises.

Модифицируем наш пример кода для возврата Promise:

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

// Install the Vuex plugin on vue
Vue.use(Vuex);

// create a Vuex store instance
export const store = new Vuex.Store({
    state: {
        cart: ["bread", "rice", "beans", "turkey"]
    },
  
    getters: {
        // Fetch the total number of items in the cart
        totalNumberOfCartItems: state => {
            return state.cart.length;
        },
    },
    
    mutations: {
        // Add item to cart
        addItemToCart (state, payload) {
            state.cart.push(payload);
        },
        // Clear items in the cart
        emtpyCart (state) {
            state.cart = [];
        }
    },
    
    actions: {
        checkout({commit}, requestObject) {
            return new Promise((resolve, reject) => {
                
                // API Call to submit the items in the cart
                Vue.http.post('submit', requestObject).then((response) => {
                    // log success
                    console.log(response);
                    // Clear Cart by mutating the state
                    commit('emptyCart');
                    // return success
                    resolve(response);
                }).catch((error) => {
                    // log error
                    console.log(error);
                    // return error
                    reject(error);
                }
            })
        }
    }
})

Теперь мы можем использовать возвращаемое значение, чтобы обновить пользователя о состоянии в представлении следующим образом:

<template>
    <main>
        <h1>Cart Content</h1>
        <p>Total Number of Items: {{totalNumberOfCartItems}}</p>
        <form @submit.prevent="addItemToCart">
            <input type="text" v-model="item" required>
            <button type="submit">Add to cart</button>
        </form>
        
        <button type="button" @click="checkout">Checkout</button>
    </main>
</template>

<script>
export default {
    data() {
        return {
            item: ''
        }
    },
    computed: {
        totalNumberOfCartItems() {
            // Accessing the Vuex state
            return this.$store.getters.totalNumberOfCartItems;
        }
    },
    methods: {
        addItemToCart() {
            // Check that the input field isn't empty
            if(this.item !== '') {
                // commiting the additemtocart mutation with the payload
                this.$store.commit('addItemToCart', this.item)
            }
        },
        
        checkout() {
            // Make sure cart is not empty
            if(this.totalNumberOfCartItems > 0 ) {
                // create request
                let requestPayload = { cart: this.$store.state.cart };
                // Dispatch the action
                this.$store.dispatch('checkout', requestPayload).then((response) => {
                    // Alert Response from API
                    alert(response);
                }).catch((error) => {
                    // Alert Error from API
                    alert(error);
                });
            }
            else {
                alert('Cart is empty');
            }
        }
    }
}
</script>

Actions (Действия) также позволяют вам отправлять несколько actions (то есть action может отправлять одно или несколько других actions). Все, что вам нужно сделать, это передать dispatch в качестве аргумента, и вы сможете отправлять другие action внутри вашего action.

checkout({ dispatch, commit }, requestObject) {
    // dispatch an action
    dispatch('actionName');

    // dispatch another action
    dispatch('actionName2', request);
};

Добавление структуры в хранилище

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

store/
--| store.js
--| state.js
--| getters.js
--| mutations.js
--| actions.js

Теперь наше хранилище выглядит так:

import Vue from 'vue'
import Vuex from 'vuex'
import state from './state'
import getters from './getters'
import mutations from './mutations'
import actions from './actions'

Vue.use(Vuex)

export default new Vuex.Store({
  state,
  getters,
  mutations,
  actions
})

Модули

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

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

Реорганизовав наше хранилище в модуль, мы создадим файл cart.js и продолжим разбивать все наши состояния, мутации и действия в нашем хранилище, относящиеся к корзине, как показано ниже:

// import Vue
import Vue from 'vue';

export default {
    state: {
        cart: ["bread", "rice", "beans", "turkey"]
    },
  
    getters: {
        // Fetch the total number of items in the cart
        totalNumberOfCartItems: state => {
            return state.cart.length;
        },
    },
    
    mutations: {
        // Add item to cart
        addItemToCart (state, payload) {
            state.cart.push(payload);
        },
        // Clear items in the cart
        emtpyCart (state) {
            state.cart = [];
        }
    },
    
    actions: {
        checkout({commit}, requestObject) {
            return new Promise((resolve, reject) => {
                
                // API Call to submit the items in the cart
                Vue.http.post('submit', requestObject).then((response) => {
                    // log success
                    console.log(response);
                    // Clear Cart by mutating the state
                    commit('emptyCart');
                    // return success
                    resolve(response);
                }).catch((error) => {
                    // log error
                    console.log(error);
                    // return error
                    reject(error);
                }
            })
        }
    }
}

Далее мы импортируем и регистрируем его в нашем главном хранилище.

import Vue from 'vue'
import Vuex from 'vuex'
import state from './state'
import getters from './getters'
import mutations from './mutations'
import actions from './actions'
import cart from './modules/cart'

Vue.use(Vuex)

export default new Vuex.Store({
  state,
  getters,
  mutations,
  actions,
  modules: {
      cart
  }
})

Наконец, наша структура кода будет выглядеть так:

store/
--| store.js
--| state.js
--| getters.js
--| mutations.js
--| actions.js
--| modules/
    --| cart.js

Заключение

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


Оригинал: Nosa Obaseki Vuex—The Deep Dive

Была ли вам полезна эта статья?
[11 / 5]

Spread the love
Подписаться
Уведомление о
guest
1 Комментарий
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Оля
Оля
3 лет назад

Ошибке по всему гексту.
Вы можете представиться аналогию с Vuex как о глобальным объекте window в JavaScript — к которому каждый компонент имеет доступ.

Last edited 3 лет назад by Оля