Как создать MVP с помощью React и Firebase

Разработчики программного обеспечения обычно создают MVP Программирование и разработка

При создании прототипа идеи рекомендуется упростить и создать только минимальные основные функции, необходимые для вывода продукта на рынок. Это поможет вам определить, есть ли рынок для вашего приложения, прежде чем тратить время и деньги на продукт, который никому не интересен. Это известно, как «минимально жизнеспособный продукт» (MVP). В этом руководстве вы узнаете, как создать MVP с использованием React и Firebase, платформы backend-as-a-service.

Чтобы получить максимальную отдачу, мы не будем разрабатывать приложение React + Firebase шаг за шагом. Вместо этого я разобью рабочий прототип и объясню ключевые концепции, используя псевдо-подобный язык кодирования. Фактический код довольно многословен, поскольку в основном это интерфейсная логика, предназначенная для работы со многими аспектами, такими как управление состоянием, адаптивный дизайн пользовательского интерфейса и доступность.

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

Почему именно Firebase?

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

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

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

Эти сервисы Firebase включают:

  • аутентификация
  • база данных
  • место хранения
  • облачные функции
  • аналитика
  • хостинг

Предпосылки

Обратите внимание, что это руководство написано для разработчиков React от среднего до продвинутого, которые хотят быстро научиться использовать Firebase в своих проектах. Я предоставил несколько дополнительных тем, с которыми вам необходимо ознакомиться, прежде чем продолжить:

  • React и TailwindCSS
  • React Query
  • Vite — лучшая альтернативаcreate-react-app

План проекта Firebase

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

Проект, который мы рассмотрим, представляет собой простую онлайн-библиотеку

Логика приложения организована следующим образом:

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

Ниже представлена ​​иллюстрация основной архитектуры проекта:

Ниже представлена ​​иллюстрация основной архитектуры проекта

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

├── components
│   ├── entity (e.g. book)
│   │   ├── Card.jsx (-> BookCard)
│   │   ├── Detail.jsx (-> BookDetail)
│   │   ├── Form.jsx
│   │   └── List.jsx
│   └── ui
│       └── Component.jsx (e.g. PageHeader, Alert)
├── layout
│   ├── Footer.jsx
│   └── Navbar.jsx
├── screens
│   ├── entity
│   │   ├── Detail.jsx (-> ScreenBookDetail)
│   │   ├── Form.jsx (-> ScreenBookForm)
│   │   └── List.jsx
│   ├── category
│   │   ├── Form.jsx
│   │   └── List.jsx
│   ├── Home.jsx
│   └── NotFound.jsx
└── services
    └── Service.js (e.g. Database, Storage)

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

Настройка проекта Firebase

Проект, который мы будем использовать, был построен с использованием шаблона Vite + React. Чтобы настроить проект в своей рабочей области, просто откройте терминал и выполните следующие действия:

# Clone project
git clone git@github.com:sitepoint-editors/sitepoint-books-firebase.git
cd sitepoint-books-firebase

# Install dependencies
npm install

# Prepare environment config file
cp env.example .env.local

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

Настройка Firebase

Перейдите в Firebase и войдите в свою учетную запись Google. Потом:

  1. Создайте новый проект Firebase и назовите его SitePointBooks.
  2. Не включайте Google Analytics для этого проекта. Нажмите кнопку » Создать проект».
  3. В консоли Firebase создайте новое веб-приложениеи вызовите его sitepoint-books-app. Вы можете получить доступ к консоли Firebase, как показано на скриншоте ниже. В консоли Firebase создайте новое веб-приложениеи вызовите
  4. На следующем шаге дайте своему приложению имя (оно может совпадать с именем проекта), а затем нажмите » Зарегистрировать приложение».
  5. В разделе » Добавить Firebase SDK» выберите » Использовать npm» и скопируйте результат. Доступно большое количество SDK Firebase Доступно большое количество SDK Firebase.
  6. Наконец, запишите конфигурацию Firebase и нажмите » Продолжить» в консоли.

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

Для быстрой настройки вы можете просто скопировать предоставленный сценарий в firebase.jsфайл. Однако я предпочитаю сохранять настройки конфигурации Firebase с помощью.env.localфайла. Вот пример конфигурации Firebase:

VITE_API_FIREBASE_API_KEY=AIzaSyDfoP234E8waxeN8QZVrkA5LXqjjyPeFYs

VITE_API_FIREBASE_AUTH_DOMAIN=sitepointdemo-26ea0.firebaseapp.com

VITE_API_FIREBASE_PROJECT_ID=sitepointdemo-26ea0

VITE_API_FIREBASE_STORAGE_BUCKET=sitepointdemo-26ea0.appspot.com

VITE_API_FIREBASE_MESSAGING_SENDER_ID=292100755259

VITE_API_FIREBASE_FIREBASE_APP_ID=1:292100755259:web:38be20c9ab080b4ab1b11e

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

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

В настоящее время наша серверная часть Firebase пуста. На следующих шагах мы заполним его данными.

Облачное хранилище

Облачное хранилище Firebase — это сервис, который позволяет разработчикам хранить изображения, видео, аудио и другой пользовательский контент. В этом уроке мы будем использовать его только для хранения изображений. Перейдите на страницу Storage и нажмите кнопку Get Started. Появится всплывающий мастер. Просто примите правила по умолчанию и выберите место для корзины по умолчанию. После нажатия кнопки «Готово» в ближайшее время для вас будет создана корзина для хранения.

На следующей странице выполните следующие действия:

1. Создайте следующие папки:

  • categories
  • books

2. Загрузите изображения, которые я предоставил через этот zip-файл. У вас должна получиться следующая структура: агрузите изображения, которые я предоставил через этот zip-файл

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

rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {
    match /{allPaths=**} {
      allow read, write: if true;
    }
  }
}

Firebase использует собственный язык на основе Common Expression Language для определения правил безопасности. Из-за сложности обучения мы не сможем обсуждать это в этой статье. Ознакомьтесь с официальной документацией по этой теме.

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

Cloud Firestore

В качестве базы данных мы будем использовать Cloud Firestore, масштабируемую базу данных NoSQL, которая позволяет разработчикам структурировать данные с помощью коллекций. Чем старше Firebase в реальное время База данные также базы данных NoSQL, но он хранит все данные в плоской вложенной структуре JSON, который трудно запрос.

В консоли перейдите на страницу базы данных Firestore и нажмите кнопку » Создать базу данных». Должен появиться всплывающий мастер:

  1. На первой странице установите Запуск в тестовом режиме, чтобы разрешить небезопасный доступ к базе данных в течение следующих 30 дней.
  2. На следующей странице установите регион базы данных и нажмите кнопку » Включить«.

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

Вам также необходимо убедиться, что вокруг значения идентификатора

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

  • authorscollection, все типы полей являются строковыми:

Имя

Даррен Джонс

Майк Абоагье

  • categories collection, все типы полей являются строковыми:
Имя Крышка
javascript категории / javascript.png
питон категории / python.png
реагировать категории / react.png
  • books коллекции, все типы полей являются строковыми, кроме author_idи category_id. Вам придется вручную скопировать соответствующие уникальные идентификаторы ( place_id) в справочные поля, как показано на скриншоте выше:
Заголовок Научитесь кодировать с помощью JavaScript Наука о данных: инструменты и навыки
Крышка книги / научиться кодировать с javascript.jpg книги / наука о данных — tools & skills.png
author_id (ссылка) / авторы / {идентификатор места} / авторы / {идентификатор места}
category_id (ссылка) / категории / {идентификатор места} / категории / {идентификатор места}
Описание Это простое и увлекательное руководство — идеальное место для начала вашего пути к программированию. Вы будете учиться программировать с помощью JavaScript — самого популярного языка программирования на Земле, — но методы, которые вы освоите, дадут вам основу для дальнейшего использования и на других языках. Эта книга представляет собой сборник подробных руководств по некоторым инструментам, наиболее часто используемым в науке о данных, таким как Pandas и PySpark, а также обзор некоторых навыков, которые вам понадобятся как специалисту по данным.
URL https://www.sitepoint.com/premium/books/learn-to-code-with-javascript/ https://www.sitepoint.com/premium/books/data-science-tools-skills/

На снимке экрана ниже показан пример настройки структуры базы данных.

На снимке экрана ниже показан пример настройки структуры базы данных

Запуск сервера разработки

