Используйте vuex для более элегантной реализации функции корзины покупок.

Vuex

предисловие

Недавно я использовал ведро семейства Vue для создания клиентского проекта ПК-версии Xiaomi Mall, и у меня есть более глубокое понимание взаимодействия компонентов и управления состоянием. Поскольку разделение компонентов относительно нормальное, я сначала использовал базовые реквизиты и emit для передачи значения, но позже обнаружил, что когда вложение становится слишком глубоким, оно становится очень громоздким.В то же время, учитывая, что есть несколько компонентов, которые должны управляться вместе, базовый проход Значение не может удовлетворить спрос, поэтому vuex используется для разделения состояния управления модулем. Здесь необходимо упомянуть, что если нет состояния, управляемого несколькими компонентами, лучше не управлять им с помощью vuex.Vuex используется для управления общим состоянием нескольких компонентов.Если вам нужно только реализовать кросс-компонентный и межкомпонентный коммуникация компонентов поколения, использование шины событий, обеспечение/ввод и т. д. могут быть достигнуты.

Базовый набор процессов для Vuex для изменения данных

Во-первых, давайте разберемся с базовым набором процессов для управления данными в Vuex:

  • Процесс модификации данных в состоянии:

    Отправьте действие в компоненте, то есть отправьте (или вызовите напрямую) действие => действие, а затем зафиксируйте мутацию => мутацию для изменения состояния.

  • Данные в состоянии запрашиваются в действии, а затем данные в состоянии устанавливаются путем совершения мутации.

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

  • Примечание. Запрос в действии асинхронный, а мутация — синхронная.

Анализ работы корзины покупок на официальном сайте Xiaomi

Официальный эффект:

Мы можем видеть функцию корзины на картинке выше, здесь я кратко подытожу ее и разделю на следующие десять пунктов:

  1. Функциональная кнопка «Выбрать все»: когда кнопка «Выбрать все» включена, это означает, что выбраны все перечисленные ниже переключатели; нажмите, чтобы выбрать все, а затем нажмите, чтобы отменить все; в то же время, когда выбраны все переключатели ниже, кнопка «Выбрать все» выше будет автоматически. Статус обновления — «Выбрать все», а затем нажмите кнопку «Выбрать все», чтобы отменить все;
  2. Радиокнопка: нажмите один раз, чтобы выбрать текущий продукт, нажмите дважды, чтобы отменить выбор этого продукта, когда все радиокнопки выбраны, кнопка «Выбрать все» выше автоматически загорится (состояние «выбрать все»), пока один товар в текущем магазине корзина не выбрана, кнопка «Выбрать все» выше не загорается;
  3. Кнопка уменьшения количества товаров: нажмите на знак плюс, чтобы уменьшить количество товаров;
  4. Кнопка увеличения количества товаров: Нажмите на знак плюс, чтобы увеличить количество товаров;
  5. Общая цена каждого предмета: Рассчитайте текущую общую цену этого предмета;
  6. Кнопка «Удалить продукт»: нажмите кнопку «Удалить», чтобы удалить этот продукт из корзины;
  7. Количество всех товаров: отображает количество всех товаров в текущей корзине;
  8. Количество выбранных товаров: отображает количество выбранных товаров в текущей корзине;
  9. Общая стоимость всех выбранных товаров: Рассчитать общую стоимость всех выбранных товаров в текущей корзине, исключая невыбранные товары;
  10. Кнопка расчета: отображается, когда есть выбранный продукт, не отображается, когда продукт не выбран;

Функцию разобрали, давайте подумаем, как управлять состоянием и делить модули

Идея модуля Vuex

Поскольку это корзина для покупок, здесь я делю состояние корзины на два модуля в Vuex: модуль продуктов и модуль корзины.Модуль продуктов используется для хранения всей информации списка данных о продуктах, а модуль корзины продукты в корзине.Информация о списке; момент, который необходимо упомянуть здесь, заключается в том, что, поскольку информация о каждом продукте в модуле корзины не должна предоставлять все поля, аналогичные продукту в prodcuts, необходимо заполнить только несколько ключевых полей. при условии, а затем перейдите к модулю prodcuts для запроса достаточно информации об этом элементе. Может быть непонятно описывать, но я покажу это на коде ниже, и всем будет понятно.

Структура модуля Vuex

Каталог моего магазина выглядит следующим образом:

Я кратко представлю:

  1. В папке модуля находятся все модули, я временно размещаю здесь три модуля: cart.js, products.js и user.js (его смотреть не нужно, к функции корзины отношения не имеет) )
  2. Файл index.js объединяет содержимое всех модулей, и каждый модуль хранит состояние, мутации, действия и геттеры своего собственного модуля.
  3. В types.js хранятся константные имена мутаций всех модулей.Обязательных здесь нет, то есть Vuex тоже самое, что и управление состоянием в Redux и Flux.Изменение данных следует за набором процессов. Каждый коммит представляет собой постоянную функцию.

вводит код файла

// cart模块
export const CART_ADD_PRODUCT_TO_CART = 'CART_ADD_PRODUCT_TO_CART' // 添加购物车
export const CART_DEL_PRODUCT_TO_CART = 'CART_DEL_PRODUCT_TO_CART' // 删除购物车
export const CART_CHANGE_LOGIN_STATUS = 'CART_CHANGE_LOGIN_STATUS'  // 切换登陆状态
export const CART_ADD_PRODUCT_QUANTITY = 'CART_ADD_PRODUCT_QUANTITY'  // 添加商品数量
export const CART_DEL_PRODUCT_QUANTITY = 'CART_DEL_PRODUCT_QUANTITY'  // 减少商品数量
export const CART_SET_CHECKOUT_STATUS_ALL = 'CART_SET_CHECKOUT_STATUS_ALL'   // 一键改变所有商品购买状态的方法

// products模块
export const PRODUCTS_SET_PRODUCT = 'PRODUCTS_SET_PRODUCT' // 获取所有商品的列表

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

Объяснение от официальной документации Vuue:

Использование констант вместо типов событий мутации является распространенным шаблоном в различных реализациях Flux. Это позволяет работать таким инструментам, как линтеры, а наличие этих констант в отдельном файле дает вашим коллегам по коду обзор мутаций, которые содержит все приложение; вы можете использовать константы - в совместной работе нескольких человек. В больших проектах это может быть полезным. Но если вам это не нравится, вы можете вообще не делать этого.

код модуля продуктов

import { fetchGet } from "@/api/index" // api文件夹下封装的axios.get请求函数
import * as types from '../types' // types目录下的mutations函数的常量名
const state = {
  recommendList: [] // 存放所有商品的信息
}

const getters = {}

const mutations = {
  [types.PRODUCTS_SET_PRODUCT](state, products) { // 第一个参数是state 可以修改state 将请求回来的数据保存在state中
    state.recommendList = products
  }
}

const actions = {
  getAllProducts({ commit }) { // 所有的api请求都放在actions中
    fetchGet("/cart").then(res => {
      let allProducts = res.data.list.list
      commit(types.PRODUCTS_SET_PRODUCT, allProducts)
    })
  }
}

export default {
  namespaced: true, // 添加命名空间
  state,
  getters,
  mutations,
  actions
}
这里我放上recommendList中每一条数据的字段 例如其中一条为:
  /*
  {
    productid: "11137",
    name: "小米CC9 Pro 6GB+128GB", 
    price: 2799, 
    image: "//i1.mifile.cn/a1/pms_1572941393.18077211.jpg",
    comments: 0
  }
  */

Логика приведенного выше кода заключается в вызове функции fetchGet(), инкапсулированной в index.js в каталоге API, для запроса данных в методе getAllProducts в действиях, отправки фиксации в функцию types.PRODUCTS_SET_PRODUCT (установить состояние) в мутациях и затем установите все Список информации о продуктеrecommendList

Несколько замечаний:

  1. Модуль products хранит состояние, геттеры (этот модуль пока не используется), мутации и действия, которые существуют в каждом модуле подмодуля, и, наконец, экспортирует четыре части этого модуля;

  2. Пространство имен namespaced: true используется при экспорте. Что такое пространство имен, это может сделать наши модули более тщательно разделенными. Каждый модуль хранит состояние, геттеры, мутации и действия; используйте инструмент отладки Vue devtools для просмотра Vuex. Статус очень ясен. , только обратите внимание на один момент, здесь используется пространство имен, пожалуйста, используйте все модули, и вам нужно добавить имя модуля при вызове геттеров, действий и других методов в компоненте

    • Например, при вызове действий:
    1. this.$store.dispatch("products/getAllProducts"); 
    2. methods: mapActions("cart", ["addProductToCart"])
    
    • При вызове геттеров:
    computed: mapGetters("user", ["loginStatus"])
    

    Необходимо добавить префикс имени модуля для корректного выполнения операции

  3. Все запросы данных должны быть размещены в действиях

