使用 React、Solidity 和 Web3.js 构建真实世界的 dApp

简介: 网络技术的世界正在迅速向 Web 3.0 过渡,人们似乎已经厌倦了中心化的平台,数字隐私每天都受到大型组织的攻击和变卖。

网络技术的世界正在迅速向  Web 3.0  过渡,人们似乎已经厌倦了中心化的平台,数字隐私每天都受到大型组织的攻击和变卖。因此,需要一个解决方案,而 Web 3.0 似乎是目前最佳的答案。本文并不打算去介绍区块链和去中心化系统的专业知识,而是展示如何使用 React、Solidity 和 Web3.js 构建真实世界的 dApp。

项目地址:github.com/QuintionTan…

工具概览

下面将介绍在这篇文章中即将使用的一些工具。

  • 智能合约工具:Truffle Framework,提供了一套用于开发以太坊智能合约的工具。它提供了智能合约管理、部署和迁移、网络管理、开发控制台等工具。
  • 以太坊测试链:Ganache,一个个人区块链,一个本地开发区块链,可以用来模仿公共区块链的所有行为。
  • 编写智能合约语言:Solidity,一种用于实现智能合约的面向对象的高级语言。
  • 区块链钱包插件:MetaMask,本文将使用 Metamask 谷歌扩展程序,它实现了浏览器与测试以太坊区块链网络进行连接。
  • 前端开发框架:React
  • web3js:一个 JavaScript 库,允许与以太坊区块链进行通信。它将 React 应用程序变成支持区块链的应用程序。

环境准备

首先,下载按照 Truffle Framework ,使用以下命令:

npm install -g truffle

接下来,下载并安装 Ganache,安装成功打开将可以快速构建一个本地ETH网络,界面如下:

image.png

npm install ganache --global

安装后,执行以下命名可以启动一个本地 ETH 网络:

ganache

启动网络,生成一些可用于测试的账号地址和私钥,如下:

Available Accounts
==================
(0) 0xA57F1010d3BC4Acf6572c42B794B67dAf35B84f2 (1000 ETH)
(1) 0x60A73B46Ea815160c83CC3F89D855a4B8b6f21b5 (1000 ETH)
(2) 0x518B8c54ff5CA623707FB9Ca78B738425972EDdE (1000 ETH)
(3) 0x555024474B387842B8089489658d62c2Fc2D012F (1000 ETH)
(4) 0x302C59aE6F9456d74afB3Ca965b5f2123Fa58fbB (1000 ETH)
(5) 0xE0016171057C950A0E101f5affdAF01e8F5afd1f (1000 ETH)
(6) 0xC7cC6407DA22b630BEE2a8a3c304DA635eC4d9CD (1000 ETH)
(7) 0x06DCa7D808748F663D22BacC85555140D98Ee7fC (1000 ETH)
(8) 0xb1A3749c08b8440D3a45612A1d8864826d68549c (1000 ETH)
(9) 0xcF9E30D63a4970e938328470f7756c372afC8bBE (1000 ETH)
Private Keys
==================
(0) 0xcfac544a9947d80e3b37b00efad50a7ea3efab6ca51dd0808cad58f4ad9ebaae
(1) 0xecd3ae8ea7b36b7acc522ca790ae0660986f0615d73704bb4ceb03138cf9144a
(2) 0x7fa1926a2b8b2d98cc54bc9c9baf99035f7e5db1f23938a0b37a1ad8d106ce57
(3) 0xa313ceb995434887f5335813c4df01ab631b72de179044a04547e3c1142a8b46
(4) 0x2d2b70124764d8e4dd6b78a4f2cb689c27f1d42d89d2d416d9e5ad0797455fab
(5) 0x2ecdffb4009281b434f7c2e0bcc94751fb45c8667f7201ec3566e0ef79c6d82b
(6) 0x0b6ea61e43c6963cff2a4f5d0806bbd08cd99abcf5a61f92cadbecbf1bca4da3
(7) 0xfe15c45eca17dd6d67d4c044e7d4ea2740d729b76085de163143bcd19699fd9e
(8) 0xb01f9a7059c685e843339839e3398a556a74d890817cde4c09ed61828c2c08ae
(9) 0x952c883b05da76f58e30ce7fa4ed98cf8479313a1c1667c0c2d2aa10c66fea31

接下来,需要安装使用 Metamask。将 Metamask 扩展添加到 google chrome ,导入上面的账号,效果如下:

image.png

