💊

IonicのIonItemは、divタグにもaタグにもbuttonタグにもなりうる万能コンポーネント

2020/12/18に公開

Ionicを使うにIonItem(ion-item)はよく使うコンポーネントです。この詳しい構成についてみていきましょう。

基本的な使い方

基本的な利用方法は以下の通りです。

<ion-item>
  <ion-label>Item</ion-label>
</ion-item>

この場合、IonItemはCustom Elementsとして以下のタグを展開しています。

<ion-item>
  <div>
    <slot name="start"></slot>
    <div class="item-inner">
    <div class="input-wrapper">
      <slot><ion-label>Item</ion-label></slot>  // ion-labelはslotが指定されていないためにここに格納される
    </div>
    <slot name="end"></slot>
  </div>
</ion-item>

(厳密にはshadow-rootなので、 host 以下に入ったり、入れ子になってる ion-label はshadow-rootの外に出るのですが、そこは割愛します)

で、ここで注目して欲しいのは ion-item > div です。何も指定してなければ、これは汎用コンテナである div がくるのですが、IonItemは持っている属性によってこの値を変更します。

aタグに変化させる

https://github.com/ionic-team/ionic-framework/blob/master/core/src/components/item/item.tsx#L258 を見てもらえばわかるのですが、上記のdivタグは以下の条件分岐によって定められています。

const TagType = clickable ? (href === undefined ? 'button' : 'a') : 'div' as any;

clickable は、以下のコードで定められています。

private isClickable(): boolean {
  return (this.href !== undefined || this.button);
}

this.hrefは、属性にhrefがついてる場合の値の受け渡しですので、つまりは ion-item[href=http://zenn.div]と言うコンポーネントだった場合、 isClickable はtrueを返して、 TagType にはaが入ります。そして、属性は TagType にそのまま受け渡されることとなります。

つまり

<ion-item href="http://zenn.div">
  <ion-label>Item</ion-label>
</ion-item>

を書いた場合、以下のように展開されるわけです。

<ion-item>
  <a href="http://zenn.div">
    <slot name="start"></slot>
    <div class="item-inner">
    <div class="input-wrapper">
      <slot><ion-label>Item</ion-label></slot>
    </div>
    <slot name="end"></slot>
  </a>
</ion-item>

IonicはWeb ComponentsというWeb標準の技術で作られているのですが、JavaScriptでイベントをハンドリングするだけではなく、HTMLのドキュメンテーションとしての作法を守りながらDOMの展開をしてる様子がわかりますね。ちなみにAngularのRouterLinkも問題なく対応しています。

buttonタグに変化させる

上記をaタグではなくbuttonタグにさせる方法はいくつかあるのですが、ドンピシャなのは、 ion-item[button=true] 属性をつけることですね。そうするとbuttonになります。

これで重要なのは、モバイルデバイスには、シングルタップかダブルタップかを判断するために、aタグとbuttonタグ、 tappable 属性のついたタグを除くと300msのイベント発生の遅延があることです。つまり、原則「要素をクリックしてイベントを発生させるものはdivタグではいけない」のです。

そのため、aタグ(リンク)でないものは、buttonタグである必要があります。

<ion-item button="true" (click)="launchZenn()">
  <ion-label>Item</ion-label>
</ion-item>

とした場合、以下のようになります。

<ion-item>
  <button (click)="launchZenn()">
    <slot name="start"></slot>
    <div class="item-inner">
    <div class="input-wrapper">
      <slot><ion-label>Item</ion-label></slot>
    </div>
    <slot name="end"></slot>
  </button>
</ion-item>

tappable 属性はWeb標準仕様ではなくモバイルデバイスに搭載されてる独自仕様ですので、できるだけWeb標準をサポートしたいですよね。

まとめ

Ionicはサンプルをみてそのまま使ってもそれなりに形になりますが、各コンポーネント様々な属性が用意されています。
これらを使った結果、より魅力的であったり、Web標準に近い仕様でコンポーネントを展開することができるので、ぜひドキュメンテーションを細かく確認して、できればコンポーネントのソースコードも見てみるとより理解が深まると思います。

https://github.com/ionic-team/ionic-framework/blob/master/core/src/components/item/item.tsx

それでは、また。

Discussion