Теперь, когда база данных заполнена, мы можем выполнить npm run devи просмотреть, localhost:3000чтобы взаимодействовать с проектом. Обратите внимание, что это прототип приложения, созданный для обучения, и не все функции полностью реализованы.

Логика проекта Firebase

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

  • TailwindCSS и плагины : основной фреймворк CSS
  • HeadlessUI : небольшая коллекция нестилизованных компонентов пользовательского интерфейса
  • HeroIcons : коллекция иконок SVG, созданных вручную командой Tailwind CSS.
  • DaisyUI : библиотека компонентов TailwindCSS
  • React Hook Form : библиотека состояний формы
  • Yup : библиотека проверки форм
Читайте также:  Что такое UX и UI дизайн

Маршрутизация

Создание интерфейса CRUD для проекта, в котором задействовано более двух объектов, может быстро усложниться. Для маршрутизации я использовал React Router и реализовал структуру маршрутизации с использованием стандартизованного синтаксиса. То есть:

  • список маршрута: /{entity}
  • создать маршрут: /{entity}/create
  • изменить маршрут: /{entity}/edit/:id
  • подробный маршрут: /{entity}/:id

Вот упрощенное представление о том, как была реализована маршрутизация App.jsx:

import React from "react";
import { Route, Switch } from "react-router-dom";

// Layout components
import Footer from "@/layout/Footer";
import Navbar from "@/layout/Navbar";

// Screen(pages or views) containers
import Home from "@/screens/Home";
import NotFound from "@/screens/NotFound";
import ScreenBookList from "@/screens/book/List";
import ScreenBookForm from "@/screens/book/Form";
import ScreenBookDetail from "@/screens/book/Detail";

function App() {
  return (
    <div>
      <header>
        <Navbar />
      </header>
      <main>
        <Switch>
          <Route exact path="/">
            <Home />
          </Route>
          <Route exact path="/book">
            <ScreenBookList />
          </Route>
          <Route path="/book/edit/:id">
            <ScreenBookForm />
          </Route>
          <Route path="/book/detail/:id">
            <ScreenBookDetail />
          </Route>
          <Route path="/book/create">
            <ScreenBookForm />
          </Route>
          <Route component={NotFound} />
        </Switch>
      </main>
      <Footer />
    </>
  );
}

Обратите внимание, что ScreenBookFormон был повторно использован как для создания, так и для редактирования маршрутов. Позже вы увидите, как один контейнер формы можно использовать для обработки обоих вариантов использования. Далее мы посмотрим, как приложение React подключается к серверной части Firebase.

Служба базы данных

Для веб-приложений, мобильных приложений и приложений Node.js в вашем проекте должен быть установлен официальный пакет Firebase. Этот пакет содержит все инструменты, необходимые для подключения к различным внутренним службам Firebase:

npm install firebase

Чтобы подключиться к базе данных Cloud Firestore, вам необходимо определить следующее в firebase.js:

import firebase from "firebase/app";  // include the Firebase module
import "firebase/firestore"; // access firestore database service

const firebaseConfig = {
  apiKey: import.meta.env.VITE_API_FIREBASE_API_KEY,
  authDomain: import.meta.env.VITE_API_FIREBASE_AUTH_DOMAIN,
  projectId: import.meta.env.VITE_API_FIREBASE_PROJECT_ID,
  storageBucket: import.meta.env.VITE_API_FIREBASE_STORAGE_BUCKET,
  messagingSenderId: import.meta.env.VITE_API_FIREBASE_MESSAGING_SENDER_ID,
  appId: import.meta.env.VITE_API_FIREBASE_APP_ID,
};
// Initialize Firebase
const app = firebase.initializeApp(firebaseConfig);

export const db = app.firestore();
export default app;

Затем вы можете импортировать dbобъект в любой контейнер React и начать напрямую запрашивать базу данных. Я предпочитаю сначала создать файл services/DatabaseService.js, включающий всю необходимую логику CRUD:

import { db } from "@/firebase";

class DatabaseService {
  collection;

  // Specify 'authors', 'categories', or 'books' as collection name
  constructor(collectionName) {
    this.collection = db.collection(collectionName);
  }