后续的开发过程将不会使用 ganache  终端来构建开发区块链网络,而是使用其客户端。

开始构建

dApp 前期准备工作已经设置好,接下来编写智能合约。打开一个终端并使用以下命令在项目文件夹中创建一个文件夹:

mkdir blockchain

现在使用以下命令在区块链文件夹中再创建一个合约文件夹:

cd blockchain
mkdir contracts
cd contracts

运行以下命令来创建一个 truffle 项目,这样就可以开发智能合约:

truffle init

使用上面的命令,应该得到如下输出:

Init successful, sweet!
Try our scaffold commands to get started:
  $ truffle create contract YourContractName # scaffold a contract
  $ truffle create test YourTestName         # scaffold a test
http://trufflesuite.com/docs

打开项目文件夹,可以看到以下文件夹和文件结构:

image.png

  • contracts 文件夹中,将编写智能合约。
  • migrations 文件夹中,将迁移新创建的智能合约。
  • test 文件夹中,通常用于编写测试用例来测试智能合约。
  • truffle-config.js 文件包含 truffle 项目的所有配置。

现在编写智能合约,在 contracts 文件夹中创建一个新文件并将其命名为 contacts.sol,并在其中写入以下代码:

// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.9.0;

这始终是智能合约文件中的第一行,用来指定 solidity 的版本。现在来编写代码制作第一个智能合约 Contacts

contract Contacts {
}

现在定义一个状态变量来跟踪智能合约中联系人的数量,代码如下:

pragma solidity >=0.4.22 <0.9.0;
contract Contacts {
    uint public count = 0; // 状态变量
}

这是一个特殊的变量,写入该变量的任何数据都将存储在区块链存储中。代码中使用一个特殊的修饰符关键字,即 public ,定义的变量允许智能合约外对其进行访问,初始赋值为 0 。现在有了一个状态变量,并且里面有一个值。现在进入前端部分,首先尝试访问这个公共状态变量。

blockchain 目录中运行以下命令以创建一个响应应用程序:

npx create-react-app contacts

接下来进入项目目录,增加依赖:

cd contacts
yarn add web3

如果使用的是 create-react-app 版本 >=5,可能会遇到构建问题,这是因为最新版本的 create-react-app 中不包含 NodeJSpolyfill。需要按照下面的方式来解决这个问题:

yarn add --dev react-app-rewired crypto-browserify stream-browserify assert stream-http https-browserify os-browserify url buffer

然后在项目根目录下创建文件 config-overrides.js ,增加以下代码:

const webpack = require("webpack");
module.exports = function override(config) {
    const fallback = config.resolve.fallback || {};
    Object.assign(fallback, {
        crypto: require.resolve("crypto-browserify"),
        stream: require.resolve("stream-browserify"),
        assert: require.resolve("assert"),
        http: require.resolve("stream-http"),
        https: require.resolve("https-browserify"),
        os: require.resolve("os-browserify"),
        url: require.resolve("url"),
    });
    config.resolve.fallback = fallback;
    config.plugins = (config.plugins || []).concat([
        new webpack.ProvidePlugin({
            process: "process/browser",
            Buffer: ["buffer", "Buffer"],
        }),
    ]);
    return config;
};

最后修改 package.json 的启动方式:

"scripts": {
    "start": "react-app-rewired start",
    "build": "react-app-rewired build",
    "test": "react-app-rewired test",
    "eject": "react-scripts eject"
}

如上所见,添加 web3.js 作为该项目的依赖项,并解决上面的问题后,以使创建的 React 应用程序与上面创建的智能合约进行交互。

将在 App.js 文件中编写所有代码,代码如下:

import { useEffect, useState } from "react";
import Web3 from "web3";
function App() {
    const [account, setAccount] = useState(); // 设置账号的状态变量
    useEffect(() => {
        async function load() {
            const web3 = new Web3(
                Web3.givenProvider || "http://localhost:7545"
            );
            const accounts = await web3.eth.requestAccounts();
            setAccount(accounts[0]);
        }
        load();
    }, []);
    return <div>当前账号: {account}</div>;
}
export default App;

为了能够成功在本地运行应用,需要在后端上调整一些配置,回到合同文件夹,打开 truffle-config.js 文件并添加以下属性:

module.exports = {
    networks: {
        development: {
            host: "127.0.0.1",
            port: 7545,
            network_id: "*",
        },
    },
    compilers: {
        solc: {
            optimizer: {
                enabled: true,
                runs: 200,
            },
        },
    },
};

