原文链接
    在上节我们没有对 Reach 程序进行修改就让石头剪刀布可以作为命令行应用程序运行。在本节中,我们仍不需要修改 Reach 程序,我们只将 Web 接口替换命令行接口。
    本教程中使用 React.js ,但其中的方法适用于任何 Web 框架。

    如果你以前未曾使用过 React ,它的基本工作原理如下:

    • React 程序是 JavaScript 程序,它使用一个特殊的库,允许您在 JavaScript 主体程序中加入 HTML 。
    • React 有一个特殊的编译器,它将一组 JavaScript 程序及其所有依赖项组合成一个可以部署在静态 Web 服务器上的大文件。这就是所谓的“打包”。
    • 当您使用 React 进行开发和测试时,您可以运行一个特殊的开发 Web 服务器,该服务器会在您每次修改源文件时监视这个打包文件的更新,因此您不必经常运行编译器。
    • 在运行./reach react时会自动启动开发服务器,并让您访问它http://localhost:3000/.

    和之前一样,在本教程中,我们假设将使用以太坊进行部署(和测试)。Reach Web 应用程序依赖 Web 浏览器提供对共识网络帐户及其关联钱包的访问。在以太坊上,标准的钱包是 MetaMask 。如果你想测试这里的代码,你需要安装并设置 MetaMask 。此外,MetaMask 不支持多个实例账户,所以如果你想在本地测试石头剪刀布!您需要有两个独立的浏览器实例(火狐+Chrome):一个作为 Alice ,另一个作为 Bob 。

    本节中的代码不建立在前一节的基础上。Reach 附有一个方便的命令来删除配置文件:
    $ ./reach unscaffold
    同样,我们不需要前面的 index.mjs 文件,因为我们会完全从头编写它以使用 React 。可以运行以下命令来删除它:
    $ rm index.mjs
    或者,您可以将 index.rsh 文件复制到一个新目录中,然后在那里开始工作。

    这段代码补充了 index.css 和一些视图。这些细节相当琐碎且不是 Reach 特有的,因此我们将不解释这些文件的细节。如果要在本地运行,你需要下载那些文件。目录应该如下所示:

    .
    ├── index.css
    ├── index.js
    ├── index.rsh
    └── views
    ├── AppViews.js
    ├── AttacherViews.js
    ├── DeployerViews.js
    ├── PlayerViews.js
    └── render.js


    我们将重点讨论 tut-9/index.js ,因为 tut-9/index.rsh 与前面的章节相同。
    tut-9/index.js

    1. 1 import React from 'react';
    2. 2 import AppViews from './views/AppViews';
    3. 3 import DeployerViews from './views/DeployerViews';
    4. 4 import AttacherViews from './views/AttacherViews';
    5. 5 import {renderDOM, renderView} from './views/render';
    6. 6 import './index.css';
    7. 7 import * as backend from './build/index.main.mjs';
    8. 8 import * as reach from '@reach-sh/stdlib/ETH';
    9. 9
    10. .. // ...
    • 第 1 行到第 6 行,我们导入视图代码和 CSS 。
    • 第 7 行,我们导入已编译的后端。
    • 第8行中,我们将 stdlib 导入为 reach。


    要在 Algorand 上运行,请在第 8 行更改导入。

    1. 从“@reach-sh/stdlib/ALGO”中导入*作为reach

    tut-9/index.js

    1. .. // ...
    2. 10 const handToInt = {'ROCK': 0, 'PAPER': 1, 'SCISSORS': 2};
    3. 11 const intToOutcome = ['Bob wins!', 'Draw!', 'Alice wins!'];
    4. 12 const {standardUnit} = reach;
    5. 13 const defaults = {defaultFundAmt: '10', defaultWager: '3', standardUnit};
    6. 14
    7. .. // ...

    在这些行上,我们定义了一些有用的常量和默认值,它们对应于我们在 Reach 中定义的枚举。

    我们开始将 App 定义为一个 React 组件,并告诉它挂载后要做什么,”挂载”是 React 的术语,即启动的意思。

    2.9 网页交互 - 图1 图1:ConnectAccount视图。参见:AppView。

    tut-9/index.js

    1. .. // ...
    2. 15 class App extends React.Component {
    3. 16 constructor(props) {
    4. 17 super(props);
    5. 18 this.state = {view: 'ConnectAccount', ...defaults};
    6. 19 }
    7. 20 async componentDidMount() {
    8. 21 const acc = await reach.getDefaultAccount();
    9. 22 const balAtomic = await reach.balanceOf(acc);
    10. 23 const bal = reach.formatCurrency(balAtomic, 4);
    11. 24 this.setState({acc, bal});
    12. 25 try {
    13. 26 const faucet = await reach.getFaucet();
    14. 27 this.setState({view: 'FundAccount', faucet});
    15. 28 } catch (e) {
    16. 29 this.setState({view: 'DeployerOrAttacher'});
    17. 30 }
    18. 31 }
    19. .. // ...

    tut-9/index.js

    1. .. // ...
    2. 39 render() { return renderView(this, AppViews); }
    3. 40 }
    4. 41
    5. .. // ...
    • 在第 18 行,我们初始化组件状态以显示 ConnectAccount 视图(图1)。
    • 在第 20 行到第 31 行,我们连接到 React 的 componentDidMount 生命周期事件,该事件在组件启动时被调用。
    • 在第 21 行,我们使用 getDefaultAccount ,它访问默认的浏览器帐户。例如,当与以太坊一起使用时,它可以发现当前选择的 MetaMask 帐户。
    • 在第 26 行中,我们使用 getFaucet 尝试访问 Reach 开发人员测试网络水龙头。
    • 在第 27 行,如果 getFaucet 成功,我们将组件状态设置为显示 FundAccount 视图(图2)。
    • 在第 29 行,如果 getFaucet 不成功,我们将组件状态设置为跳到 DeployerOrAttacher 视图(图3)。
    • 在第 39 行,我们从 tut-9/views/AppViewws.js 中呈现适当的视图。

      2.9 网页交互 - 图2 图2:基金账户视图。参见:AppView。

    接下来,我们在App上定义callback,即当用户点击某些按钮时该做什么。
    tut-9/index.js

    1. .. . // ...
    2. 32 async fundAccount(fundAmount) {
    3. 33 await reach.transfer(this.state.faucet, this.state.acc, reach.parseCurrency(fundAmount));
    4. 34 this.setState({view: 'DeployerOrAttacher'});
    5. 35 }
    6. 36 async skipFundAccount() { this.setState({view: 'DeployerOrAttacher'}); }
    7. .. . // ...
    • 在第 32 行到第 35 行,我们定义了当用户点击Fund Account时会发生什么。
    • 在第 33 行,我们将资金从水龙头转到用户的帐户。
    • 在第 34 行,我们设置组件状态以显示 DeployerOrAttacher 视图(图3)。
    • 在第 36 行,我们定义了当用户单击 Skip 按钮时要做的事情,即设置组件状态以显示 Deployer Or Attacher 视图(图 3 )。

      2.9 网页交互 - 图3 图3:Deployer或Attacher视图。AppViews.DeployerOrAttacher

    tut-9/index.js

    1. .. // ...
    2. 37 selectAttacher() { this.setState({view: 'Wrapper', ContentView: Attacher}); }
    3. 38 selectDeployer() { this.setState({view: 'Wrapper', ContentView: Deployer}); }
    4. .. // ...

    在第 37 和 38 行中,我们根据用户是单击 Deployer 还是 Attacher 来设置子组件。

    接下来,我们将把 Player 定义为 React 组件,它将由 Alice 和 Bob 的专用组件扩展。

    2.9 网页交互 - 图4 图4:GetHand视图。参见:PlayerView。

    我们的 Web 前端需要为玩家实现参与者交互界面,我们定义为:
    tut-9/index.rsh

    ..    // ...
    20    const Player =
    21          { ...hasRandom,
    22            getHand: Fun([], UInt),
    23            seeOutcome: Fun([UInt], Null),
    24            informTimeout: Fun([], Null) };
    ..    // ...
    

    我们将通过 React 组件直接提供这些 callback 。
    tut-9/index.js

    ..    // ...
    42    class Player extends React.Component {
    43      random() { return reach.hasRandom.random(); }
    44      async getHand() { // Fun([], UInt)
    45        const hand = await new Promise(resolveHandP => {
    46          this.setState({view: 'GetHand', playable: true, resolveHandP});
    47        });
    48        this.setState({view: 'WaitingForResults', hand});
    49        return handToInt[hand];
    50      }
    51      seeOutcome(i) { this.setState({view: 'Done', outcome: intToOutcome[i]}); }
    52      informTimeout() { this.setState({view: 'Timeout'}); }
    53      playHand(hand) { this.state.resolveHandP(hand); }
    54    }
    55    
    ..    // ...
    
    • 在第 43 行,我们提供 hasRandom 回调函数
    • 在第 44 至 50 行,我们提供 getHand 回调函数。
    • 在第 45 行到第 47 行,我们将组件状态设置为显示 Get Hand 视图(图 4 ),并等待可以通过用户交互解决的 Promise 。
    • 在 Promise 解析之后的第 48 行中,我们将组件状态设置为显示 Waiting For Results 视图(图 5 )。
    • 在第 51 行和第 52 行中,我们提供了seeOutcome 和 informTimeout 回调,它们设置组件状态来分别显示 Done 视图(图6)和 Timeout 视图(图7)。
    • 在第 53 行,我们定义了当用户点击石头、剪刀、布时会发生什么:第 45 行的 Promise 被解析。

      2.9 网页交互 - 图5 图5:WaitingForResults视图。参见:PlayerViews.WaitingForResults 2.9 网页交互 - 图6 图6:完成视图。参见:PlayerView.Done 2.9 网页交互 - 图7 图7:超时视图。参见:PlayerViews.Timeout


    接下来,我们将把 Deployer 定义为 Alice 的 React 组件,它扩展了 Player 。

    2.9 网页交互 - 图8 图8:SetWager视图。参见:DeployerView。 2.9 网页交互 - 图9 图9:部署视图。

    我们的 Web 前端需要实现 Alice 的参与者交互界面,我们定义为:
    tut-9/index.rsh

    ..    // ...
    25    const Alice =
    26          { ...Player,
    27            wager: UInt };
    ..    // ...
    

    我们将提供赌注值,并定义一些按钮处理程序,以触发合约的部署。
    tut-9/index.js

    ..    // ...
    56    class Deployer extends Player {
    57      constructor(props) {
    58        super(props);
    59        this.state = {view: 'SetWager'};
    60      }
    61      setWager(wager) { this.setState({view: 'Deploy', wager}); }
    62      async deploy() {
    63        const ctc = this.props.acc.deploy(backend);
    64        this.setState({view: 'Deploying', ctc});
    65        this.wager = reach.parseCurrency(this.state.wager); // UInt
    66        backend.Alice(ctc, this);
    67        const ctcInfoStr = JSON.stringify(await ctc.getInfo(), null, 2);
    68        this.setState({view: 'WaitingForAttacher', ctcInfoStr});
    69      }
    70      render() { return renderView(this, DeployerViews); }
    71    }
    72    
    ..    // ...
    
    • 第 59 行,我们设置组件状态以显示 SetWager 视图(图8)。
    • 在第 61 行,我们定义了当用户单击 Set Wager 按钮时要做的事情,即设置组件状态以显示 Deploy 视图(图 9 )。
    • 在第 62 至 69 行中,我们定义了当用户单击 Deploy 按钮时要做什么。
    • 在第 63 行中,我们调用 acc.deploy ,它触发契约的部署。
    • 在第 64 行,我们设置组件状态以显示部署视图(图10)。
    • 在第 65 行,我们设置了赌注属性。
    • 在第 66 行,我们开始作为 Alice 运行 Reach 程序,使用这个 React 组件作为参与者交互界面对象。
    • 在第 67 行和第 68 行,我们设置组件状态以显示 WaitingForAttacher 视图(图11),它将部署的合约信息显示为 JSON 。
    • 在第 70 行中,我们从 tut-9/views/DeployerViews.js. 中呈现适当的视图。

      2.9 网页交互 - 图10 图10:部署视图。参见:部署查看 2.9 网页交互 - 图11 图11:WaitingForAttacher视图。DeployerViews.WaitingForAttacher

    2.9 网页交互 - 图12 图12:Attacher视图。 2.9 网页交互 - 图13 图13:附加视图,参见:附件视图

    我们的 Web 前端需要为 Bob 实现参与者交互界面,我们定义为:
    tut-9/index.rsh

    ..    // ...
    28    const Bob =
    29          { ...Player,
    30            acceptWager: Fun([UInt], Null) };
    ..    // ...
    

    我们将提供 acceptWager 回调,并定义一些按钮处理程序,以便附加到已部署的合约。
    tut-9/index.js

    ..    // ...
    73    class Attacher extends Player {
    74      constructor(props) {
    75        super(props);
    76        this.state = {view: 'Attach'};
    77      }
    78      attach(ctcInfoStr) {
    79        const ctc = this.props.acc.attach(backend, JSON.parse(ctcInfoStr));
    80        this.setState({view: 'Attaching'});
    81        backend.Bob(ctc, this);
    82      }
    83      async acceptWager(wagerAtomic) { // Fun([UInt], Null)
    84        const wager = reach.formatCurrency(wagerAtomic, 4);
    85        return await new Promise(resolveAcceptedP => {
    86          this.setState({view: 'AcceptTerms', wager, resolveAcceptedP});
    87        });
    88      }
    89      termsAccepted() {
    90        this.state.resolveAcceptedP();
    91        this.setState({view: 'WaitingForTurn'});
    92      }
    93      render() { return renderView(this, AttacherViews); }
    94    }
    95    
    ..    // ...
    
    • 在第 76 行,我们初始化组件状态以显示 Attach 视图(图12)。
    • 在第 78 至 82 行,我们定义了当用户单击 Attach 按钮时会发生什么。
    • 在第 79 行,我们调用 acc.attach
    • 在第 80 行,我们设置组件状态以显示附加视图(图13)。
    • 在第 81 行,我们开始以 Bob 的身份运行 Reach 程序,使用这个 React 组件作为参与者交互接口对象。
    • 在第 83 行到第 88 行,我们定义了 acceptWager 回调函数。
    • 在第 85 行到第 87 行,我们将组件状态设置为显示 Accept Terms 视图(图 14 ),并等待可以通过用户交互解决的 Promise 。
    • 在第 89 行到第 92 行,我们定义了当用户单击 Accept Terms 和 Pay Wager 按钮时发生的事情:第 90 行的 Promise 被解析,我们设置组件状态以显示 Waiting For Turn 视图(图 15 )。
    • 在第 93 行,我们从 tut-9/views/AttacherViews.js 中呈现适当的视图

      2.9 网页交互 - 图14 图14:AcceptTerms视图,请参见:AttacherViews.AcceptTerms 2.9 网页交互 - 图15 图15:WaitingForTurn视图。参见:AttacherViews.WaitingForTurn


    tut-9/index.js

    ..    // ...
    96    renderDOM(<App />);
    

    最后,我们调用 tut-9/views/render.js 中的一个小助手函数来呈现我们的 App 组件。

    为了方便运行 React 开发服务器,您可以调用:
    $ ./reach react

    要使用 Algorand 运行 React 开发服务器,您可以调用: $REACH_CONNECTOR_MODE=ALGO./reach react

    如果你想在你自己的JavaScript项目中使用Reach,你可以调用:
    $npm install @reach-sh/stdlib

    Reach 标准库正在不断改进,并经常更新。如果您遇到 Node.js 包的问题,请尝试更新!

    与往常一样,您可以将 Reach 程序 index.rsh 编译到后端构建工件 build/index.main.mjs 中,使用:
    $/reach run

    现在我们的石头剪刀布的实现在浏览器上!我们可以利用参与者交互界面中的回调,通过我们选择的任何 Web UI 框架向用户显示和收集信息。
    如果我们想将这个应用程序发布,那么我们将获取 React 生成的静态文件,并将它们托管在 Web 服务器上。这些文件嵌入了你编译好的 Reach 程序,所以除了将它们提供给外界之外,别无他法。
    在下一节中,我们将总结我们所取得的成果,并指导您迈向精通去中心化应用程序之旅的下一步。

    您知道了吗?: 是非题: Reach 集成了所有的 Web 界面库,如 React 、 Vue 等,因为 Reach 前端只是普通的 JavaScript 程序。 答案是: 正确的

    您知道了吗?: 是非题: Reach 通过嵌入 React 开发服务器和部署过程来在本地测试 React 程序,从而加快您使用 React 的开发。 答案是: 正确的