Как разработчик Vue.js, мы хотим, чтобы наш код был чистым и аккуратным, создавая оптимальный объем повторно используемого кода.

С Composition API мы можем сделать это, создавая многоразовые JS модули, также известные как функции-ловушки и дочерние компоненты.

Так в чем между ними разница?

Когда мы используем тот или иной вариант?

Давайте разберемся!

Модуль JavaScriptон же функция ловушки — отличный способ отделить бизнес-логику от компонента Vue.

Он полностью переиспользуемый и независимый от пользовательского интерфейса.

Если вы исходите из шаблона MVC, это будет его часть M (модель).

Компоненты Vue, которые не основаны на странице, то есть к ним не привязан маршрут, также известные как дочерние компоненты, отлично подходят, когда мы хотим повторно использовать код HTML-шаблона, также известный как пользовательский интерфейс, который можно использовать в нескольких родительских компонентах.

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

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

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

Получить список продуктов

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

Я использовал Firebase в приведенном ниже примере для асинхронных операций.

У меня есть статья о том, как установить Firebase в ваш проект vue, а также о том, как делать запросы Cloud Firestore, если вам интересно.

Сначала создайте реактивный массив продукта и вызовите функцию loadProduct() внутри метода жизненного цикла onMounted().

Рекомендуется
Must-Know Ref и Reactive Differences In Vue 3 Composition API

import firebase from "firebase";
import { onMounted, reactive } from "vue";
export default {
  setup() {
    const products = reactive([]);
    onMounted(() => {
      loadProducts();
    });
    const loadProducts = async () {
      try {
        const productsSnap = await firebase
          .firestore()
          .collection("products")
          .orderBy("brand", "asc")
          .get();
        productsSnap.forEach((doc) => {
          let product = doc.data();
          product.id = doc.id;
          products.push(product);
        });
      } catch (e) {
        console.log("Error Loading Products");
      }
    }
    return {
      products,
    };
  },
};

Внутри объявления loadProducts() используйте запрос Firebase, чтобы получить список продуктов, которые я уже сохранил в базе данных Firebase Cloud Firestore.

Наконец, верните массив продуктов для использования в шаблоне.

Каждый объект продукта имеет два ключа:

  • заглавие
  • УПЦ

Ничего фантастического.

Это прекрасно работает!

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

Мне придется переписать код loadProducts(), чего я делать не хочу.

С помощью Vue 3 Composition API мы можем легко извлечь асинхронный код в независимый от пользовательского интерфейса модуль JavaScript и повторно использовать его в любом месте нашего приложения.

Давайте посмотрим, как это сделать.

Рекомендуем
Обязательно знать Ref и Reactive Различия в Vue 3 Composition API

Многоразовый модуль Product.js

Создайте файл Product.js в папке src/modules, куда будут помещаться все мои асинхронные операции, связанные с продуктом, такие как loadProducts, добавитьПродукт, обновитьПродукт, удалитьПродукт и т. д.

Давайте посмотрим, как извлечь код loadProducts() из ProductList.vue в Product.js.

Импортируйте Firebase, так как мы будем использовать его для асинхронных операций CRUD.

Затем объявите функцию useProduct(), а также объект реактивного состояния внутри функции, которая будет иметь одно свойство, называемое продуктами, которое является типом массива.

import { reactive, toRefs } from "vue";
import firebase from "firebase";
export default function useProduct() {
  const state = reactive({
    products: [],
  });
  const loadProducts = async() => {
    try {
      const productsSnap = await firebase.firestore().collection("products").orderBy("brand", "asc").get();
      productsSnap.forEach(doc => {
        let product = doc.data();
        product.id = doc.id;
        state.products.push(product);
      });
    } catch (e) {
      console.log("Error Loading Products")
    }
  }
  return { ...toRefs(state),
    loadProducts
  }
}

Затем создайте функцию loadProducts(), которая получает данные о продуктах из коллекции продуктов в Cloud Firestore и получает их снимок.

Прокрутите их и поместите каждый продукт в массив продуктов.

Наконец, верните состояние и loadProduct.

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

Теперь у нас есть модуль Product.js, готовый к использованию внутри ProductList.vue или любого компонента, которому нужен список продуктов в нашем приложении.

ProductList.vue

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

Сначала импортируйте модуль продукта вверху.

Во-вторых, вызовите функцию useProduct() и деструктурируйте products и loadProducts внутри функции setup().

