Challenge UNCHAIN vol.9 – 急がば回れ –

さて、前回は「Astar-SocialFi」のSection 0とSection 1を進めてみたので、今回もできるところまでやってみようと思う。

ということで、Section 2。

No Title

ここからはフロントエンドを開発していきましょう! 本教材の主眼はコントラクトの作成なので、UI部分の解説は簡略化していることをご了承ください 😔 まずはフロントエンドの開発に必要なライブラリをインポートしましょう。次のコマンドをターミナルで実行してください。 次に環境変数として必要な値を登録しましょう。 ディレクトリ構造において一番上階層( pagesと同じ階層)に.env ファイルを作成して、内容を下のように記述しましょう。 ここではコントラクトアドレスとまだプロフィールを作成していないユーザーのプロフィール画像のURLでそれぞれ NEXT_PUBLIC_CONTRACT_ADDRESS, NEXT_PUBLIC_UNKNOWN_IMAGE_URL という名前で登録することになります。 取得したコントラクトアドレスをcontract_addressへ代入します(こちら動作確認のためにデプロイした際に行います)。 unknown_imageにはユーザーがデフォルトで設定するプロフィールの画像のURLを記述してください。 ここで注意点ですが、今回使用できるのは こちらにある画像のみです。また、 NEXT_PUBLIC_UNKNOWN_IMAGE_URLに代入するURLは下のように https から全てのURLを記述しましょう! 下のように代入してみてください。 次にさきほどコントラクトを実装して後に更新した metadata.jsonを一番上の階層( pages と同じ階層)に加えましょう。 次に linter の設定をしましょう。linterをきちんと設定しないと無駄な部分にエラーが発生してアプリのデプロイ時に問題があるように写りデプロイができないようになります。 一番上の階層( pagesと同じ階層)に.eslintrc.json ファイルがあるので下のように編集しましょう。 [ eslintrc.json] その後コードのエディター(vscodeなど)を一度閉じて、再度開いてみましょう。きちんとlinterが動くようになります。 次に環境構築で作成したプロジェクトの一番上の階層に hooksと components というディレクトリを作成してください。 その後 pages、 hooks、 components を下のようなディレクトリ構造にしてください。 [ components] これらがUIを構成するパーツやフロントで使用する関数を含んだものになります。 では順番にそれぞれのディレクトリについて説明して行きます。 pages にはそれぞれの画面全体を記述することになります。ファイルの名前(.tsxを除く)が直接URLの名前となるので、URLがそのファイル名変更されるとそのページが表示されることになります。 hooksにはコントラクトで作成した関数をフロントで使用できるようにしたものが記述されます。ウォレットと接続するための関数は connect.tsx に記述することになり、他のファイルにはコントラクトの関数が記述することになります。 componentsにはUIで使用するパーツを記述して行きます。今回のプロジェクトでは アトミックデザイン という考え方を採用しており、完全ではありませんがこの考え方に沿って構成されています。 アトミックデザインとはアプリを構成するパーツの単位を5つの段階に分けるというもので Atoms(原子), Molecules(分子), Organism(生体), Template(テンプレート), Pages(ページ)に分けられ Atoms(原子)に近いほど小さく、 Pages に近いほど大きなパーツになります。 今回はTemplate以外のものを採用して作成しています。詳しくは こちら の記事を参考にしてください。 こんなことをしたらファイルが多すぎて逆にわかりにいのでは? と思われる方もいるかもしれないんですが、特に Atoms(原子) などの小さい単位のものは使い回しができるので重複してコードを書く必要はありませんし、また何処かでエラーが発生した場合にどのパーツでエラーが発生しているか追っていけばいいだけなので対処しやすいというメリットがあります。 次に、フロントエンドで使用する画像をプロジェクトの中に入れておきましょう。 下の画像を添えられている名前の通りに、ディレクトリ構造の一番上の階層にある public に保存してください。 [ Astar_logo.png] [ cross_mark_2_logo-removebg.png] [ cross_star_2_logo-removebg.png] [ cross_star_6_logo-removebg.png] [ …

Lesson 1. フロントエンド環境整備/hooks(コントラクト呼び出し)

環境整備

react-iconsreact-modalを使うようなので、npmインストール。

react-iconsはかなりの種類のアイコンを扱っていて、かつLicenseについても記載があるので今後も使えそう。

React Icons

Include popular icons in your React projects easily with react-icons, which utilizes ES6 imports that allows you to include only the icons that your project is using. npm install react-icons –save import { FaBeer } from ‘react-icons/fa’ ; class Question extends React . Component { If your project grows in size, this option is available.

NEXT_PUBLIC_CONTRACT_ADDRESS=contract_address
NEXT_PUBLIC_UNKNOWN_IMAGE_URL=unknown_image

.envファイルを上記で記載。

ESLintの設定を変更。

ファイル構成

  • src
    • pages: ファイル名とURLを一致させて呼び出すようにする
    • hooks: コントラクトで作成した関数を使用できるようにする
    • components: アトミックデザインを採用して、重複が少なく、再利用しやすくする

ReactのUIのベストプラクティスを知りたかったので、アトミックデザインの考え方はかなり参考になった。

React の Component 設計と Atomic Design

DROBE では React のコンポーネント設計に Atomic Design を取り入れていますが、特にデザインにおける設計と React における実装という目的の違いから、実装していると微妙に概念が取り入れ辛く、しばしば方針に迷う事があります

結局、サーバサイドでも出てくるようなレイヤーの問題にぶち当たるのは目に見えているけど、考え方としては非常に大事な気はする。

静的ファイルについては、注意が必要で、next.config.jsimagesを変更する必要があるとのこと。

next/image | Next.js

Note: This page is the API reference for the next/image component. For a feature overview and usage information, please see the Image Component and Image Optimization documentation. Note: If you are using a version of Next.js prior to 13, you’ll want to use the next/legacy/image documentation since the component was renamed.

今回はdomainsを使っていたが、

Note: We recommend using remotePatterns instead so you can restrict protocol and pathname.

を見るかぎり、remotePatternsを使った方が良さそう。

hooks

ここで重要なのはconnect.tsx

import { ApiPromise, WsProvider } from "@polkadot/api";
import { InjectedAccountWithMeta } from "@polkadot/extension-inject/types";
import { Dispatch } from "react";

type Props = {
  api: ApiPromise | undefined;
  accountList: InjectedAccountWithMeta[];
  actingAccount: InjectedAccountWithMeta;
  isSetup: boolean;
  setApi: Dispatch<React.SetStateAction<ApiPromise | undefined>>;
  setAccountList: Dispatch<React.SetStateAction<InjectedAccountWithMeta[]>>;
  setActingAccount: Dispatch<
    React.SetStateAction<InjectedAccountWithMeta | undefined>
  >;
  setIsSetup: React.Dispatch<React.SetStateAction<boolean>>;
};

export const connectToContract = async (props: Props) => {
  const blockchainUrl = "ws://127.0.0.1:9944";

  const extensionSetup = async () => {
    const { web3Accounts, web3Enable } = await import(
      "@polkadot/extension-dapp"
    );
    const extensions = await web3Enable("Polk4NET");
    if (extensions.length === 0) {
      return;
    }
    const accounts = await web3Accounts();
    props.setAccountList(accounts);
    props.setActingAccount(accounts[0]);
    props.setIsSetup(true);
  };

  const wsProvider = new WsProvider(blockchainUrl);
  const connectedApi = await ApiPromise.create({ provider: wsProvider });
  props.setApi(connectedApi);
  await extensionSetup();
};

blockchainUrl以外は環境依存しているところはなく、外出しにしてやれば、どこでも使えそう。

上記では、Polkadotのいくつかのライブラリ(@polkadot/api, @polkadot/extension-inject, @polkadot/extension-dapp)を利用しているので、npmインストール。

その他のファイル(messageFunction.tsx, ftFunction.tsx, postFunction.tsx, profileFunction.tsx)については、コントラクト側と対になった記載方法と思われるので、できれば自動生成ができるようになるとありがたいところ。

  • 対象のオブジェクトの構造体
  • 各ファンクションの引数となる構造体
  • 各ファンクション

を記載すれば良さそう。

export type MessageType = {
  message: string;
  senderId: string;
  createdTime: string;
};

messageFunction.tsxでは、メッセージが対象のオブジェクトとなるので、MessageTypeという構造体をexport付きで宣言。

type PropsGLM = {
  api: ApiPromise | undefined;
  id: number;
};

export const sendMessage = async (props: PropsSM) => {
  const { web3FromSource } = await import("@polkadot/extension-dapp");
  const contract = new ContractPromise(props.api!, abi, contractAddress);
  const performingAccount = props.actingAccount;
  const injector = await web3FromSource(performingAccount.meta.source);
  const date = new Date();
  const add_likes = await contract.tx.sendMessage(
    {
      value: 0,
      gasLimit: 18850000000,
    },
    props.message,
    props.id,
    [date.getMonth() + 1, date.getDate()].join("-") +
      " " +
      [
        date.getHours().toString().padStart(2, "0"),
        date.getMinutes().toString().padStart(2, "0"),
      ].join(":")
  );
  if (injector !== undefined) {
    add_likes.signAndSend(
      performingAccount.address,
      { signer: injector.signer },
      (result) => {}
    );
  }
};

更新系について見た感じは、

  • ファンクションの引数となる構造体を作る
  • ファンクションを実装する
    • コントラクトを呼び出す
    • ガス代の調整

くらい。

※ガス代の調整については、polkadot.jsのデプロイしたコントラクトの各ファンクションを確認すると表示される。

type PropsGML = {
  api: ApiPromise | undefined;
  id: number;
};

export const getMessageList = async (props: PropsGML) => {
  const contract = new ContractPromise(props.api!, abi, contractAddress);
  const { gasConsumed, result, output } = await contract.query.getMessageList(
    "",
    {
      value: 0,
      gasLimit: -1,
    },
    props.id,
    1
  );
  if (output !== undefined && output !== null) {
    return output;
  }
  return [];
};

参照系についてみた感じは、

  • ファンクションの引数となる構造体を作る
  • ファンクションを実装する
    • コントラクトを呼び出す
    • outputを調整する ※上記例ではそのまま返しているだけ

くらい。

ここでもPolkadotのライブラリ(@polkadot/api-contract)を利用しているので、npmインストール。

コントラクト側で作成されたmetadata.jsonswankyで作成したらastar_sns_contract.jsonという名前だった)をimportできるところに配置。このファイルにABIが記載されており、プログラム中はabiだけimportされている。

ファイル名がxxFunction.tsxと「Function」がついていることだけ気になった・・・

Lessson 2. UIコンポーネント

アトミックデザインの実装がどうなるかが非常に気になるが、とりあえず、React.FCの方が気になったので調べてみた。

【検証】React.FC と React.VFC はべつに使わなくていい説

こんにちは、クレイの正岡です。 コロナ禍が始まってから小学生時代以来のゲーム生活を送っています。ゲームボーイと呼んでください。 さて、今回は React × Typescript でコードを書いている人/書こうとしている人に向けて、Reactコンポーネントの型定義について頭の片隅に置いておいて欲しい情報を共有したいと思います。 interface Props { text: string } const Hoge = ({ text }: Props) => { return ( { text } ) } 上記のように返り値の型を特に指定していない場合、 このコンポーネントは JSX.Element型 を返す関数( () => JSX.Element )として返ります。 一方、React( v17時点 )には React.FunctionComponent(= React.FC) 型と React.VoidFunctionComponent (= React.VFC)型が用意されています。 この記事では、以下、便宜上それぞれ FC と VFC

使わない方がいいんじゃないか説があるのでもう少し検討しないと。

アトミックデザイン方針のcomponents配下をざっと見たところ、数多くのサイト制作やアプリケーションUIを手掛けてきた身としては、ちょっとやりすぎ感がある。おそらく個人的な達成感はあるだろうけど、チーム開発としてはしんどそう。

atom(原子)レベルまで落とし込む必要がどこまであるかというところ。

要するに、どこまで共通化を考える必要があるかというところ。

で、最後にはプログラミングの好みの話にまでなってしまう。

どのくらいUIの変更があったり、UXの変更を考えていくかなど、運用要件も絡んできそう。

ということで、アトミックデザインは考えについては自分の中に落とし込みつつ、

  • Lv3. Organisms
  • Lv4. Templates
  • Lv5. Pages

の3レイヤーくらいじゃないのかなぁと。

Lv3は「Parts」として、共通利用していいように実装していた気がする。

Lesson 3. ページ

index.tsxでさっそく気になったのが「Home」。

ホームはページもあるから、「Index」の方がいい気がする。。。

あとは、、、各種ページで同じような処理があるので共通化できないものか。

基本的には、

  • これまで作ってきたUIコンポーネントを組み合わせてページを作っていく
  • コントラクトを呼び出していく

という感じ。

いざ、動作確認へ!!!!

・・・

だめだこれは・・・

ソースのコピペでは動かない・・・

全体理解のために綺麗に作り直そう。。。

まとめ

Next.js初挑戦ということもあったが、ソースコピペで動いてくれないかな、という甘い考えが崩壊した。。。

一歩ずつ進むのが大事なので、しっかり復習しながらやっていこう。

急がば回れ

本日の教訓。