Sui Move 前端共学笔记(三)

235 阅读6分钟

HOH水分子社区,是一个专注于编程、教育和创新的社区。

项目构建

通过Sui官方目前提供的react的Dapp项目框架可以快速构建项目,构建命令如下:

npm/pnpm/yarm:
npm create @mysten/dapp

burn:
bunx @mysten/create-dapp

React扫盲

构建完项目后可以看到如下目录结构:

image.png

.idea目录是开发工具的初始化目录,存放了用户的一些配置信息,这个目录由开发工具生成,在项目开发过程中我们一般不会去直接修改该目录下内容。

node_modules目录是项目中所需要的一些依赖文件。

src是资源文件,用来存放代码,和一些静态资源文件。

index.html是网页模板,在react中一般我们不对其直接修改,而是通过react对其进行渲染。

package.json目录记录当前项目用到的库的信息,如版本什么的,由于依赖问题可能会经常对其进行修改。

prettier.config.cjs这个不清楚干啥的,应该是个配置文件,基本也是不会对其进行修改。

README.md跟名字一样,是作者自述文件,因为有的作者比较内向所有有的项目是空白的或者干脆就没有这个文件。

tsconfig.json、tsconfig.node.json、vite.config.ts是项目配置文件,在项目编写过程中可能会经常对齐其进行修改,但是要慎重,改错了,就崩了。

image.png

展开src目录,能看到这些文件重点关注一下main.tsx的以下内容其他的内容后续用到再细说:

···省略部分代码
import App from "./App.tsx";
···省略部分代码

ReactDOM.createRoot(document.getElementById("root")!).render(
//创建了一个根节点,渲染模板文件中id为root的标签,一个react项目只能有一个根节点
  <React.StrictMode>
  //开启严格模式,开启这个模式一些非致命错误也会显示
    ···省略部分代码
            <App />
            //渲染App.tsx的内容
    ···省略部分代码
  </React.StrictMode>,
);

这个文件前期定义完成后也不会做太大改动,省略的部分是引入的库的内容,暂时不用管他们做啥的,在react中每一文件都可以视为一个组件,在项目构建过程中我们要尽量对需求进行拆分,一个功能一个组件,有助于后期项目的维护工作。

前端合约交互

0x1:查看合约中定义的结构体(合约内容见笔记(一)):

public struct State has key{
    id:UID,
    users:Table<address,address>,
}

public struct Profile has key{
    id:UID,
    name:String,
    description:String,
}

0x2:根据结构体定义类型-->scr/types/index.ts:

export type Profile={
  id:string,
  name:string,
  description:string,
}

export type State={
  id:string,
  users:user,
}

//定义了类型Profile和State,Profile中有三个字符串属性的值分别是id,name,description,用于描述用户基本信息,State中包含2个属性值,id和users,用来记录用户的地址和凭证信息。

type user={
  own:string,
  profile:Profile,
}

//定义user类型,包含属性own和profile,分别表示用户地址和,用户和对应的凭证地址。

0x3:配置测试合约package id,编写suiClient-->src/networkConfig.ts

import { getFullnodeUrl, SuiClient } from "@mysten/sui/client";
import {
  TESTNET_COUNTER_PACKAGE_ID,
} from "./constants.ts";
import { createNetworkConfig } from "@mysten/dapp-kit";

//导入组件所需的依赖

const { networkConfig, useNetworkVariable, useNetworkVariables } =
  createNetworkConfig({
    testnet: {
      url: getFullnodeUrl("testnet"),
      variables: {
        counterPackageId: TESTNET_COUNTER_PACKAGE_ID,
        stateId:"0x39c01e5e7f1aecd5de8f212acb775c2702402d7261d213f02cfa26ba7b51a8f2",
      },
    },
    // 定义可被外部调用的变量url表示测试网,variables内可任意自定义变量,counterPackageId为合约发布后Id,stateId为合约初始化时生成的State对象地址。
  });
  
  // 定义了3个常量networkConfig、useNetworkVariable、 useNetworkVariables通过dapp-kit库中的方法createNetwrokConifig生成,参数中用到了一个TESTNET_COUNTER_PACKAGE_ID的值,这个值需要在文件constant.ts中自定义。

const suiClient= new SuiClient(
  {
    url:networkConfig.testnet.url,
  }
);

// 定义一个suiClient,用于简化代码在方便在其他组件中调用suiClient的一些内置方法。

export { useNetworkVariable, useNetworkVariables, networkConfig ,suiClient};
// 导出当前组件内的函数及变量,允许其他组件引入。

0x4:编写state查询方法-->src/contracts/index.js

