用 Substrate Front-end Template 轻松打造你的 React 应用
Substrate 前端开发系列 - 2/2
前言
前端开发系列第一篇講了如何用 Polkadot JS API (簡稱 JS API) 來搭建前端。如果你的前端是用 React 或它家族的框架來寫,那可參考今天要深入討論的另一個官方項目 Substrate Front-end Template。这个项目是官方支持的,它把 JS API 封装好在 [React 应用]里,并对常用的操作进行了封装,放在不同的組件內使用。使你在前端開發中更專注頁面和用戶的互動,省卻一些力量處理㡳層如何和 Substrate 節點交互。
接下来,我们会手把手在本机跑起 Substrate 节点,前端模版。然后查看模版代码是如何查询链上数据,提交交易,最后也说明如何查询链上自定义的数据类型。
连接到本机开发节点
因为我们也需要一个 Substrate 节点,我们会同时 git 克隆一个 Node Template 及 Front-end Template 下来。
mkdir ui-tutorial
cd ui-tutorial
# -- 安装 Rust 及 Substrate
# 更详细的安装指引可参考:
# https://substrate.dev/docs/en/overview/getting-started
curl https://getsubstrate.io -sSf | bash
# -- 下载 Substrate Node Template,编译并运行起来
git clone https://github.com/substrate-developer-hub/substrate-node-template node-template
cd node-template
# 这指令会编译 Substrate, 须时 30 - 45 分钟不等
cargo build --release
# 运行以下指令会占据整个终端,直到 Cmd+C / Ctrl+C 停止
target/release/node-template --dev
如一切无误,你在终端会看到类似如下的输出:
按下 Ctrl+C / Cmd+C 退出。
要重置本机区块链,则输入:
target/release/node-template purge-chain --dev
接下来安装 Front-end Template:
# -- 安装 NodeJS v12 (https://nodejs.org/en/download/),
# 及 yarn 工具 (https://classic.yarnpkg.com/en/docs/install)
# -- 下载 Substrate 前端模版并跑起来
cd ..
git clone https://github.com/substrate-developer-hub/substrate-front-end-template front-end-template
cd front-end-template
yarn install
yarn start
接着,访问 http://localhost:8000,你会见到以下页面:
而图中右上方的当下区块生成数也和 Substrate 节点终端所显示的一致 (请确定本机 Substrate 节点在运行着,即上面 target/release/node-template --dev 那句)。如果是这样,那恭喜你,你已成功跑起一个 React 的前端,并成功连接到本机 Substrate 节点上。
如果你打开浏覧器的开发视窗 (Developer Console), 你会注意到一行 console log 说明已连接的远端 socket。
调整远端连接的 Substrate 节点
如果需要调整要连接的终端节点,可打开 src/config/development.json 来指定你的终端。
源码:src/config/development.json
{
"PROVIDER_SOCKET": "ws://<你的 ws/wss 地址>"
}
SubstrateContext 与 useSubstrate
与 Substrate 节点的交互是封装在 React Context 内, 以 Hook 形式供开发使用. 所以首先我们在主要 <App> 外包一层 Context Provider.
源码:src/App.js
//...
import { SubstrateContextProvider, useSubstrate } from './substrate-lib';
import { DeveloperConsole } from './substrate-lib/components';
//...
function Main() {
const [accountAddress, setAccountAddress] = useState(null);
const { apiState, keyring, keyringState } = useSubstrate();
const accountPair =
accountAddress &&
keyringState === 'READY' &&
keyring.getPair(accountAddress);
//...
return (
)
}
export default function App () {
return (
<SubstrateContextProvider>
<Main />
</SubstrateContextProvider>
);
}
在 <SubstrateContextProvider> 的子组件内,就能用 useSubstrate() 来取得整个 Substrate Context, 当中包含了:
socket: 对应现在连接的远端types: Substrate 网络内的自定义结构组keyring: 储存着用户帐号(用户公钥),也开放出接口来为数据和交易签名keyringState: 用户帐号状态,为 [null,'READY','ERROR'] 其中一个api: Polkadot-JS APIapiState: Polkadot-JS API 对远端的连接状态,为 [null,'CONNECTING','READY','ERROR'] 其中一个
我们检查着 apiState 及 keyringState,当它们的值都为 'READY' 时,我们就可以开始读取链上数据。
读取及订阅链上数据 (Queries)
接下来我们会专门讨论 在 Front-end Template 最下面的一个模块 src/TemplateModule.js。
这个模块虽然看似简单,但已包含了读取链上数据,提交交易,及监听事件。这前端模块对应着后端 Substrate 节点的一个模块 Template Pallet (查看Pallet 源码)。它有着:
- 一个存取项
Something - 一个读取接口
something - 一个外部交易接口
do_something
我们继续看回前端源码。先看介面部份:
源码:src/TemplateModule.js
import { useSubstrate } from './substrate-lib';
import { TxButton } from './substrate-lib/components';
//...
function main (props) {
//...
return (
<Grid.Column>
<h1>Template Module</h1>
<Card>
<Card.Content textAlign='center'>
<Statistic
label='Current Value'
value={currentValue}
/>
</Card.Content>
</Card>
<Form>
<Form.Field>
<Input
type='number'
id='new_value'
state='newValue'
label='New Value'
onChange={(_, { value }) => setFormValue(value)}
/>
</Form.Field>
<Form.Field>
<TxButton
accountPair={accountPair}
label='Store Something'
setStatus={setStatus}
type='TRANSACTION'
attrs={{
params: [formValue],
tx: api.tx.templateModule.doSomething
}}
/>
</Form.Field>
<div style={{ overflowWrap: 'break-word' }}>{status}</div>
</Form>
</Grid.Column>
)
}
从上面能看到显示的数值是存在 currentValue (第15行)内,可用 setCurrentValue() 来更改此值。(这是基于 React 的 State Hook。而此 currentValue 的窍门在于它是如何被初始化及修改的。那得跟着看接下来的 useEffect。
useEffect(() => {
let unsubscribe;
api.query.templateModule.something(newValue => {
// The storage value is an Option<u32>
// So we have to check whether it is None first
// There is also unwrapOr
if (newValue.isNone) {
setCurrentValue('<None>');
} else {
setCurrentValue(newValue.unwrap().toNumber());
}
}).then(unsub => {
unsubscribe = unsub;
})
.catch(console.error);
return () => unsubscribe && unsubscribe();
}, [api.query.templateModule]);
api 是从 useSubstrate() 取得的 JS API 接口。 api.query.templateModule.something 就是我们从 Substrate 节点处取得数据的方法。而 api query 的用法法则是:
api.query.<pallet_名字>.<pallet 存储名字>(回调函数)
这是对应着 Substrate 后端的 runtime 有个 TemplateModule 为名字的 pallet 模块。而这模块的存储内有个 something() 的读取函数。因为在 node-template 内是如下定义的:
源码: node-template/pallets/template/src/template.rs (以下是 Rust 语法)
// This module's storage items.
decl_storage! {
trait Store for Module<T: Trait> as TemplateModule {
Something get(fn something): Option<u32>;
}
}
JS API 提供两种读取数据的方法。
- 一是基于 JS Promise 的方法,可以这样读取数值:
javascript const val = await api.query.templateModule.something(); - 另一方法,如果想收听该数值,并每次该值在远端作出变更时都收到回调,则用现在
TemplateModule.js里的写法。源码:src/TemplateModule.jsjavascript let unsubscribe; api.query.templateModule.something(function(val) { // 回调函数 // 在这里设置 UI 里使用的变量。 }).then(unsub => { //取消订阅函数 unsubscribe = unsub; })
最后把取消函数在useEffect()内返回即可。这是 React Effect Hook 的清理方法。
另外值得注意一点是 something 在 Substrate 内是一个 Option<u32> 的格式。因为 Rust 和 Javascript 的数据类型没有一对一映射,因此无符号整数回到 JS 时是以封装的对象呈现。得使用跟着的 .unwrap().toNumber() 来把对象转化回 JS 内的数值。
提交外部交易 (Extrinsics)
接下来看一下这模块是如何提交外部交易,在 Substrate,所有从外部提交的交易都叫 Extrinsics。在这里,我们用一个封装好的组件 <TxButton>。
<TxButton
accountPair={accountPair}
label='Store Something'
setStatus={setStatus}
type='TRANSACTION'
attrs={{
params: [formValue],
tx: api.tx.templateModule.doSomething
}}
/>
这个组件会生成一个按钮。点击就会向 Substrate 远端发送外部交易。需要以下参数:
accountPair: 对交易进行签名的用户帐号(公钥)label:显示在按钮上的文字setStatus:提交交易后的状态更新回调函数type: ['QUERY','TRANSACTION'] 其中一个。如果是作写入交易,则选'TRANSACTION'。attrs: 这里传入一个对象:javascript { //外部交易函数 // 这个参看回 `pallet/template/src/lib.rs` 的名字 tx: api.tx.<模块名字>.<extrinsic 名字> //外部交易函数的输入参数数组 params: [...] }style: React 组件的 styledisabled: [true,false]。如果是true,按钮会进入屏闭状态。
用这个组件,基本上能处理大部份点击按钮来触发的外部交易。在下一篇文章,我们会提到如何运用它底层的 JS API 作直接交易。
帐号管理/签署 (Keyring)
keyring 内包含了你的帐号 (也就是你的公钥),以及这帐号签名所需的函数。用 .getPair(), 以一个字符串作输入参数, 取得 accountPair 对象。
const accountPair = keyring.getPair(accountAddress);
然后就以以下方法来签署你的交易:
api.tx.my_pallet.dispatch_call(params).signAndSend(accountPair, 回调函数)
不过这个逻辑已封装在 <TxButton> 内。 如果你是用 <TxButton> 组件来提交外部交易,就不需要顾虑这事了。
收听自定义类型 (Custom Types)
若你要收听的 pallet 内有自定义类型,则在 Substrate 客户端也需要提供这个自定义类型的结构。这自定义结构可放在 src/config/common.json 内。比如:
源码:src/config/common.json
{
...,
"CUSTOM_TYPES": {
"Price": {
"dollars": "u32",
"cents": "u32",
"currency": "Vec<u8>"
},
}
}
u32, Vec<u8>,这些都是 Rust 里的数值类型。上面例子是 Price 结构内分别有 dollars, cents, 及 currency 的栏位。开发者在 Substrate Node Template 代码内怎样定义这结构,也把这结构复制到前端来。
小结
读到这里,我们已经展示了如何利用 Front-end Template 及其封装好的 API 和组件,连接到 Substrate 节点,读取及收听链上数据,提交外部交易,及收听自定义结构数据。
用以上知识,已经足够制作一个简单的前端应用与 Substrate 网络交互。這方面的知識可與我們上一篇用 Polkadot-JS API 與 Substrate 作交互的知識配搭着使用。作起前端開發起來能更得心應手。
本篇读后有什么意见,欢迎在下方留言。对了,如果这篇文章你已读到这里,可能你会有兴趣再知道两个消息:
- Parity 在亚洲正招聘开发推广及工程师,详情看这里,欢迎报名。
- 想了解更多 Substrate 开发,来 substrate.dev 查看更多吧。
本文介绍了如何使用Substrate Front-end Template创建React应用,包括连接本地Substrate节点、查询和订阅链上数据、提交交易、管理账户以及处理自定义类型。通过实例展示了如何与Substrate节点交互,提供了一种轻松构建前端应用的方法。
2753

被折叠的 条评论
为什么被折叠?