Код модуля корзины (основной модуль)

Позвольте мне сначала поставить код, а затем я объясню его медленно

import * as types from '../types'

const state = { // 购物车需要自己的状态 购物列表
  items: [ 
    { productid: "11137", quantity: 1, checkoutStatus: false },
    { productid: "8750", quantity: 1, checkoutStatus: false }
  ]
}

const getters = {
  // 返回购物车商品列表完整信息
  cartProducts: (state, getters, rootState) => {
    if (!state.items.length) return [] // map不会对空数组进行检测 map不会改变原始数组
    return state.items.map(({ productid, quantity, checkoutStatus }) => { // map()方法返回一个新数组,数组中的元素为原始数组元素调用函数处理的后值。
      const product = rootState.products.recommendList.find(product => product.productid === productid) // 拿到items中的数据去查阅products中的数据, rootState(根节点状态)参数可以拿到别的模块的state状态
      if (!product) return {} // action请求异步,如果此时的数据还没有请求回来 就返回空对象
      return {
        src: product.image, // product的图片地址
        name: product.name, // product的名字
        price: product.price, // product的单价
        productid, // product的id
        quantity, // product的数量,默认为1
        simpleTotal: quantity * product.price, // 单项product的总价价
        checkoutStatus: checkoutStatus // product的选中状态
      }
    })
  },
  // 返回选中商品的总价
  cartTotalPrice: (state, getters) => {
    return getters.cartProducts.reduce((total, product) => {
      if (product.checkoutStatus) {
        return total + product.simpleTotal
      }
      return total
    }, 0)
  },
  // 返回所有商品总价,不管有没有选中
  allPrice: (state, getters) => {
    return getters.cartProducts.reduce((total, product) => {
      return total + product.simpleTotal
    }, 0)
  },
  // 返回所有商品总数量,不管有没有选中
  allProducts: (state, getters) => {
    return getters.cartProducts.reduce((total, product) => {
      return total + product.quantity
    }, 0)
  },
  // 返回所有选中的商品数量
  allSelectProducts: (state, getters) => {
    return getters.cartProducts.reduce((total, product) => {
      if (product.checkoutStatus) {
        return total + product.quantity
      }
      return total
    }, 0)
  },
  // 返回所有商品条数
  allProductsItem: (state) => {
    return state.items.length
  },
  // 返回商品是否全选 是返回true 否则false
  isSelectAll: (state) => {
    if (!state.items.length) return false
    return state.items.every(item => { // every() 不会对空数组进行检测
      return item.checkoutStatus === true
    })
  },
  // 返回是否有选中的商品 是返回true 否则false
  hasSelect: (state) => {
    if (!state.items.length) return false
    return state.items.some(item => { // some() 不会对空数组进行检测
      return item.checkoutStatus === true
    })
  }
}

const mutations = {
  // 添加一条商品的方法
  [types.CART_ADD_PRODUCT_TO_CART](state, { productid }) {
    state.items.push({
      productid,
      quantity: 1,
      checkoutStatus: false
    })
  },
  // 删除一条商品的方法
  [types.CART_DEL_PRODUCT_TO_CART](state, productid) {
    state.items.forEach((item, index) => {
      if (item.productid === productid) {
        state.items.splice(index, 1)
      }
    });
  },
  // 增加一条商品中商品数量的方法
  [types.CART_ADD_PRODUCT_QUANTITY](state, productid) {
    const cartItem = state.items.find(item => item.productid == productid)
    cartItem.quantity++
  },
  // 减少一条商品中商品数量的方法
  [types.CART_DEL_PRODUCT_QUANTITY](state, productid) {
    const cartItem = state.items.find(item => item.productid == productid)
    if (cartItem.quantity > 1) { // 商品数量大于1时才能减少
      cartItem.quantity--
    }
    else cartItem.quantity = 1
  },
  // 改变单条商品的选中不选中状态的方法(单选按钮)
  [types.CART_SET_CHECKOUT_STATUS](state, productid) {
    const cartItem = state.items.find(item => item.productid == productid)
    cartItem.checkoutStatus = !cartItem.checkoutStatus
  },
  // 改变所有商品的选中不选中状态的方法(全选按钮)
  [types.CART_SET_CHECKOUT_STATUS_ALL](state, status) {
    state.items.forEach(item => {
      if (!item.checkoutStatus === status) {
        item.checkoutStatus = status
      }
    })
  }
}

