さて、前回は「Astar-SocialFi」のSection 0とSection 1を進めてみたので、今回もできるところまでやってみようと思う。
ということで、Section 2。
Lesson 1. フロントエンド環境整備/hooks(コントラクト呼び出し)
環境整備
react-icons
とreact-modal
を使うようなので、npmインストール。
react-icons
はかなりの種類のアイコンを扱っていて、かつLicenseについても記載があるので今後も使えそう。
NEXT_PUBLIC_CONTRACT_ADDRESS=contract_address
NEXT_PUBLIC_UNKNOWN_IMAGE_URL=unknown_image
.env
ファイルを上記で記載。
ESLintの設定を変更。
ファイル構成
- src
- pages: ファイル名とURLを一致させて呼び出すようにする
- hooks: コントラクトで作成した関数を使用できるようにする
- components: アトミックデザインを採用して、重複が少なく、再利用しやすくする
ReactのUIのベストプラクティスを知りたかったので、アトミックデザインの考え方はかなり参考になった。
結局、サーバサイドでも出てくるようなレイヤーの問題にぶち当たるのは目に見えているけど、考え方としては非常に大事な気はする。
静的ファイルについては、注意が必要で、next.config.js
のimages
を変更する必要があるとのこと。
今回は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.json
(swanky
で作成したらastar_sns_contract.json
という名前だった)をimport
できるところに配置。このファイルにABIが記載されており、プログラム中はabi
だけimport
されている。
ファイル名がxxFunction.tsx
と「Function
」がついていることだけ気になった・・・
Lessson 2. UIコンポーネント
アトミックデザインの実装がどうなるかが非常に気になるが、とりあえず、React.FCの方が気になったので調べてみた。
使わない方がいいんじゃないか説があるのでもう少し検討しないと。
アトミックデザイン方針のcomponents
配下をざっと見たところ、数多くのサイト制作やアプリケーションUIを手掛けてきた身としては、ちょっとやりすぎ感がある。おそらく個人的な達成感はあるだろうけど、チーム開発としてはしんどそう。
atom(原子)レベルまで落とし込む必要がどこまであるかというところ。
要するに、どこまで共通化を考える必要があるかというところ。
で、最後にはプログラミングの好みの話にまでなってしまう。
どのくらいUIの変更があったり、UXの変更を考えていくかなど、運用要件も絡んできそう。
ということで、アトミックデザインは考えについては自分の中に落とし込みつつ、
- Lv3. Organisms
- Lv4. Templates
- Lv5. Pages
の3レイヤーくらいじゃないのかなぁと。
Lv3は「Parts」として、共通利用していいように実装していた気がする。
Lesson 3. ページ
index.tsx
でさっそく気になったのが「Home
」。
ホームはページもあるから、「Index
」の方がいい気がする。。。
あとは、、、各種ページで同じような処理があるので共通化できないものか。
基本的には、
- これまで作ってきたUIコンポーネントを組み合わせてページを作っていく
- コントラクトを呼び出していく
という感じ。
いざ、動作確認へ!!!!
・・・
だめだこれは・・・
ソースのコピペでは動かない・・・
全体理解のために綺麗に作り直そう。。。
まとめ
Next.js初挑戦ということもあったが、ソースコピペで動いてくれないかな、という甘い考えが崩壊した。。。
一歩ずつ進むのが大事なので、しっかり復習しながらやっていこう。
急がば回れ
本日の教訓。