Коммуникация компонентов Vue уходит глубоко в Vuex

Vue.js Vuex внешний фреймворк

Предложение: примеры в блоге размещать вvue_blog_projectВ инженерии рекомендуется сочетать инженерные примеры и блоги, чтобы учиться вместе

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

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

clipboard.png

1. Введение в Vuex

Отказ от ответственности: в этой презентации только Vuex сущность знаний, пожалуйста, обратитесь к более подробным знаниямОфициальный китайский сайт Vuex

1.1 Первое знакомство с Vuex

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

Vuex решен多个视图依赖于同一状态а также来自不同视图的行为需要变更同一状态Проблема в том, что разработчики энергии фокусируются на данных, а не на обновлении данных о передаче между компонентами.

1.2 Vuex каждый модуль

(1)state: используется для хранения данных, в магазинеуникальный источник данных

// 定义
new Vuex.Store({
    state: {
        allProducts: []
    }
    //...
})
// 组件中获取
this.$store.state.allProducts

(2)getters: Как и вычисляемые свойства в vue,Вторичная упаковка на основе данных состояния, обычно используется для фильтрации данных и расчета корреляции нескольких данных

// 定义
getters: {
    cartProducts(state, getters, rootState) 
        => (getters.allProducts.filter(p => p.quantity))
}
// 组件中获取
this.$store.getters.cartProducts

(3)mutations: аналогичная функция,Единственный способ изменить данные состояния и не может использоваться для обработки асинхронных событий (важно!!!)

// 定义
mutations: {
    setProducts (state, products) {
        state.allProducts = products
    }
}