const actions = {
  // 添加购物车的方法,如果此时购物车内有该条商品,就添加商品数量,否则添加商品
  addProductToCart({ state, commit }, product) {
    const cartItem = state.items.find(item => item.productid === product.productid)
    if (!cartItem) {
      commit(types.CART_ADD_PRODUCT_TO_CART, { productid: product.productid })
    } else {
      commit(types.CART_ADD_PRODUCT_QUANTITY, cartItem.productid)
    }
  },
  // 购物车内删除一条商品的方法
  delProductToCart({ commit }, productid) {
    commit(types.CART_DEL_PRODUCT_TO_CART, productid)
  },
  // 添加商品数量的方法
  addProductQuantity({ commit }, productid) {
    commit(types.CART_ADD_PRODUCT_QUANTITY, productid)
  },
  // 减少商品数量的方法
  delProductQuantity({ commit }, productid) {
    commit(types.CART_DEL_PRODUCT_QUANTITY, productid)
  },
  // 切换一条商品的选中状态的方法
  setCheckoutStatus({ commit }, productid) {
    commit(types.CART_SET_CHECKOUT_STATUS, productid)
  },
  // 切换所有商品选中状态的方法
  setCheckoutStatusAll({ commit }, status) {
    commit(types.CART_SET_CHECKOUT_STATUS_ALL, status)
  }
}

export default {
  namespaced: true, // 添加命名空间
  state,
  getters,
  mutations,
  actions
}

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

В соответствии с идеей демонстрации корзины покупок Youda: каждый элемент данных в корзине не должен хранить все поля данных каждого продукта, должны существовать только некоторые ключевые поля, а затем перенести эти поля в продукты. Вы можете запросить соответствующие данные о продукте в , а затем вернуть данные. Бывает, что эту задачу может выполнить геттер в Vuex, который может поддерживать данные и автоматически обновлять их в ответ на какие-то операции с данными о товаре на странице корзины.

В cartProducts я использую метод карты, чтобы получить идентификатор каждой информации о продукте в элементах, а затем передаю идентификатор каждого продукта в рекомендательный список, который хранит все списки информации о продуктах в модуле продуктов для запроса, и если я нахожу элемент, Я верну объект, формат объекта следующий:

{
	src: product.image, // product的图片地址
    name: product.name, // product的名字
    price: product.price, // product的单价
    productid, // product的id
    quantity, // product的数量,默认为1
    checkoutStatus: checkoutStatus, // product的选中状态
    simpleTotal: quantity * product.price, // 单项product的总价格
}

Вышеупомянутый объект - это все информационное содержание всего продукта, отображаемого в корзине на странице. Первые три элемента - это поля, запрашиваемые в модуле продуктов из идентификатора в элементах, а следующие три элемента - это данные каждого элемент в поле "товары", последний пункт предназначен для расчета общей стоимости текущего продукта, то есть умножения цены за единицу этого продукта на количество продуктов в этом продукте. Строго говоря, этот объект находится в состоянии слияния, потому что данные, которые вы получаете, не могут соответствовать всем требованиям в корзине, поэтому есть еще некоторые поля, которые вам нужно определить и добавить самостоятельно.Почему некоторые общие поля не в списке рекомендаций в продуктах?Что насчет добавления? Например, поле checkoutStatus, поскольку мои данные о продукте взяты непосредственно из данных, возвращенных Xiaomi, я поместил их непосредственно в локальный модуль mock.js и не изменил эти официальные данные. Поэтому я добавил поле checkoutStatus к элементам, и эффект не влияет на логику.

Необходимо рассказать о параметрах каждого метода в геттерах, официальное определение этих методов может иметь четыре параметра state, getters, rootState, rootGetters

  • состояние: представляет состояние в текущем модуле

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

  • rootState: после включения пространства имен пространство имен модуль может получить доступ к данным состояния другого модуля.

  • rootGetters: после того, как пространство имен включено, модуль может получить доступ к данным Getters другого модуля.

Раз уж мы говорили о параметрах геттеров, поговорим о параметрах других действий и методов в мутациях.

Официально определите параметры, полученные каждым методом в действиях:

** 1. контекст (объект) содержит { state, commit, rootState, rootGetters, getters ... } и т. д. **