  // returns list of records as an array of javascript objects
  getAll = async () => {
    const snapshot = await this.collection.get();
    return snapshot.docs.map((doc) => {
      return {
        id: doc.id, // append document id to each document
        ...doc.data(),
      };
    });
  };

  // returns a single document in object format
  getOne = async ({ queryKey }) => {
    const { id } = queryKey[1];
    if (!id) return; // entity form is in create mode
    const snapshot = await this.collection.doc(id).get();
    return snapshot.data();
  };

  // resolve a relation, returns the referenced document
  getReference = async (documentReference) => {
    const res = await documentReference.get();
    const data = res.data();

    if (data && documentReference.id) {
      data.uid = documentReference.id;
    }

    return data;
  };

  // save a new document in the database
  create = async (data) => {
    return await this.collection.add(data);
  };

  // update an existing document with new data
  update = async (id, values) => {
    return await this.collection.doc(id).update(values);
  };

  // delete an existing document from the collection
  remove = async (id) => {
    return await this.collection.doc(id).delete();
  };
}

// Create services for each entity type
export const AuthorService = new DatabaseService("authors");

export const CategoryService = new DatabaseService("categories");

export const BookService = new DatabaseService("books");

В приведенном выше коде есть два основных компонента:

  • DatabaseService Класс, который содержит логику CRUD — то есть, чтение ( getAll, getOne), создавать, обновлять и удалять.
  • Экземпляры службы базы данных для каждого из типов коллекций, с которыми мы работаем, то есть books, categoriesи authors. Мы будем использовать это в компонентах контейнера (экрана) для взаимодействия с нашей серверной частью Firebase.

Некоторые дополнительные примечания для DatabaseServiceкласса:

  • Для getAllметода, когда вы вызываете data.doc()метод, вы получаете только значения данных без идентификатора объекта. Чтобы исправить это, нам нужно вызвать doc.idи объединить его с остальными значениями. Это необходимо для того, чтобы операции обновления и удаления работали.
  • Я объясню эту getReferenceфункцию позже в разделе «Устранение взаимосвязей между документами».
  • Для остальных функций обратитесь к встроенным комментариям и документации Firestore для получения дополнительной информации.

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

Список документов

Теперь, когда у нас настроена служба базы данных, нам нужно вызвать ее из контейнера, например ScreenAuthorList. Как только данные будут получены, они будут переданы через реквизиты компоненту презентации, то есть AuthorList.

Чтобы управлять данными сервера в состоянии нашего интерфейсного приложения, мы будем использовать React Query. Использовать этот пакет намного проще, чем настраивать Redux или любое другое внешнее решение для управления состоянием. Вот упрощенная версия ScreenAuthorList.jsx, демонстрирующая эту концепцию в действии:

import React from "react";
import { useQuery } from "react-query";

import { AuthorService } from "@/services/DatabaseService";
import PageHeading from "@/components/ui/PageHeading";
import AuthorList from "@/components/author/List";

function ScreenAuthorList() {
  const { data, status } = useQuery("authors", AuthorService.getAll);

  return (
    <>
      <PageHeading title="Author List" />
      <div>{status === "success" && <AuthorList data={data} />}</div>
    </>
  );
}

export default ScreenAuthorList;

А вот упрощенная версия AuthorList.jsx, которая просто принимает данные и отображает их в виде таблицы:

import React from "react";
import { Link } from "react-router-dom";

