Вторая часть руководства по созданию простого обозревателя блокчейна Эфириума. [ Предыдущий ] В этой части мы создадим компонент для отображения самых последних транзакций, создадим небольшое раскрывающееся меню и реактивный поиск ввода для фильтрации блоков.

Фаза 4: Транзакции

Наш компонент Transactions будет построен аналогично предыдущему компоненту Blocks. Он расширит ReactiveComponent из oo7-react и будет иметь обновление связей в качестве аргумента конструктора super(['bonds']); . Кроме того, нам нужно будет получать транзакции (TXN), потому что блочный компонент дает нам только массив с TXN-хэшами. Библиотека oo7-parity снова дает нам функцию для извлечения транзакции либо по хешу, либо по blockNumber/blockHash inc. индекс в массиве. bonds.transactions(x) .

Итак, в конструкторе мы создаем новый массив this.transactions = [];, который будет инициализирован при загрузке this.state.bonds (облигация возвращенного обещания разрешена).

Мы могли бы вызвать функцию инициализации в методе Reacts componentWillReceiveProps(). Однако нам нужно будет выполнить проверку, если this.state.bonds не определено. И, как и в нашем компоненте Blocks, нам уже нужна эта проверка в render(), чтобы мы могли переместить вызов init() прямо туда.

Это делает настройку нашего компонента очень похожей на Blocks . Мы используем компонент Transaction из parity-reactive-ui с включенными свойствами по умолчанию: TXN-Hash, From, To, Ether.

Примечание. Если компонент Transaction component не включен в parity-reactive-ui, вы можете скопировать его локально из здесь.

Мы также инициализируем TimeBond again, чтобы использовать его позже, чтобы показать, как долго транзакция уже подтверждена. По-прежнему отсутствует метод init(5); , который принимает параметр длины, указывающий, сколько транзакций должно быть извлечено.

Поскольку у нас уже есть некоторые блоки, мы можем перебирать их массив транзакций и вытягивать транзакции, пока не будет достигнута запрашиваемая сумма.

init(length) {
    this.state.bonds.map(b => {
      for (let i=0;i<b.transactions.length;i++) {
        if (this.transactions.length<length) 
         this.transactions
              .push(bonds.transaction(b.transactions[i]));
      }
    })
  }

Однако это все еще может быть довольно непоследовательным, потому что иногда все блоки, загруженные нашим приложением, могут не включать достаточно транзакций, которые мы хотим отобразить. Таким образом, мы могли бы дополнительно вытягивать блоки и повторять вытягивание транзакции, если сумма не достигнута. Мы пропустим это здесь, потому что это будет довольно сложно.

Для работы с транзакциями я бы порекомендовал обратить внимание на функцию паритета trace-API, которая позволяет детально отслеживать и отслеживать транзакции, но не включена по умолчанию из-за высокого энергопотребления.

Наконец, нам нужно сбрасывать массив транзакций каждый раз, когда вызывается init. Мы можем сделать это как раз в начале init : this.transactions = []; .

Давайте еще раз дадим сводку транзакции, показав TX-Hash и время, в течение которого она уже подтверждена. Мы снова будем использовать Bond.all() для соединения связей, а затем вычислим время, когда обе будут готовы. Для отображения TX-хэша мы можем использовать компонент Hash из oo7-react, который сокращает хэш, чтобы сделать его более читаемым.

const computeTimeDiff = ([t1,t2]) => 
                        Math.floor((t1 - t2.getTime()) / 1000);
...
<a>TX# <Hash value={txn.hash}></Hash></a>
<Feed.Date>
 <Rspan> 
  {Bond.all([this.time,bonds.blocks[txn.blockHash].timestamp])
        .map(computeTimeDiff)}s ago
 </Rspan>
</Feed.Date>

Наша контентная часть теперь будет выглядеть примерно так с постоянно обновляемыми транзакциями и блоками:

Фаза 5: Расширение NavBar

Пока что наша панель навигации очень проста. Давайте добавим к нему некоторые функции, чтобы иметь возможность идти дальше. Наше выпадающее меню пока будет состоять только из semantic-ui-react компонентов. Например.

<Menu>
    <Menu.Item>
      Home
    </Menu.Item>
    <Dropdown text='Blockchain' pointing className='link item'>
      <Dropdown.Menu>
        <Dropdown.Header>Blockchain</Dropdown.Header>
        <Dropdown.Item>View Txns</Dropdown.Item>
        <Dropdown.Item>View Pending Txns</Dropdown.Item>
        <Dropdown.Item>View Contract Internal Txns</Dropdown.Item>
        <Dropdown.Item>Cancellations</Dropdown.Item>
        <Dropdown.Divider />
        <Dropdown.Item>
          View Blocks
          <Dropdown.Menu>
            <Dropdown.Item>
              FORKED Blocks (Reorgs)
            </Dropdown.Item>
          </Dropdown.Menu>
        </Dropdown.Item>
        <Dropdown.Item>
          View Uncles
        </Dropdown.Item>
      </Dropdown.Menu>
    </Dropdown>

