Vue.js③・コンポーネント

Image from Gyazo

コンポーネントとは

コンポーネント はHTMLを構成する部品のことです. Vue.jsでは,Vue.component()を使ってコンポーネントを定義します. コンポーネントには,複数のHTML要素をまとめたtemplateオプション,データバインディングのためのdataオプション,関数を定義するためのmethodsオプションなどを記述します. コンポーネントとして定義することで,コンポーネントの再利用が可能となり,プログラムの設計が簡単になります. また,propsオプションを利用することで,コンポーネントに任意のデータを渡すことが可能です. 今回は日進市の観光地を紹介するアプリを開発します. 観光地の情報や画像を表示するHTML要素をコンポーネントとして実装します.

CodePenの準備

CodePenにアクセスして,Penを作成し,タイトルを設定しましょう. Penのタイトルは「Chapter10」に設定しましょう.

Image from Gyazo

前回と同様にVue.jsのライブラリをペンに追加してください. Vue.jsのバージョンは2.6.11を採用します.

Image from Gyazo

また,Vue.jsの基本構造も記述しておきます. Vueクラスのインスタンスには,eldatamethodsを指定します. Webアプリのタイトルと説明文も記載しておきましょう.

<!-- HTML -->
<div id="app">
  <h1> {{ title }} </h1>
  <p> {{ description }} </p>
<div>
// JavaScript
let app = new Vue({
  el: "#app",
  data: {
    title: "日進市観光アプリ",
    description: "日進市の観光地を紹介するアプリです.",
  },
  methods: {
  },
})

Image from Gyazo

コンポーネントの作成

日進市の観光情報を表示するためのHTML要素を コンポーネント として定義します. コンポーネントを定義するには,Vue.component()を利用します. コンポーネントには名前を付けることが可能で,ここではpanel-componentに設定しています. コンポーネントには,templateオプションを記述します. templateオプションでは,テンプレート文字列を利用して,コンポーネントで表示するHTMLを記述します. ここでは,マスタッシュ記法を利用して,dataプロパティで定義したnameを表示しています. この他にも,dataオプション,methodsオプションなどを定義することができます. dataオプションで定義する変数は,returnを使って関数として定義する必要があることに注意してください(インスタンス間で変数の競合を避けるため).

// JavaScript
// templateオプションはテンプレート文字列(``)を使って定義
Vue.component("panel-component", {
  template: `
    <div class="panel"> 
      <h3>{{ name }}</h3>
    </div>
  `,
  data(){
    return {
      name: "日進市立図書館",
    }
  },
})

上記のtemplateプロパティで記述したdivタグを装飾するためのpanelクラスのCSSを記述します. 幅(width),背景色(background),パディング(padding),マージン(margin),角丸(border-radius),中央寄せ(text-align)を設定しています.

/* CSS */
.panel{
  width: 300px;
  background: #DDFFDD;
  padding: 10px;
  margin: 10px;
  border-radius: 20px;
  text-align: center;
}

HTMLで上記で定義したコンポーネントを作成します. コンポーネントの名前であるpanel-componentを要素名として記述するだけです. templateプロパティで記述したHTML要素が表示されていることがわかります.

<!-- HTML -->
<div id="app">
  <h1> {{ title }} </h1>
  <p> {{ description }} </p>

  <!-- コンポーネントの作成 -->
  <panel-component></panel-component>

</div>

Image from Gyazo

観光地の名前を表すnameだけでは寂しいので,観光地の写真imageと,観光地の説明文descriptionを追加します. 観光地の写真は,日進市オープンデータミュージアム・画像データサイトで提供されている画像を利用しました. 画像を表示するためのimgタグでは,v-bindディレクティブを利用してsrc属性を設定しています.