然后在 migrations 文件夹中,创建一个新文件并将其命名为 2_deploy_contracts.js 代码如下:

const Contacts = artifacts.require("./Contacts.sol");
module.exports = function (deployer) {
    deployer.deploy(Contacts);
};

在终端中,输入一下命令以迁移智能合约:

cd contracts
truffle migrate

应该会看到如下输出:

image.png

回到前端 React 文件夹并运行以下命令来运行应用程序:

cd contacts
yarn start

这将在浏览器中打开 React 应用程序,并会触发 Metamask 与区块链网络进行交互,应该会看到如下所示的屏幕:

image.png

现在已经成功地与智能合约交互并检索了账户 ID,继续在智能合约中创建一个新函数来获取联系人列表并将其发送到前端并在视图上呈现它们。

// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.9.0;
contract Contacts {
    uint public count = 0; // 状态变量
    struct Contact {
        uint id;
        string name;
        string phone;
    }
    constructor() public {
        createContact('QuintionTang', '18900000000');
    }
    mapping(uint => Contact) public contacts;
    function createContact(string memory _name, string memory _phone) public {
        count++;
        contacts[count] = Contact(count, _name, _phone);
    }
}

现在再次迁移此合约:

truffle migrate

完成后回到前端React 应用程序,在 src/ 文件夹中创建一个新文件 config.js,配置合约地址和合约ABI,这些信息可以在文件blockchain/contracts/build/contracts/Contacts.json 中找到 ,代码如下:

export const CONTRACT_ADDRESS = "0xD3329a764935C7AA9B32f162C196E28B4e3310A3"; // 合约地址
export const CONTRACT_ABI = [
    {
        inputs: [],
        payable: false,
        stateMutability: "nonpayable",
        type: "constructor",
    },
    {
        constant: true,
        inputs: [
            {
                internalType: "uint256",
                name: "",
                type: "uint256",
            },
        ],
        name: "contacts",
        outputs: [
            {
                internalType: "uint256",
                name: "id",
                type: "uint256",
            },
            {
                internalType: "string",
                name: "name",
                type: "string",
            },
            {
                internalType: "string",
                name: "phone",
                type: "string",
            },
        ],
        payable: false,
        stateMutability: "view",
        type: "function",
    },
    {
        constant: true,
        inputs: [],
        name: "count",
        outputs: [
            {
                internalType: "uint256",
                name: "",
                type: "uint256",
            },
        ],
        payable: false,
        stateMutability: "view",
        type: "function",
    },
    {
        constant: false,
        inputs: [
            {
                internalType: "string",
                name: "_name",
                type: "string",
            },
            {
                internalType: "string",
                name: "_phone",
                type: "string",
            },
        ],
        name: "createContact",
        outputs: [],
        payable: false,
        stateMutability: "nonpayable",
        type: "function",
    },
];

现在将智能合约地址和 ABI 导入 App.js 文件,如下所示,并使用以下代码更新加载函数:

import { useEffect, useState } from "react";
import Web3 from "web3";
import { CONTRACT_ABI, CONTRACT_ADDRESS } from "./config";
function App() {
    const [account, setAccount] = useState();
    const [contactList, setContactList] = useState();
    const [contacts, setContacts] = useState([]);
    useEffect(() => {
        async function load() {
            const web3 = new Web3(
                Web3.givenProvider || "http://localhost:7545"
            );
            const accounts = await web3.eth.requestAccounts();
            setAccount(accounts[0]);
            // 使用ABI和地址实例化智能合约。
            const contactListByContract = new web3.eth.Contract(
                CONTRACT_ABI,
                CONTRACT_ADDRESS
            );
            // 将联系人列表设置为状态变量。
            setContactList(contactListByContract);
            // 得到迭代的联系人总数
            const counter = await contactListByContract.methods.count().call();
            // 遍历计数器的时间量
            for (var i = 1; i <= counter; i++) {
                // 调用 contacts 方法从智能合约中获取特定的联系人
                const contact = await contactListByContract.methods
                    .contacts(i)
                    .call();
                // 添加最近获取的联系状态变量。
                setContacts((contacts) => [...contacts, contact]);
            }
        }
        load();
    }, []);
    return (
        <div>
            当前账号: {account}
            <h1>联系人列表</h1>
            <ul>
                {Object.keys(contacts).map((contact, index) => (
                    <li key={`${contacts[index].name}-${index}`}>
                        <h4>{contacts[index].name}</h4>
                        <span>
                            <b>手机号码: </b>
                            {contacts[index].phone}
                        </span>
                    </li>
                ))}
            </ul>
        </div>
    );
}
export default App;