Не стесняйтесь настраивать свои собственные здесь. Всю информацию для настройки можно найти здесь.

Наш поиск будет реактивным и будет использовать связь для прямой фильтрации нашего списка блоков при поиске адреса. Для этого мы можем использовать InputBond из parity-reactive-ui. Мы создаем дополнительный searchBond в нашем приложении и передаем его нашему компоненту Navbar.

this.searchBond = new Bond();
...
<Navbar bond={this.searchBond}></Navbar>

Панель навигации теперь будет использовать простой ввод, привязанный к данной связи:

const Search = () => (
 <InputBond 
   bond={this.props.bond} 
   placeholder="Search by Address" 
   fluid 
 />
);

Теперь мы можем использовать значение непосредственно в нашем компоненте приложения. Однако может быть лучше фильтровать блоки, когда обещание уже разрешено. поэтому мы передаем ту же связь, что и фильтр, нашему компоненту Blocks.

<Blocks bonds={this.bonds} filter={this.searchBond}></Blocks>

Теперь мы модифицируем нашу функцию карты, чтобы перебирать только те блоки, которые проходят фильтр в нашей функции рендеринга.

const filterBlock = b => 
                    b.hash.startsWith(this.state.filter) || 
                    b.hash.startsWith(this.state.filter,2);
...
this.state.bonds.filter(filterBlock).map((block,i) => {

Если мы сейчас начнем вводить адрес блока, мы сможем отфильтровать наш список. Мы видим, что секунды в обработанных транзакциях перепутались. Итак, давайте добавим фильтр и к нашим метаданным:

<a>{block.transactions.length} txns</a> in 
<Rspan>
 {i === (this.state.bonds.filter(filterBlock).length-1)
  ? Bond.all([block.timestamp,getParent(block).timestamp])
         .map(computeTime)
  : Bond.all([block.timestamp,
              this.state.bonds.filter(filterBlock[i+1].timestamp])
         .map(computeTime)} sec
</Rspan>

Код становится очень запутанным, поэтому давайте рефракторим и объявляем отфильтрованные блоки в начале нашего метода рендеринга else-branch, когда определены связи:

const blocks = this.state.bonds.filter(filterBlocks);

Теперь довольно раздражает обновление блоков, пока мы исследуем один. Давайте отключим обновление, когда наш поиск не пуст. Мы подписались на нашу страницу, чтобы всегда обновлять и получать самую свежую информацию, поэтому с точки зрения дизайна было бы нехорошо, если бы мы каким-то образом отключили наши облигации, чтобы быть в курсе последних событий. Однако мы можем ввести в наш конструктор простую логическую переменную this.update = true;, которая определяет, отображаем ли мы статический снимок наших блоков или недавние.

Теперь мы привязываем функцию к связке поиска в нашем приложении. Эта функция будет вызываться каждый раз при изменении поиска. Назовем его setUpdate(bool) .

this.searchBond.tie(search => this.update 
    ? this.setUpdate(search === '') 
    : '');

В setUpdate мы установим this.update в true, если поиск пуст. Также мы сделаем снимок наших текущих блоков. Поскольку у нас есть только облигации, мы будем использовать Bond.all() для объединения всех промисов, а затем при срабатывании установим наши staticBlocks inc. Обновить.

setUpdate(bool) {
   if (!bool) {
    Bond.all(this.bonds).then(blocks => {
     this.update = bool;
     this.staticBlocks = blocks;
    })
   } else {
    this.update = bool;
   }
 }

Теперь нам нужно только изменить нашу функцию рендеринга, чтобы использовать this.staticBlocks, если обновление неверно:

render() {
  return (<div className={'ui stackable'}>
       <Grid>
        <Grid.Row>
         <Grid.Column>
          <Navbar bond={this.searchBond} checked={this.checked}></Navbar>
         </Grid.Column>
        </Grid.Row>
        {this.update ?
          <Grid.Row centered columns={2}>
            <Grid.Column mobile={16} tablet={16} computer={7}>
          <Blocks bonds={this.bonds} filter={this.searchBond} />
            </Grid.Column>
            <Grid.Column mobile={16} tablet={16} computer={7}>
          <Transactions bonds={this.bonds}></Transactions>
            </Grid.Column>
          </Grid.Row> :
        <Grid.Row centered columns={2}>
         <Grid.Column mobile={16} tablet={16} computer={7}>
          <Blocks bonds={this.staticBlocks} 
                  filter={this.searchBond}></Blocks>
         </Grid.Column>
         <Grid.Column mobile={16} tablet={16} computer={7}>
          <Transactions bonds={this.staticBlocks}></Transactions>
         </Grid.Column>
        </Grid.Row> }
        </Grid>
       </div>);
 }

И вот мы идем, мы можем искать один из блоков из списка без постоянного обновления.

Мы могли бы добавить сюда еще несколько функций, но я думаю, вы получили первое представление о том, как работает API oo7-Bonds и как он может помочь вам создавать идеально обновляемые dApp на лету! Определенно стоит попытаться использовать только низкоуровневые API, такие как parity-js или web3.js.