Как разработать децентрализованное приложение на блокчейне с помощью React и Juno.

Первоначально опубликовано на веб-сайте Juno: https://juno.build/blog/build-a-web3-app-with-react-js

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

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

В этом сообщении блога мы рассмотрим, как объединить React и Juno для разработки dApp. Итак, давайте погрузимся и узнаем, как Juno может помочь вам создавать мощные и удобные децентрализованные приложения!

Как работает Юнона

Juno — это платформа Blockchain-as-a-Service с открытым исходным кодом. Он работает так же, как традиционные бессерверные платформы, такие как Google Firebase или AWS Amplify, но с одним ключевым отличием: все в Juno работает на блокчейне. Это означает, что вы получаете полностью децентрализованную и безопасную инфраструктуру для своих приложений, что, на мой взгляд, довольно круто.

За кулисами Juno использует сеть и инфраструктуру блокчейна Интернет-компьютер для запуска того, что мы называем сателлитом для каждого создаваемого вами приложения. Сателлит — это, по сути, смарт-контракт на стероидах, который содержит все ваше приложение. От своих активов, предоставленных в Интернете (таких как JavaScript, HTML и файлы изображений), до своего состояния, сохраненного в сверхпростой базе данных, файлового хранилища и аутентификации, каждый Satellite, управляемый исключительно вами, содержит все необходимое для бесперебойной работы.

Создайте свое первое децентрализованное приложение

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

Инициализация

Прежде чем вы сможете интегрировать Juno в свое приложение ReactJS, вам необходимо создать спутник. Этот процесс подробно описан в документации.

Кроме того, вам также необходимо установить SDK.

npm i @junobuild/core

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

import { useEffect } from "react";
import { initJuno } from "@junobuild/core";

function App() {
  useEffect(() => {
    (async () =>
      await initJuno({
        satelliteId: "pycrs-xiaaa-aaaal-ab6la-cai",
      }))();
  }, []);

  return (
      <h1>Hello World</h1>
  );
}

export default App;

Вот и все для конфигурации! Теперь ваше приложение готово для Web3! 😎

Аутентификация

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

import { signIn, signOut } from "@junobuild/core";

<button
  type="button"
  onClick={signIn}
>Login</button>

<button
  type="button"
  onClick={signOut}
>Logout</button>

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

Чтобы наблюдать за этой записью и, соответственно, узнавать состояние пользователя, Juno предоставляет наблюдаемую функцию под названием authSubscribe(). Вы можете использовать его столько раз, сколько потребуется, но я считаю удобным подписаться на него в верхней части приложения. Таким образом, мы можем создать Context для распространения пользователя.

import { createContext, useEffect, useState } from "react";
import { authSubscribe } from "@junobuild/core";

export const AuthContext = createContext();

export const Auth = ({ children }) => {
  const [user, setUser] = useState(undefined);

  useEffect(() => {
    const sub = authSubscribe((user) => setUser(user));

    return () => unsubscribe();
  }, []);

  return (
    <AuthContext.Provider value={{ user }}>
      {user !== undefined && user !== null ? (
        <div>
          {children}
        </div>
      ) : (
        <p>Not signed in.</p>
      )}
    </AuthContext.Provider>
  );
};

Библиотека Juno не зависит от фреймворка и в настоящее время не содержит кода, специфичного для фреймворка. Тем не менее, мы приветствуем вклад сообщества. Если вы заинтересованы в предоставлении плагинов React, контекстов, хуков или чего-то еще, не стесняйтесь вносить свой вклад в проект! 💪

Хранение документов

Хранение данных в блокчейне с помощью Juno осуществляется с помощью функции под названием «Хранилище данных». Хранилище данных состоит из списка коллекций, содержащих ваши документы, каждая из которых идентифицируется заданным вами текстовым ключом.

В этом уроке наша цель — хранить заметки. Для этого вам нужно будет, следуя инструкциям в документации, создать коллекцию, которую можно назвать соответствующим образом (заметки).

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

import { setDoc } from "@junobuild/core";

// TypeScript example from the documentation
await setDoc<Example>({
  collection: "my_collection_key",
  doc: {
    key: "my_document_key",
    data: myExample,
  },
});