// JavaScript
Vue.component("panel-component", {
  template: `
    <div class="panel"> 
      <h3>{{ name }}</h3>
      <p>
        <img v-bind:src="image" height="200px">
      </p>
      <p>{{ description }}</p>
    </div>
  `,
  data(){
    return{
      name: "日進市立図書館",
      image: "https://assets.codepen.io/4660782/nissin-tosyokan.jpg",
      description: "2008年に岡田新一設計事務所の設計による現行館が開館した."
    }
  },
})

Image from Gyazo

キーポイント

  • コンポーネントはVue.component()で定義
  • templateプロパティで記述したHTML要素が表示される

propを利用したデータの引き渡し

コンポーネントとして定義することで,簡単に複数のコンポーネントを並べて表示することができます. しかし,上記の方法では,nameimagedescriptionがコンポーネント内で定義されているため,全く同じ内容のコンポーネントが複数表示されるだけです. 下記の例では,日進市立図書館のコンポーネントが2回表示されています.

<!-- HTML -->
<div id="app">
  <h1> {{ title }} </h1>
  <p> {{ description }} </p>

  <!-- 複数のコンポーネントを作成 -->
  <panel-component></panel-component>
  <panel-component></panel-component>

</div>

Image from Gyazo

コンポーネントごとに異なる情報を保持させるにはpropsオプションを利用します. propsで定義された変数は,panel-componentタグの属性として,コンポーネントに渡すことができます. このとき,{変数名: 型}のように,変数の型を合わせて記述することが奨励されています. 今回はいずれも文字列Stringとして,変数のデータを受け取ります.

// JavaScript
Vue.component("panel-component", {
  template: `
    <div class="panel"> 
      <h3>{{ name }}</h3>
      <p>
        <img v-bind:src="image" height="200px">
      </p>
      <p>{{ description }}</p>
    </div>
  `,
  // propsで変数を受け取る
  props: {
    name: String,
    image: String,
    description: String,
  }
})

HTMLでは,上記のpropsで定義した3つの変数を,属性として設定します. このように,コンポーネントは共通の枠組みで,異なる情報を表示させることができます.

<!-- HTML -->
<div id="app">
  <h1> {{ title }} </h1>
  <p> {{ description }} </p>

  <!-- 異なる情報を保持するコンポーネントを作成 -->
  <panel-component name="日進市立図書館" image="https://assets.codepen.io/4660782/nissin-tosyokan.jpg" description="2008年に岡田新一設計事務所の設計による現行館が開館した."></panel-component>
  <panel-component name="五色園" image="https://assets.codepen.io/4660782/goshikien.jpg" description="日本で唯一の宗教公園.浄土真宗系単立寺院の大安寺がある."></panel-component>
</div>

Image from Gyazo

キーポイント

  • コンポーネントで受け取りたい変数はpropsオプションで宣言
  • HTMLタグの属性としてコンポーネントに変数のデータを渡す

繰り返しを利用したコンポーネントの作成

配列の要素を繰り返して取り出すことが可能なv-forディレクティブを利用して, 複数のコンポーネントを定義してみましょう.

まずは,Vueクラスのdataプロパティにpanelsを定義し,3箇所の観光情報(日進市立図書館,五色園,岩崎城)を配列として代入します.

// JavaScript
panels = [
  {
    "name": "日進市立図書館",
    "image": "https://assets.codepen.io/4660782/nissin-tosyokan.jpg",
    "description": "2008年に岡田新一設計事務所の設計による現行館が開館した."
  },
  {
    "name": "五色園",
    "image": "https://assets.codepen.io/4660782/goshikien.jpg",
    "description": "日本で唯一の宗教公園.浄土真宗系単立寺院の大安寺がある."
  },
  {
    "name": "岩崎城",
    "image": "https://assets.codepen.io/4660782/iwasaki.jpg",
    "description": "岩崎城は日進市の戦国時代の様子を今に伝える貴重な城址."
  }
]