启动 React 应用程序可以看到如下效果:

image.png

到此,一个简单的 dAPP 已经实现了。


相关文章
|
5天前
|
数据采集 存储 XML
如何利用Python构建高效的Web爬虫
本文将介绍如何使用Python语言以及相关的库和工具,构建一个高效的Web爬虫。通过深入讨论爬虫的基本原理、常用的爬虫框架以及优化技巧,读者将能够了解如何编写可靠、高效的爬虫程序,实现数据的快速获取和处理。
|
10天前
|
运维 前端开发 JavaScript
【专栏:HTML进阶篇】HTML与Web标准:构建可访问与可维护的网页
【4月更文挑战第30天】本文探讨了HTML与Web标准的关系,强调遵循标准对创建高质量、可访问、可维护网页的重要性。通过使用语义化标签、提供文本替代、合理使用表格和列表,可提升网页可访问性;通过结构化文档、添加注释、分离结构与表现,能增强网页可维护性。遵循Web标准,可确保网页在不同设备上的兼容性,并满足各类用户需求。
|
10天前
|
开发框架 Dart 前端开发
【Flutter前端技术开发专栏】Flutter中的Web支持:构建跨平台Web应用
【4月更文挑战第30天】Flutter,Google的开源跨平台框架,已延伸至Web领域,让开发者能用同一代码库构建移动和Web应用。Flutter Web通过将Dart代码编译成JavaScript和WASM运行在Web上。尽管性能可能不及原生Web应用,但适合交互性强、UI复杂的应用。开发者应关注性能优化、兼容性测试,并利用Flutter的声明式UI、热重载等优势。随着其发展,Flutter Web为跨平台开发带来更多潜力。
【Flutter前端技术开发专栏】Flutter中的Web支持:构建跨平台Web应用
|
10天前
|
JavaScript 前端开发 API
如何利用JavaScript和Electron构建具有丰富功能的桌面应用
【4月更文挑战第30天】如何利用JavaScript和Electron构建具有丰富功能的桌面应用
5 0
|
10天前
|
缓存 监控 JavaScript
Node.js中构建RESTful API的最佳实践
【4月更文挑战第30天】本文介绍了在Node.js中构建RESTful API的最佳实践:选择合适的框架(如Express、Koa)、设计清晰的API接口(遵循HTTP动词和资源路径)、实现认证授权(JWT、OAuth 2.0)、错误处理、限流缓存、编写文档和测试,以及监控性能优化。这些实践有助于创建健壮、可维护和易用的API。
|
10天前
|
消息中间件 监控 JavaScript
Node.js中的微服务架构:构建与实践
【4月更文挑战第30天】本文探讨了在Node.js中构建微服务的实践,包括定义服务边界、选择框架(如Express、Koa或NestJS)、设计RESTful API、实现服务间通信(HTTP、gRPC、消息队列)、错误处理、服务发现与负载均衡,以及监控和日志记录。微服务架构能提升应用的可伸缩性、灵活性和可维护性。
|
11天前
|
缓存 监控 测试技术
【Go语言专栏】使用Go语言构建高性能Web服务
【4月更文挑战第30天】本文探讨了使用Go语言构建高性能Web服务的策略,包括Go语言在并发处理和内存管理上的优势、基本原则(如保持简单、缓存和并发控制)、标准库与第三方框架的选择、编写高效的HTTP处理器、数据库优化以及性能测试和监控。通过遵循最佳实践,开发者可以充分利用Go语言的特性,构建出高性能的Web服务。
|
11天前
|
网络协议 数据库 开发者
构建高效Python Web应用:异步编程与Tornado框架
【4月更文挑战第29天】在Web开发领域,响应时间和并发处理能力是衡量应用性能的关键指标。Python作为一种广泛使用的编程语言,其异步编程特性为创建高性能Web服务提供了可能。本文将深入探讨Python中的异步编程概念,并介绍Tornado框架如何利用这一机制来提升Web应用的性能。通过实例分析,我们将了解如何在实际应用中实现高效的请求处理和I/O操作,以及如何优化数据库查询,以支持更高的并发用户数和更快的响应时间。
|
11天前
|
开发者 Python
使用Python构建Web应用的简介
【4月更文挑战第28天】
|
11天前
|
缓存 前端开发 JavaScript