Поскольку документы в коллекции идентифицируются уникальным ключом, мы создаем ключи с помощью nanoid — крошечного генератора строковых идентификаторов для JavaScript.

import { useState } from "react";
import { setDoc } from "@junobuild/core";
import { nanoid } from "nanoid";

export const Example = () => {
  const [inputText, setInputText] = useState("");

  const add = async () => {
    await setDoc({
      collection: "data",
      doc: {
        key: nanoid(),
        data: {
          text: inputText,
        },
      },
    });
  };

  return (
    <>
      <textarea
        onChange={(e) => setInputText(e.target.value)}
        value={inputText}
      ></textarea>

      <button type="button" onClick={add}>
        Add
      </button>
    </>
  );
};

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

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

В этом уроке мы будем использовать минимальный пример. Мы просто перечисляем все данные пользователей, соблюдая Context, которые мы объявили ранее. Если пользователь установлен, мы извлекаем данные; если нет, мы сбрасываем записи. Это возможно, потому что каждый раз, когда пользователь входит в систему или выходит из нее, состояние будет автоматически отражаться.

import { useContext, useEffect, useState } from "react";
import { AuthContext } from "./Auth";
import { listDocs } from "@junobuild/core";

export const ListExample = () => {
  const { user } = useContext(AuthContext);

  const [items, setItems] = useState([]);

  const list = async () => {
    const { items } = await listDocs({
      collection: "notes",
      filter: {},
    });

    setItems(items);
  };

  useEffect(() => {
    if ([undefined, null].includes(user)) {
      setItems([]);
      return;
    }

    (async () => await list())();
  }, [user]);

  return (
    <>
      {items.map(({ key, data: { text } }) => (
        <p key={key}>{text}</p>
      ))}
    </>
  );
};

Загрузка файла

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

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

Сохраненные данные идентифицируются уникальными именами файлов и путями. Это связано с тем, что данные предоставляются в Интернете, и поэтому каждый фрагмент данных должен соответствовать уникальному URL-адресу.

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

import { useContext, useState } from "react";
import { AuthContext } from "./Auth";
import { uploadFile } from "@junobuild/core";

export const UploadExample = () => {
  const [file, setFile] = useState();
  const [image, setImage] = useState();

  const { user } = useContext(AuthContext);

  const add = async () => {
    const filename = `${user.key}-${file.name}`;

    const { downloadUrl } = await uploadFile({
      collection: "images",
      data: file,
      filename,
    });

    setImage(downloadUrl);
  };

  return (
    <>
      <input
        type="file"
        accept="image/png, image/gif, image/jpeg"
        onChange={(event) => setFile(event.target.files?.[0])}
      />

      <button type="button" onClick={add}>
        Add
      </button>

      {image !== undefined && <img src={image} loading="lazy" /> }
    </>
  );
};

После загрузки ресурса возвращается downloadUrl, который предоставляет прямую ссылку HTTPS для доступа к загруженному ресурсу в Интернете.

Список активов

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

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

import { useContext, useEffect, useState } from "react";
import { AuthContext } from "./Auth";
import { listAssets } from "@junobuild/core";

export const ListAssetsExample = () => {
  const { user } = useContext(AuthContext);

  const [assets, setAssets] = useState([]);

  const list = async () => {
    const { assets } = await listAssets({
      collection: "images",
      filter: {},
    });

    setAssets(assets);
  };

  useEffect(() => {
    if ([undefined, null].includes(user)) {
      setAssets([]);
      return;
    }

    (async () => await list())();
  }, [user]);

  return (
    <>
      {assets.map(({ fullPath, downloadUrl }) => (
        <img key={fullPath} loading="lazy" src={downloadUrl} />
      ))}
    </>
  );
};

Развертывание 🚀

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

npm i -g @junobuild/cli

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

juno login

Наконец, вы можете развернуть свой проект с помощью следующей команды:

juno deploy

Поздравляем! Ваше приложение теперь децентрализовано 🎉.

Ресурсы

👋

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

И, если вы дочитали до этого места, вам обязательно стоит присоединиться к Юноне в Discord. 😉