let app = new Vue({
  el: "#app",
  data: {
    title: "日進市観光アプリ",
    description: "日進市の観光地を紹介するアプリです.",
    panels: panels,
  },
  methods: {
  },
}

HTMLでは,v-forディレクティブを利用して,配列panelsから要素panelを取り出し, コンポーネントに,panel.namepanel.imagepanel.descriptionを属性として渡しています. ここでdisplay:flexは,子要素を横並びで表示するためのCSSです. この結果,3箇所の観光情報(日進市立図書館,五色園,岩崎城)が横並びで表示されます.

<!-- HTML -->
<div id="app">
  <h1> {{ title }} </h1>
  <p> {{ description }} </p>

  <div style="display:flex">
    <div v-for="(panel, index) in panels">
      <panel-component v-bind:name="panel.name" v-bind:image="panel.image" v-bind:description="panel.description"></panel-component>
    </div>
  </div>
</div>

Image from Gyazo

フォントアイコンを利用したページャー

ページャー とはウェブページのページを切り替える機能のことを指します. ここでは,全ての観光情報を表示するのではなく,1ページに1箇所の観光情報を表示させることにします. また,ボタンをクリックすることで,ページが切り替わるように実装します.

ページャーのアイコンには,ウェブフォントを利用することにします. ここでは,Font Awesomeというサービスを利用します. Pen SettingのCSSで,Font AwesomeのスタイルシートをCDNを追加しておきます.

Image from Gyazo

表示するページを制限するために,v-ifディレクティブを利用して, panelsの要素番号がnumbernumber+1であるときだけ表示させています.

また,ページャーの役割を担う2種類のボタンを設置しています. ボタンには,Font Awesomeを利用して,矢印形のフォントアイコン(fa-arrow-leftfa-arrow-right)を表示しています. また,v-onディレクティブを利用して,ボタンのクリック時にprevPanel()nextPanel()が呼び出されるように設定します.

<!-- HTML -->
<div id="app">
  <h1> {{ title }} </h1>
  <p> {{ description }} </p>

  <div style="display:flex">
    <div v-for="(panel, index) in panels">
	  <!-- v-ifディレクティブを利用して表示する観光情報を制限 -->
      <panel-component v-if="(index == number)" v-bind:name="panel.name" v-bind:image="panel.image" v-bind:description="panel.description"></panel-component>
    </div>
  </div>

  <div>
	<!-- フォントアイコンを利用したページャー -->
    <button v-on:click="prevPanel()"><i class="fas fa-arrow-left"></i></button>
    {{ number+1 }}
    <button v-on:click="nextPanel()"><i class="fas fa-arrow-right"></i></button>
  </div>
</div>

JavaScriptでは,dataオプションでnumberを宣言します. また,methodsオプションで,nextPanel()prevPanel()を定義します.

// JavaScript
let app = new Vue({
  el: "#app",
  data: {
    title: "日進市観光アプリ",
    description: "日進市の観光地を紹介するアプリです.",
    panels: panels,
    number: 0, // 表示するpanelsの要素番号
  },
  methods: {
    nextPanel(){
	  // 番号を1だけ増やす
      this.number = this.number + 1;
    },
    prevPanel(){
	  // 番号を1だけ減らす
      this.number = this.number - 1;
    }
  },
})

Image from Gyazo

キーポイント

  • フォントアイコンはFont Awesomeが便利

アプリの確認

See the Pen Chapter10 課題 by Naoto Mukai (@nmukai) on CodePen.

課題

観光地のURLの情報を追加し,クリックしたら対応するページに遷移するリンクを設置してください.

Image from Gyazo

課題を完成させたら,Penの ZIPファイルリンク を提出してください. 提出方法は初回のWebアプリの開発を参考にしてください.

参考書籍

愛知県名古屋市にある椙山女学園大学 文化情報学部 向研究室の公式サイトです. 専門は情報科学であり,人工知能やデータベースなどの技術要素を指導しています. この公式サイトでは,授業で使用している教材を公開すると共に, ベールに包まれた女子大教員のミステリアスな日常を4コマ漫画でお伝えしていきます. サイトに関するご意見やご質問はFacebookまたはTwitterでお問い合わせください.