Затем вызовите loadProducts() внутри метода жизненного цикла onMounted().

import { onMounted } from "vue";
import useProduct from "@/modules/product";
export default {
  setup() {
    const { products, loadProducts } = useProduct();
    onMounted(async () => {
      await loadProducts();
    });
    return {
      products,
    };
  },
};

Наконец, верните продукты, которые такие же, как и раньше.

На этом этапе продукты доступны в шаблоне для использования, и мы можем просмотреть их, используя v-for, чтобы показать, как мы хотим.

Рекомендуем
Vue + Firestore ← Создайте простое приложение CRUD с аутентификацией

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

Еще одним преимуществом использования таких модулей, как Product.js, является их использование для управления состоянием.

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

Чтобы использовать модули для управления состоянием, все, что нам нужно сделать, это объявить объектную модель state вне функции useProduct(), которая затем станет глобальной переменной.

import { reactive, toRefs } from "vue";
import firebase from "firebase";
 const state = reactive({
        products: [],
 });
export default function useProduct() {
  ...
}

Таким образом, мне не нужно извлекать данные из Firebase каждый раз, когда я иду по маршруту ProductList.vue.

Давайте взглянем на создание повторно используемого компонента.

Многоразовый компонент Vue

В некоторых сценариях вам потребуется повторно использовать код шаблона.

Например, предположим, что у нас есть два представления в нашем приложении; один для создания нового продукта (ProductAdd.vue), а другой — для редактирования продукта (ProductEdit.vue).

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

Давайте создадим компонент с именем ProductForm.vue в папке src/components.

Это будет дочерний компонент с элементами формы, которые затем будут использоваться файлами ProductAdd.vue и ProductEdit.vue.

Рекомендуем
Обязательно знать различия в создании свойств в Vue 2 и Vue 3

ProductForm.vue

Вот код простого шаблона формы в компоненте ProductForm.vue с двумя полями ввода: title и upc.

<template>
  <form @submit.prevent>
    <h1>Create Product</h1>
    <div class="field">
      <label>Title</label>
      <input type="text" v-model="product.title" />
    </div>
    <div class="field">
        <label>UPC</label>
        <input type="text" v-model="product.upc" />
      </div>
    </div>
    <button class="ui button blue tiny" @click="productSaveButtonPressed">Save Product</button>
  </form>
</template>

Есть одна кнопка с функцией обратного звонка.

Ничего необычного.

Теперь давайте объявим объект продукта внутри функции setup(), которая уже привязана к полю ввода в приведенном выше коде шаблона.

<script>
import { reactive } from "vue";
export default {
  
  setup(_, {emit}) {
    const product = reactive({
      title: "",
      upc: 0,
    });
    const saveProductButtonPressed = () => {
      emit("product", product);
    };
    return { product, saveProductButtonPressed };
  },
};
</script>

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

Наконец, верните product и saveProductButtonPressed, чтобы код шаблона имел к ним доступ.

Теперь, когда у нас есть повторно используемый компонент представления, давайте используем его внутри компонента ProductAdd.vue.

ProductAdd.vue

Давайте используем дочерний компонент ProductForm.vue внутри компонента ProductAdd.vue.

Сначала импортируйте компонент ProductForm.vue.

Затем добавьте его в объект компонентов.

<template>
  <ProductForm @product="getProductData" />
</template>
<script>
import ProductForm from "@/components/ProductForm";
export default {
  components: {
    ProductForm,
  },
  setup() {
    const getProductData = (product) => {
      console.log(product)
    };
    return { getProductData };
  },
};
</script>

Наконец, запишите данные о продукте, созданные ProductForm.vue в шаблоне с помощью функции getProductData(), и фактическое значение будет в аргументе сигнатуры функции.

Теперь, когда у нас есть данные формы, давайте фактически добавим продукт в базу данных.

Добавить продукт в Product.js

Теперь создайте функцию addProduct() внутри Product.js, о которой я уже упоминал, где выполняются все асинхронные операции, связанные с продуктом.

...
export default function useProducts() {
   ...
    const addProduct = async (product) => {
        try {
            await firebase.firestore().collection("products").doc().set(product);
        } catch (e) {
            console.log(e.message);
        }
    }
    return { ...toRefs(state), loadProducts, addProduct }
}

Теперь функция addProduct() готова к использованию внутри компонента ProductAdd.vue, мы можем использовать ее для фактической вставки данных в базу данных Firebase.

<template>
  <ProductForm @product="getProductData" />