2. Параметр полезной нагрузки (payload) переданный при вызове

  • контекст: функция действия принимает объект контекста с теми же методами и свойствами, что и экземпляр хранилища, а мутацию можно отправить, вызвав context.commit;context.stateа такжеcontext.gettersполучить состояние и геттеры
  • Полезная нагрузка: это параметр, который передается при вызове действия.В большинстве случаев передаваемый параметр представляет собой объект, который официально называется полезной нагрузкой.

Параметры, полученные каждым методом в мутациях, состоянии, полезной нагрузке (load)

  • состояние: представляет состояние в текущем модуле
  • Пейлоад (payload): Фактически это параметр, передаваемый при коммите, только в официальном документе сказано, что в большинстве случаев пейлоад должен быть объектом, который может содержать несколько полей и записанная мутация будет легче читаться. Это означает, что в большинстве случаев лучше передать параметр объекту, и нет обязательного требования.

Затем я сосредоточусь на методе добавления в корзину и методе удаления товара из корзины.

Как добавить в корзину: addProductToCart({ state, commit }, product)

Во втором параметре передается product, это информация о каждом товаре на текущей странице, которая является объектом Затем в addProductToCart по id текущего товара product проверяется есть ли этот товар в позициях , и если есть такой товар Для товара я совершу мутацию, добавляющую количество товаров. Если нет, то я совершу метод для добавления товара. В мутации, добавляющей товар в корзину, я добавлять объект с тремя полями по умолчанию каждый раз.Тот же формат, что и каждый фрагмент данных в элементах, пока элементы в состоянии меняются, корзинуПродукты в геттерах автоматически обнаружат, а затем пересчитают и повторно обновят данные , что приведет к тому, что часть данных появится на странице.

**Как удалить товар из корзины: **delProductToCart({commit}, productid)

В логике этого метода нет ничего особенного, но когда я вызываю этот метод, я вызываю его в пользовательском всплывающем окне.Это пользовательское всплывающее окно похоже на плагин, но не такое элегантное. конец, я просто использую Vue.extend(), инкапсулирующий два глобальных метода, смонтированных на Vue.prototype, один щелкает всплывающее окно (добавляет DOM в тело), ​​а другой щелкает, чтобы закрыть (удалить DOM), в Реализация Иногда также встречаются небольшие ямки, о которых можно будет рассказать в следующей статье.

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

код index.js

import Vue from 'vue'
import Vuex from 'vuex'
import cart from './module/cart'
import products from './module/products'
import user from './module/user'
Vue.use(Vuex)

export default new Vuex.Store({
  // 设计数据中心 模块
  modules: {  // 分模块
    user, 
    cart, 	  // 购物车 cart 
    products // 商品 products
  }
})

Здесь нужно интегрировать все модули и объединить их в один магазин. Наконец, его можно импортировать глобально в main.js.

Окончательный эффект

Суммировать

Наконец, давайте взглянем на общую идею процесса: во-первых, он должен быть разделен на модули, все данные о товарах должны быть размещены в одном модуле и запрашиваться обратно в действии, корзина должна хранить свой собственный статус списка товаров, и возьмите идентификатор каждого продукта в корзине. Перейдите к модулю продукта, чтобы запросить соответствующую информацию, а затем объедините фактический спрос, чтобы рассчитать соответствующее значение, и объедините их в объект. Этот объект в основном все, что нужно отображается на странице продукта. Просто возьмите его в компоненте. Затем некоторые другие соответствующие функции могут быть реализованы через геттеры и мутации соответственно. После реализации просто вызовите эти методы в компоненте.

Относительно функциональная корзина завершена, по сути сложного кода нет. Но для меня, все еще новичка, я думаю, что это все еще хорошо и очень радостно, поэтому я написал эту статью с душой. Потом при написании этих методов используются методы forEach, map, reduce, every, some и другие в массиве, и мне лично кажется, что они написаны более изящно. Это первая статья, которую я написал, поэтому я всегда дрожал, когда писал ее, потому что боялся, что не смогу внятно описать ее или объяснить неправильную концепцию.Короче говоря, это было довольно сложно. Но я, наконец, написал это, и я надеюсь, что смогу настоять на том, чтобы написать что-нибудь в будущем, чтобы я мог расти быстрее.

Проект еще не разработан, но также дам адрес для ознакомления:адрес проекта

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