import { networkConfig, suiClient } from "../networkConfig.js";
//导入networkConfig定义的网络配置和客户端

export const queryState =async ()=>{
//定义查询state的异步方法并导出。
  const state = await suiClient.getObject(
  // 定义一个state接受suiClient查询结果
    {
      id:networkConfig.testnet.variables.stateId,
      // State对象的地址。
      options:{
        showContent:true
      }
      // 可选参数,用于指定想要获取的值。
    }
  )
  
  return {
    state: state,
  }
  // 将获取的内容返回供其他组件处理。
}

0x5 编写事件查询方法

export  const queryEvent = ()=>{
// 定义查询事件函数并导出
  const event = suiClient.queryEvents({
    query:{
      MoveEventType:"0x4c8b26e8fc90ddbf24267a729b9366464bc5c8ebadc429cc13564e559e6da974::assets_manage::ProfileCreate"
      // 根据类型筛选事件
    }
  })
  return event
  //返回查询的结果供其他组件调用。
}

0x6:编写调用合约create_Profile方法

export const createProfile = async(name:string,description:string)=>{
//定义异步方法,接受传参name,description用于创建用户凭据
  const tx = new Transaction();
  // 创建交易对象
  tx.moveCall({
      package:networkConfig.testnet.variables.counterPackageId,
      // 合约地址
      module:"assets_manage",
      // 调用合约中哪个模块
      function:"create_Profile",
      // 调用合约assets_manage中的哪个方法。
      arguments:[
        tx.pure.toString(name),
        tx.pure.toString(description),
        tx.object(networkConfig.testnet.variables.stateId)
      ]
      // 调用方法用到的参数。
    }
  )
  // 调用执行交易
}

功能测试

import { ConnectButton,useSignAndExecuteTransaction } from "@mysten/dapp-kit";
import { Box, Flex, Heading } from "@radix-ui/themes";
import { useEffect, useState } from "react";
import {queryState,queryEvent,createProfile} from "./contracts/"

// 导入依赖

function App() {
  const  [name, setName]  = useState("");
  const [description, setDesccription]  = useState("");
  
  // 定义name、description状态,用于接受用户传参。
  const { mutate: signAndExecute } = useSignAndExecuteTransaction();

  useEffect(() => {
  //副作用hook用户获取state及事件数据
    const fetchState = async () => {
    // 定义fetchState方法
      const state = await queryState();
      // 调用事件查询方法,该方法中返回的值为未反序列化内容,无法看到state表内具体内容。
      const event = await queryEvent();
      // 调用事件查询方法,获取用户profile创建事件内容
      console.log(state);
      console.log("event", event);
      // 打印查询结果。
    };
    fetchState();
    // 调用fetchState方法
  }, []);

  const handleCreateProfile = async () => {
  // 用户凭据创建函数
    const tx = await createProfile(name, description);
    // 调用createProfile方法获取交易对象内容
    console.log("createProfile", tx);
    signAndExecute(
      {
        transaction: tx,
      },
      {
        onSuccess: () => {
          console.log("create profile");
        },
        onError: () => {
          console.log("create profile failed");
        },
      },
     // 调用钱包对交易进行签名执行,成功提示create profile ,失败则提示create profile failed
    );
  };

  return (
    <>
      <Flex
        position="sticky"
        px="4"
        py="2"
        justify="between"
        style={{
          borderBottom: "1px solid var(--gray-a2)",
        }}
      >
        <Box>
          <Heading>dApp Starter Template</Heading>
        </Box>
        // 这个是模板文件的标题,忘记改了,不影响使用

        <Box>
          <ConnectButton />
        </Box>
        // 这个是链接钱包的按钮
      </Flex>
      <br />
      name:
      <input value={name} onChange={(e) => setName(e.target.value)} />
      <br />
      // 定义输入框,当监听到内容发生改变则调用setName方法改变Name的值
      description
      <input value={description} onChange={(e) => setDesccription(e.target.value)} />
       // 定义输入框,当监听到内容发生改变则调用setDescription方法改变description的值
      <br />
      姓名:{name}
      <br />
      描述:{description}
      <br />
      // 上面两个是为了确定输入框改变时,name跟description值是否被改变写的,不写也没事。
      <button onClick={handleCreateProfile}>Create Profile</button>
      // 定义按钮点击后触发handleCreateProfile创建prifile
    </>
  );
}

export default App;
// 导出App在main.ts里看到的那个<App />就是引用的这个文件。

效果展示

界面

image.png

调用合约创建profile

image.png

image.png

查询State和事件

image.png

Contact US

HOH水分子公众号 Alya @HOH Jane @HOH