function AuthorList({ data }) {
  return (
    <div>
      <table>
        <thead>
          <tr>
            <th>Name</th>
          </tr>
        </thead>
        <tbody>
          {data.map((author, index) => (
            <tr key={index}>
              <td>{author.name}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

export default AuthorList;

Обратите внимание, что я исключила editи delet eкнопки, которые мы рассмотрим в следующем.

Удаление документов с диалоговым окном подтверждения

Кнопка Удалить в AuthorListкомпоненте определяется следующим образом:

<table>
  ...
  <tbody>
    <tr>
      ...
      <td>
        <button
          title={`Delete ${author.name}`}
          onClick={() => showDeleteModal(author.id)}
        >
          <TrashIcon />
        </button>
      </td>
    </tr>
</table>

Давайте посмотрим, как showDeleteModal(id) функция определяется внутри компонента:

import React, { useState } from "react";

function AuthorList({ data, deleteAction }) {
  const [selected, setSelected] = useState(); // set author.id for deletion
  const [openModal, setOpenModal] = useState(false); // show or hide DeleteModal

  const showDeleteModal = (id) => {
    setSelected(id);
    setOpenModal(true);
  };

  const deleteModalAction = () => {
    deleteAction(selected);
    setOpenModal(false);
  };

  const cancelModalAction = () => {
    setOpenModal(false);
  };

  return (
    <div>
      <DeleteModal
        open={openModal}
        deleteAction={deleteModalAction}
        cancelAction={cancelModalAction}
      />
      <table>// delete button is here</table>
    </div>
  );
}

Обычно при showDeleteModal(id) вызове функции происходит следующее:

  • selected состояние устанавливается на токauthor.id
  • диалоговое окно подтверждения становится видимым

Диалог подтверждения, он же DeleteModal.jsxслишком подробный, чтобы отображать здесь полный код. Для простоты я использовал псевдоязык, чтобы определить его структуру, чтобы сделать ее более читаемой:

function DeleteModal({ isOpen, deleteAction, cancelAction }) {
  return (
    <Modal.Root show={isOpen}>
      <modal-content>
        <p>
          {" "}
          Are you sure you want to permanently remove this record forever?{" "}
        </p>
      </modal-content>
      <modal-footer>
        <button onClick={deleteAction}>Delete</button>
        <button onClick={cancelAction}>Cancel</button>
      </modal-footer>
    </Modal.Root>
  );
}

cancelAction Функция будет просто скрыть диалоговое окно подтверждения. deleteActionФункция будет вызывать обработчик базы данных, ответственный за выполнение фактического удаления документа. Этот обработчик определяется на уровне контейнера ScreenAuthorList.jsx. Ниже представлена ​​упрощенная версия кода:

import { useMutation, useQueryClient } from "react-query";

function ScreenAuthorList() {
  const queryClient = useQueryClient();

  const deleteMutation = useMutation((id) => AuthorService.remove(id), {
    onSuccess: () => {
      queryClient.invalidateQueries("authors");
    },
  });

  const deleteAction = async (id) => {
    deleteMutation.mutateAsync(id);
  };

  return (
    <>
      <AuthorList data={data} deleteAction={deleteAction} />
    </>
  );
}

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

Создание и обновление документов

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

Во- первых, мы должны смотреть на Createи Editкнопки, которые перенаправляет пользователей на ScreenAuthorFormстраницу. Это делается в AuthorList.jsxкомпоненте:

import { Link } from "react-router-dom";
import { UserCircleIcon, PencilAltIcon } from "@heroicons/react/outline";

function AuthorList() {
  return (
    <div>
      <div>
        <Link to="/author/create">
          <UserCircleIcon />
          New Author
        </Link>
      </div>
      <table>
        ...
        <td>
          <Link to={`/author/edit/${author.id}`} title={`Edit ${author.name}`}>
            <PencilAltIcon />
          </Link>
        </td>
        ...
      </table>
    </div>
  );
}

ScreenAuthorFormКонтейнер предназначен для обработки как создавать и обновлять использование автора случаев. В случае обновления нам нужно получить idиз URL-адреса, а затем использовать его для получения документа для нашей базы данных Firebase. Для создания мы просто визуализируем форму без передачи каких-либо значений:

import { useParams } from 'react-router-dom'

function ScreenAuthorForm() {
  const { id } = useParams() // retrieve id from url parameters
  // fetch document
  const { data, isLoading, error, status } = useQuery(
    ['author', { id }],
    AuthorService.getOne
  )

  // Render create form
  if (!id) {
    return (
      <>
        <PageHeading title="Create Author" />
        <AuthorForm submit={onSubmit} />
      </>
    )
  }

  // Render update form
  return (
    <>
      <PageHeading title="Edit Author" />
      <AuthorForm values={data} submit={onSubmit} />
    <>
  )
}

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

import React, { useState, useEffect } from "react";
import { useForm } from "react-hook-form";

function AuthorForm({ values, submit }) {
  // initialize react-hook-form
  const { register, reset, handleSubmit } = useForm();

  // populate form fields
  useEffect(() => {
    reset(values);
  }, [values]);

  // call container submit handler to save new/updated values
  const onSubmit = (submittedData) => {
    submit(submittedData);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div className="form-control">
        <label>Name</label>
        <input type="text" {...register("name")} />
      </div>

      <div className="form-footer">
        <button type="submit"> Save </button>
        <Link to="/author"> Cancel </Link>
      </div>
    </form>
  );
}

Встроенные комментарии должны объяснять основные разделы кода. Обратитесь к React Крюк форме в документации о том, как библиотеки работ. Мы не рассматривали submitфункцию, поэтому давайте сделаем это сейчас в ScreenAuthorFormконтейнере:

import { useParams, Redirect } from 'react-router-dom'
import { useQuery, useMutation, useQueryClient } from 'react-query'

function ScreenAuthorForm() {
  const { id } = useParams()
  const queryClient = useQueryClient()

  // call the database service to create or update document depending on presence of id
  const saveData = (data) => {
    if (id) {
      return AuthorService.update(id, data)
    } else {
      AuthorService.create(data)
    }
  }

  // create mutation
  const mutation = useMutation((data) => saveData(data), {
    onSuccess: () => {
      if (id) queryClient.invalidateQueries(['author', { id }])
    },
  })

  // track mutation status i.e. return true after successful mutation
  const { isSuccess } = mutation

  // define submit action handler to be passed down as prop to AuthorForm
  const onSubmit = async (submittedData) => {
    mutation.mutate(submittedData)
  }

  // if mutation is successful, redirect to ScreenAuthorList
  if (isSuccess) {
    return <Redirect to="/author" />
  }

  // render create and update form
  return (
    ...
    <AuthorForm submit={onSubmit} />
    ...
  )
  ...
}

Встроенные комментарии должны объяснять, что делает каждый блок кода. Обратитесь к документации по изменениям React Query, чтобы понять, как это работает. В следующем разделе мы рассмотрим, как отображать изображения, хранящиеся в облачном хранилище Firebase.

Читайте также:  Векторные итераторы C++

Отображение изображений

В этом разделе мы будем использовать CategoryCardдля демонстрации рендеринга изображений.

Напоминаем, что вот пример данных категории:

{
  "name": "javascript",
  "cover": "categories/javascript.png"
}

Если вы перейдете к одному из изображений, загруженных в облачное хранилище, вы увидите URL-ссылку в следующем формате:

gs://<app id>.appspot.com/<folder>/<filename>

Эта ссылка не может быть обработана браузером. Его необходимо преобразовать в ссылку для загрузки в формате HTTP. Для этого нам нужно импортировать пакет, который позволяет нашему приложению взаимодействовать со службой хранилища Firebase. Это делается в firebase.js:

...
import 'firebase/storage'

...
export const storage = app.storage()

Затем мы можем импортировать storageэкземпляр и определить функцию, выполняющуюся при этом преобразовании. Это было сделано в StorageService.js:

import { storage } from "../firebase";

const storageRef = storage.ref(); // access the default bucket

// accepts file path in the format `folder/filename.ext`
const getImageURL = async (filePath) => {
  const url = await storageRef.child(filePath).getDownloadURL();
  return url;
};

const StorageService = {
  getImageURL,
};

export default StorageService;

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

import React, { useEffect, useState } from "react";
import { Link } from "react-router-dom";
import StorageService from "../../services/StorageService";

function CategoryCard({ category }) {
  const [imageLink, setImageLink] = useState();

  // download the image link
  useEffect(async () => {
    const url = await StorageService.getImageURL(category.cover);
    setImageLink(url);
  }, [category]);

  return (
    <div>
      <Link to={`/category/edit/${category.id}`}>
        <img src={imageLink} alt={category.name} />
      </Link>
    </div>
  );
}

export default CategoryCard;

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

Листинг файлов

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

При работе с изображениями мы можем предоставить

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

// input: folder name
// output: list of fileNames in array format
const listFiles = async (folder) => {
  const listRef = storageRef.child(folder);
  const res = await listRef.listAll();
  const list = res.items.map((itemRef) => itemRef._delegate._location.path_);
  return list;
};

const StorageService = {
  ...listFiles,
};

С listFilesфункцией, определенной, теперь мы можем назвать его из CategoryForm компонента:

import React, { useState, useEffect } from "react";
import StorageService from "../../services/StorageService";

function CategoryForm({ values, action }) {
  const [coverOptions, setCoverOptions] = useState([]);

  // Get list of available images from cloud storage
  useEffect(async () => {
    const availableFiles = await StorageService.listFiles("categories");
    setCoverOptions(availableFiles);
  }, []);

  return (
    <form>
      ...
      <div className="form-control">
        <label>Select Cover</label>

        <select {...register("cover")}>
          <option disabled="disabled" value="nocover">
            Choose a cover
          </option>
          {coverOptions.map((fileName, index) => (
            <option key={index} value={fileName}>
              {fileName}
            </option>
          ))}
        </select>
      </div>
      ...
    </form>
  );
}

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

Разрешение документальных отношений

Если вспомнить bookструктуру сущности, она содержала справочные поля с именами author_idи category_id. Для большинства систем баз данных и библиотек ORM существует возможность заполнения ссылок значениями, так что для загрузки всех требуемых данных требуется только один запрос.

К сожалению, для базы данных Firestore вам необходимо выполнить дополнительные запросы для загрузки в ссылочные документы. Нам нужно определить специальную функцию для этого в DatabaseService.js:

class DatabaseService {
  ...
  getReference = async (documentReference) => {
    const res = await documentReference.get()
    const data = res.data()

    if (data && documentReference.id) {
      data.uid = documentReference.id
    }

    return data
  }
  ...
}

Определив функцию, мы теперь можем полностью загрузить документ со ссылочными полями. См. BookDetailКомпонент в качестве примера:

import { BookService } from "@/services/DatabaseService";

function BookDetail({ book }) {
  const [author, setAuthor] = useState();
  const [category, setCategory] = useState();

  // Resolve book.author_id document reference
  useEffect(async () => {
    const authorRef = await BookService.getReference(book.author_id);
    setAuthor(authorRef);
  }, [book]);

  // Resolve book.category_id document reference
  useEffect(async () => {
    const categoryRef = await BookService.getReference(book.category_id);
    setCategory(categoryRef);
  }, [book]);

  return (
    <div>
      ...
      {category && <p>{category.name}</p>}
      ...
      {author && <p>By {author.name}</p>}
      ...
    </div>
  );
}

В приведенном выше примере мы используем асинхронные useEffectперехватчики для выполнения дополнительных запросов. В следующем разделе мы начнем завершать статью.

Другие сервисы Firebase

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

  • Аутентификация. Эта служба позволяет легко добавлять функции входа в приложение Firebase. Он поддерживает электронную почту, учетные записи в социальных сетях, GitHub и даже методы аутентификации по SMS. Firebase auth тесно интегрируется с другими сервисами Firebase и может быть легко интегрирована с вашим пользовательским сервером.
  • Облачные функции. Это служба, которая позволяет вам писать и выполнять внутренний код в ответ на события, запускаемые функциями Firebase и запросами HTTPS. Код написан на JavaScript / TypeScript и работает в управляемой среде Google Cloud.
  • Хостинг. Это сервис, который обеспечивает размещение веб-приложений, статического и динамического контента, а также микросервисов. Контент обслуживается через глобальную CDN (сеть доставки контента).
  • Аналитика. Вы можете использовать Google Analytics для сбора данных об использовании и поведении вашего веб-приложения черезfirebase/analyticsпакет. Вы можете собирать и отслеживать события и атрибуты пользователей (например, язык, географический язык) вашей аудитории.

Как упоминалось ранее, настроенные нами правила безопасности разрешают общедоступный доступ для чтения / записи к нашей серверной части. Чтобы узнать, как защитить свою учетную запись Firebase, я рекомендую ознакомиться с правилами безопасности. Обратите внимание, что вы также должны реализовать аутентификацию Firebase в своем приложении, чтобы обеспечить безопасный доступ к данным.

Заключение

Подводя итог, вы научились:

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

Есть еще так много сервисов Firebase, которые мы еще не коснулись. Как видите, проще создать MVP, когда все серверные сервисы находятся под одной крышей. Мы установили только одну библиотеку Firebase, которая обеспечивает большинство внутренних функций, которые нужны большинству MVP.

Оцените статью
bestprogrammer.ru
Добавить комментарий