// 组件中使用
this.$store.commit('setProducts', {//..options})

(4)actions: похоже на мутацию,Используется для отправки мутации для изменения состояния, не измененного напрямую, может содержать любую асинхронную операцию

// 定义(shop为api)
actions: {
    getAllProducts ({ commit }, payload) {
        shop.getProducts((res) => {
            commit('setProducts', res)
        })
    }
}

// 组件中使用
this.$store.dispatch('getAllProducts', {//..payload})

(5)modules: Подобно пространству имен, оно используется для определения и управления состоянием каждого модуля отдельно в проекте для упрощения обслуживания.

// 定义
const moduleA = {
    state: { ... },
    mutations: { ... },
    actions: { ... },
    getters: { ... }
}

const moduleB = {
    state: { ... },
    mutations: { ... },
    actions: { ... }
}

const store = new Vuex.Store({
    modules: {
        a: moduleA,
        b: moduleB
    }
})

// 组件中使用
store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态

Уведомление:По умолчанию действия, мутации и геттеры внутри модулей регистрируются в глобальном пространстве имен — это позволяет нескольким модулям реагировать на одну и ту же мутацию или действие, только состояние является локальным.Поэтому геттеры часто используются для обертывания состояния и его вывода, чтобы его можно было напрямую передать черезthis.$store.getters.способ получить данные без доступа к состоянию под модулем

1.3 Вспомогательные функции

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

// 组件中注册
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'

export default {
    computed: {
        // 数组形式,当映射的计算属性的名称与 state 的子节点名称相同时使用
        ...mapState(['allProducts'])
        // 对象形式,可重命名 state 子节点名称
        ...mapState({
            products: state => state.allProducts
        })
        // 下面为了简便,均以数组形式使用
        ...mapGetters(['cartProducts'])
    },
    methods: {
        ...mapMutations(['setProducts']),
        ...mapActions(['getAllProducts'])
    }
}

// 组件中使用
// 变量
this.allProducts
this.products
// 方法
this.setProducts()
this.getAllProducts()

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

Уведомление:

  1. Мутация должна следовать правилам ответа Vue.,См. раздел «Мутация» на официальном сайте Vuex.
  2. Прямые изменения модификации при запуске состояния обработки формывопрос,См. раздел обработки форм на официальном сайте Vuex.

Лучшие практики(Он будет использоваться в следующей демонстрации):

  1. Используйте константы вместо типов событий Mutation, что позволяет работать таким инструментам, как линтеры, а наличие этих констант в отдельных файлах дает вашим коллегам по коду обзор мутаций, содержащихся во всем приложении.
  2. хранить конфигурацию, используемую следующим образом
store
    ├── index.js             # 导出 store 的地方
    ├── state.js             # 根级别的 state
    ├── getters.js           # 二次包装state数据
    ├── actions.js           # 根级别的 action
    ├── mutations.js         # 根级别的 mutation
    ├── mutation-types.js    # 所有 mutation 的常量映射表
    └── modules              # 如果有.
        ├── ...

2. Установка Вьюкса

(1) Установить в проектеVuex:

npm install vuex --save

(2) Создайте новый в каталоге srcstore/index.js, где код выглядит следующим образом:

import Vue from 'vue'
import Vuex from 'vuex'
// 修改state时在console打印,便于调试
import createLogger from 'vuex/dist/logger'

Vue.use(Vuex)

const debug = process.env.NODE_ENV !== 'production'

const state = {}
const getters = {}
const mutataions = {}
const actions = {}

export default new Vuex.Store({
    state,
    getters,
    mutataions,
    actions,
    // 严格模式,非法修改state时报错
    strict: debug,
    plugins: debug ? [createLogger()] : []
})

(3) В файле вводаmain.jsДобавить:

// ...
import router from './router'
import store from './store'

new Vue({
    el: '#app',
    router,
    store,
    // ...
})

Вы можете сравнить способы установки vue-router и vuex: ониОба являются плагинами vue и вводятся при создании экземпляров компонентов. Ко всем компонентам в этом экземпляре можно получить доступ с помощьюthis.$routerа такжеthis.$storeзапрос к соответствующему экземпляру подключаемого модуля

3. Практика проекта Vuex

нужно: Завершите функцию анимации, показанную в начале статьи [Примечание:исходный код демо], данные и функции API следующие:

// 商品列表
[
    { 'id': 1, 'title': 'iPad 4 Mini', 'price': 500, 'inventory': 2 },
    { 'id': 2, 'title': 'H&M T-Shirt White', 'price': 10, 'inventory': 10 },
    { 'id': 3, 'title': 'Charli XCX - Sucker CD', 'price': 20, 'inventory': 5 }
]

Функция 1: Изменения инвентаря, список корзины покупок и сумма меняются при увеличении или уменьшении количества товаров.
Особенность 2: При очистке корзины все данные восстанавливаются

анализировать:
Компонентная структура: родительский компонент включает в себя два дочерних компонента, список продуктов и корзину;Аспекты данных: данные списка товаров поступают из API-интерфейса + количество товаров, добавленных в корзину, а список товаров, добавленных в корзину, поступает из фильтрации списка товаров;

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

(1) Код в магазине

const state = {
    all: []
}

const getters = {
    // 总商品列表
    allProducts: state => state.all,
    // 购物车商品列表
    cartProducts: (state, getters) => (getters.allProducts.filter(p => p.quantity)),
    // 购物车商品总价
    cartTotalPrice: (state, getters) => {
        return getters.cartProducts.reduce((total, product) => {
            return total + product.price * product.quantity
        }, 0)
    }
}

const mutations = {
    setProducts (state, products) {
        state.all = products
    },
    clearCartProducts (state) {
        state.all.forEach(p => {
            p.quantity = 0
        })
    }
}

const actions = {
    // 获取数据后,加入选取数量quantity的标识,以区分是否被加入购物车
    getAllProducts ({ commit }) {
        shop.getProducts((res) => {
            const newRes = res.map(p => Object.assign({}, p, {quantity: 0}))
            commit('setProducts', newRes)
        })
    }
}

(2) Компоненты списка продуктов ProductList.Vue

<template>
    <ul class="product-wrapper">
        <li class="row header">
            <div v-for="(th,i) in tHeader" :key="i">{{ th }}</div>
        </li>
        <li class="row" v-for="product in currentProducts" :key="product.id">
            <div>{{ product.title }}</div>
            <div>{{ product.price }}</div>
            <div>{{ product.inventory - product.quantity }}</div>
            <div>
                <el-input-number
                    :min="0" :max="product.inventory"
                    v-model="product.quantity"
                    @change="handleChange">
                </el-input-number>
            </div>
        </li>
    </ul>
</template>

<script>
import { mapGetters, mapMutations, mapActions } from 'vuex'

export default {
    data () {
        return {
            tHeader: ['名称', '价格', '剩余库存', '操作'],
            currentProducts: []
        }
    },
    computed: {
        ...mapGetters(['allProducts'])
    },
    // 为了避免表单直接修改store中的数据,需要使用watch模拟双向绑定
    watch: {
        allProducts: {
            handler (val) {
                this.currentProducts = JSON.parse(JSON.stringify(this.allProducts))
            },
            deep: true
        }
    },
    created () {
        this.getAllProducts()
    },
    methods: {
        handleChange () {
            this.setProducts(this.currentProducts)
        },
        ...mapMutations(['setProducts']),
        ...mapActions(['getAllProducts'])
    }
}
</script>

(3) Компоненты списка автомобилей для покупок ShoppingCart.Vue

<template>
    <div class="cart">
        <p v-show="!products.length"><i>Please add some products to cart.</i></p>
    <ul>
        <li v-for="product in products" :key="product.id">
            {{ product.title }} - {{ product.price }} x {{ product.quantity }}
        </li>
    </ul>
    <p>Total: {{ total }}</p>
    <el-button @click="clearCartProducts">CLEAR</el-button>
</div>
</template>

<script>
import { mapGetters, mapMutations } from 'vuex'

export default {
    computed: {
        ...mapGetters({
            products: 'cartProducts',
            total: 'cartTotalPrice'
        })
    },
    methods: {
        ...mapMutations(['clearCartProducts'])
    }
}
</script>

(4) В сочетании с упомянутой выше передовой практикой оптимизации:

Сначала разделите папку магазина в соответствии с древовидной структурой выше; далее:

новый в магазинеmutation-types.jsдокумент,

export const SET_PRODUCTS = 'SET_PRODUCTS'
export const CLEAR_CART_PRODUCTS = 'CLEAR_CART_PRODUCTS'

mutations.jsВнесите следующие изменения:

import * as types from './mutation-types'

export default {
    [types.SET_PRODUCTS] (state, products) {
        state.all = products
    },
    [types.CLEAR_CART_PRODUCTS] (state) {
        state.all.forEach(p => {
            p.quantity = 0
        })
    }
}

actions.jsВнесите следующие изменения:

import shop from '@/api/shop'
import * as types from './mutation-types'

export default {
    // 获取数据后,加入选取数量quantity的标识,以区分是否被加入购物车
    getAllProducts ({ commit }) {
        shop.getProducts((res) => {
        const newRes = res.map(p => Object.assign({}, p, {quantity: 0}))
            commit(types.SET_PRODUCTS, newRes)
        })
    },
    // 这里将mutation中的方法以action的形式输出,主要是组件中有使用mutation的方法,到时仅需引用mapActions即可,可按实际情况使用
    setProducts ({ commit }, products) {
        commit(types.SET_PRODUCTS, products)
    },
    clearCartProducts ({ commit }) {
        commit(types.CLEAR_CART_PRODUCTS)
    }
}

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

Здесь перечислены только основные части демо, пожалуйста, проверьте полный кодисходный код демо

Предыдущая: Углубленная коммуникация компонентов Vue