</template>
<script>
import ProductForm from "@/components/ProductForm";
import useProduct from "@/modules/product";
export default {
  components: {
    ProductForm,
  },
  setup() {
    const getProductData = async (product) => {
      await addProduct(product);
    };
    return { getProductData };
  },
};
</script>

Довольно просто!

Давайте посмотрим, как использовать тот же компонент ProductForm.vue для редактирования продукта.

ProductEdit.vue

В отличие от ProductAdd.vue, нам нужно сначала отправить данные о продукте, которые мы хотим отредактировать, в файл ProductForm.vue, чтобы предварительно заполнить данные в полях ввода.

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

<template>
  <ProductForm
    @product="getProductData"
  />
</template>
<script>
import ProductForm from "@/components/ProductForm";
export default {
  components: {
    ProductForm,
  },
  setup() {
    const getProductData = (product) => {
      console.log(product)
    };
    return { getProductData };
  },
};
</script>

Теперь нам нужно создать две функции внутри модуля Product.js, чтобы правильно редактировать продукт.

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

Загрузка и редактирование продукта в Product.js

Добавьте свойство продукта, которое является типом объекта, к объекту состояния.

Затем определите loadProduct(), которая будет принимать идентификатор продукта в качестве аргумента, получать данные о продукте из Firebase и устанавливать их в объект продукта.

...
const state = reactive({
    products: [],
    product: {},
});
export default function useProduct() {
    ...
   const loadProduct = async (id) => {
        try {
            const productSnap = await firebase.firestore().collection("products").doc(id).get();
            state.product = productSnap.data();
        } catch (e) {
            console.log(e.message);
        }
    }
    const updateProduct = async (id, product) => {
        try {
            await firebase.firestore().collection("products").doc(id).update(product);
        } catch (e) {
            console.log(e.message);
        }
    }
    return { ...toRefs(state), loadProducts, loadProduct, updateProduct, addProduct }
}

Далее следует updateProduct(), который принимает два аргумента: идентификатор продукта и фактические данные о продукте, которые необходимо обновить.

Наконец, верните их, чтобы мы могли получить к ним доступ в компоненте ProductEdit.vue.

Теперь давайте получим продукт, который мы хотим отредактировать, и заполним данные о продукте в полях формы.

Обновить продукт

Предположим, что мы можем получить идентификатор продукта из URL-адреса при переходе к компоненту ProductEdit.vue и вызвать функцию loadProduct() с продуктом внутри onMounted() метод жизненного цикла.

<template>
  <ProductForm
    :editProduct="product"
    @product="getProductData"
  />
</template>
<script>
import { onMounted } from "vue";
import { useRoute } from "vue-router";
import useProduct from "@/modules/product";
import ProductForm from "@/components/ProductForm";
export default {
  components: {
    ProductForm,
  },
  setup() {
    const route = useRoute();
     const {
      product,
      loadProduct,
    } = useProduct();
    onMounted(async () => {
      await loadProoduct(route.params.id) 
    });
    const getProductData = (product) => {
      console.log(product)
    };
    return { product, getProductData };
  },
};
</script>

После получения данных о продукте в компоненте ProductEdit.vue отправьте их в шаблон ProductForm.vue в качестве значения атрибута :editProduct.

Заполненные данные о продукте в форме

В ProductForm.vue зафиксируйте свойство editProduct с помощью объекта props.

Затем прокрутите свойства editProduct и установите соответствующее значение для объекта продукта с помощью ключа.

import { onMounted, reactive } from "vue";
export default {
 props: {
    editProduct: Object,
 },
  onMounted(() => {
      if (props.editProduct) {
        for (let key in props.editProduct) {
          product[key] = props.editProduct[key];
      }
   }
...
}

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

Обновить данные о продукте

На этом этапе пользователь сможет изменить любые данные о товаре.

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

Как и в предыдущем примере, мы можем использовать функцию getProductData() для сбора данных.

...
     const {
      product,
      loadProduct,
      updateProduct
    } = useProduct();
    ...
    const getProductData = (product) => {
      await updateProduct(route.params.id, product);
    };
...
  },
};
</script>

Наконец, вызовите функцию updateProduct() с идентификатором продукта и объектом продукта, который мы хотим обновить новыми данными в базе данных.

Вот и все!

Вывод

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

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

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

Теперь вы лучше понимаете не только различия, но и то, какой из них использовать.

Я надеюсь, что это помогает!

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

Удачного кодирования!