初心者向けVue.jsレッスン11:タブ、グローバルイベントバス

今日は、このVue Fundamentalsチュートリアルを終了する11番目のレッスンで、タブを使用してアプリページのコンテンツを整理する方法について説明します。ここでは、グローバルイベントバス(アプリケーション内でデータを転送するための単純なメカニズム)について説明します。







Vue.js初心者レッスン1:インスタンスVue

初心者向けVue.js、レッスン2:バインディング属性

Vue.js初心者レッスン3:条件付きレンダリング

Vue.js初心者レッスン4:リストレンダリング

Vue .js初心者向けレッスン5:イベント処理

Vue.js初心者向けレッスン6:クラスとスタイルのバインド

Vue.js初心者向けレッスン7:計算されたプロパティ

Vue.js初心者向けレッスン8:コンポーネント

Vue。初心者向けjsレッスン9:カスタムイベント

初心者向けVue.jsレッスン10:フォーム



レッスンの目的



アプリケーションページにタブを配置したいと考えています。1つは訪問者が製品に関するレビューを書くことができ、もう1つは既存のレビューを表示することができます。



初期コード



これは、ファイルのコンテンツが作業のこの段階でどのように見えるかですindex.html



<div id="app">
  <div class="cart">
    <p>Cart({{ cart.length }})</p>
  </div>

  <product :premium="premium" @add-to-cart="updateCart"></product>
</div>


ではmain.js、次のコードがあります:



Vue.component('product', {
  props: {
    premium: {
      type: Boolean,
      required: true
    }
  },
  template: `
  <div class="product">

    <div class="product-image">
      <img :src="image" />
    </div>

    <div class="product-info">
      <h1>{{ title }}</h1>
      <p v-if="inStock">In stock</p>
      <p v-else>Out of Stock</p>
      <p>Shipping: {{ shipping }}</p>

      <ul>
        <li v-for="(detail, index) in details" :key="index">{{ detail }}</li>
      </ul>
      <div
        class="color-box"
        v-for="(variant, index) in variants"
        :key="variant.variantId"
        :style="{ backgroundColor: variant.variantColor }"
        @mouseover="updateProduct(index)"
      ></div>

      <button
        @click="addToCart"
        :disabled="!inStock"
        :class="{ disabledButton: !inStock }"
      >
        Add to cart
      </button>

    </div>

    <div>
      <h2><font color="#3AC1EF">Reviews</font></h2>
      <p v-if="!reviews.length">There are no reviews yet.</p>
      <ul>
        <li v-for="review in reviews">
        <p>{{ review.name }}</p>
        <p>Rating: {{ review.rating }}</p>
        <p>{{ review.review }}</p>
        </li>
      </ul>
    </div>

    <product-review @review-submitted="addReview"></product-review>   
  
    </div>
  `,
  data() {
    return {
      product: 'Socks',
      brand: 'Vue Mastery',
      selectedVariant: 0,
      details: ['80% cotton', '20% polyester', 'Gender-neutral'],
      variants: [
        {
          variantId: 2234,
          variantColor: 'green',
          variantImage: './assets/vmSocks-green.jpg',
          variantQuantity: 10
        },
        {
          variantId: 2235,
          variantColor: 'blue',
          variantImage: './assets/vmSocks-blue.jpg',
          variantQuantity: 0
        }
      ],
      reviews: []
    }
  },
    methods: {
      addToCart() {
        this.$emit('add-to-cart', this.variants[this.selectedVariant].variantId);
      },
      updateProduct(index) {
        this.selectedVariant = index;
      },
      addReview(productReview) {
        this.reviews.push(productReview)
      }
    },
    computed: {
      title() {
        return this.brand + ' ' + this.product;
      },
      image() {
        return this.variants[this.selectedVariant].variantImage;
      },
      inStock() {
        return this.variants[this.selectedVariant].variantQuantity;
      },
      shipping() {
        if (this.premium) {
          return "Free";
        } else {
          return 2.99
        }
      }
    }
})

Vue.component('product-review', {
  template: `
    <form class="review-form" @submit.prevent="onSubmit">

      <p v-if="errors.length">
        <b>Please correct the following error(s):</b>
        <ul>
          <li v-for="error in errors">{{ error }}</li>
        </ul>
      </p>

      <p>
        <label for="name">Name:</label>
        <input id="name" v-model="name">
      </p>
      
      <p>
        <label for="review">Review:</label>      
        <textarea id="review" v-model="review"></textarea>
      </p>
      
      <p>
        <label for="rating">Rating:</label>
        <select id="rating" v-model.number="rating">
          <option>5</option>
          <option>4</option>
          <option>3</option>
          <option>2</option>
          <option>1</option>
        </select>
      </p>
          
      <p>
        <input type="submit" value="Submit">  
      </p>    

    </form>

  `,
  data() {
    return {
      name: null,
      review: null,
      rating: null,
      errors: []
    }
  },
  methods: {
    onSubmit() {
      if(this.name && this.review && this.rating) {
        let productReview = {
          name: this.name,
          review: this.review,
          rating: this.rating
        }
        this.$emit('review-submitted', productReview)
        this.name = null
        this.review = null
        this.rating = null
      } else {
        if(!this.name) this.errors.push("Name required.")
        if(!this.review) this.errors.push("Review required.")
        if(!this.rating) this.errors.push("Rating required.")
      }
    }
  }
})

var app = new Vue({
  el: '#app',
  data: {
    premium: true,
    cart: []
  },
  methods: {
    updateCart(id) {
      this.cart.push(id);
    }
  }
})


これは、アプリケーションが現在どのように見えるかです。





申し込みページ



仕事



現在、レビューとレビューの送信に使用されるフォームは、隣り合ったページに表示されています。これはかなり機能する構造です。しかし、時間の経過とともに、ますます多くのレビューがページに表示されると予想されます。これは、ユーザーが選択したフォームまたはレビューのリストを表示するページを操作する方が便利であることを意味します。



問題の解決策



問題を解決するために、タブのシステムをページに追加できます。そのうちの1つには、タイトルが付いており、Reviewsレビューが表示されます。タイトル付きの2つ目は、Make a Reviewレビューを送信するためのフォームを表示します。



タブシステムを実装するコンポーネントの作成



コンポーネントを作成することから始めましょうproduct-tabsコンポーネントの視覚的表現の下部に表示されproductます。時間の経過とともに、ページにレビューとフォームのリストを表示するために現在使用されているコードが置き換えられます。



Vue.component('product-tabs', {
  template: `
    <div>
      <span class="tab" v-for="(tab, index) in tabs" :key="index">{{ tab }}</span>
    </div>
  `,
  data() {
    return {
      tabs: ['Reviews', 'Make a Review']      
    }
  }
})


現在、これは空白のコンポーネントであり、まもなく完成する予定です。とりあえず、このコードで何が示されているかを簡単に説明しましょう。



コンポーネントデータにはtabs、タブヘッダーとして使用する文字列を含む配列があります。コンポーネントテンプレートは、v-for各配列tabs要素に<span>対応する文字列含む要素作成する構造使用ます。作業のこの段階でこのコンポーネントを形成するものは、次のようになります。





作業の初期段階でのproduct-tabsコンポーネント



。目標を達成するには、どのタブがアクティブであるかを知る必要があります。したがって、コンポーネントデータにプロパティを追加しましょうselectedTabタブタイトルのクリックに応答するイベントハンドラーを使用して、このプロパティの値を動的に設定します。



@click="selectedTab = tab"


プロパティには、タブのタイトルに対応する行が書き込まれます。



つまり、ユーザーがタブをクリックするReviewsselectedTab、文字列がに書き込まれReviewsます。タブをクリックすると、Make a ReviewそのselectedTab行が含まれMake a Reviewます。



これは、完全なコンポーネントコードがどのようになるかを示しています。



Vue.component('product-tabs', {
  template: `
    <div>    
      <ul>
        <span class="tab" 
              v-for="(tab, index) in tabs" 
              @click="selectedTab = tab"
        >{{ tab }}</span>
      </ul> 
    </div>
  `,
  data() {
    return {
      tabs: ['Reviews', 'Make a Review'],
      selectedTab: 'Reviews'  //    @click
    }
  }
})


クラスをアクティブなタブにバインドする



タブを使用するインターフェースを使用するユーザーは、どのタブがアクティブであるかを知っている必要があります。タブ名の表示に使用される要素へのクラスバインディング使用して、同様のメカニズムを実装でき<span>ます。



:class="{ activeTab: selectedTab === tab }"


ここで使用されるクラスのスタイルを定義するCSSファイルは次のとおりですactiveTabこれはこのスタイルがどのように見えるかです:



.activeTab {
  color: #16C0B0;
  text-decoration: underline;
}


そしてここにクラススタイルがありtabます:



.tab {
  margin-left: 20px;
  cursor: pointer;
}


上記の構成を簡単な言葉で説明すると、equalsactiveTabの場合、クラスに指定されたスタイルがタブ適用されることがわかりますユーザーがクリックしたばかりのタブ名前が書き込まれるため、スタイルはアクティブなタブに特に適用されます。 最初のタブをユーザーがクリックすると、換言すれば、配置されます、同じことがに書き込まれますその結果、スタイルは最初のタブに適用されます これで、ページのタブタイトルは次のようになります。selectedTabtabselectedTab.activeTab



tabReviewsselectedTab.activeTab









アクティブなタブの強調表示されたタイトル



この段階ではすべてが期待どおりに機能しているよう見えるので、次に進むことができます。



コンポーネントテンプレートでの作業



どのタブがアクティブなタブであるかをユーザーに伝えることができるので、コンポーネントの作業を続けることができます。つまり、テンプレートを完成させ、各タブがアクティブ化されたときにページに正確に何が表示されるかを説明します。



ユーザーがタブをクリックした場合に何を表示するかを考えてみましょうReviewsもちろん、これは製品レビューです。したがって、我々は、コンポーネントにコンポーネントのテンプレートからのレビューを表示するためのコードを移動しますproductテンプレートproduct-tabs、タブのヘッダーを表示するために使用される建設以下、このコードを配置します。これは、コンポーネントテンプレートが現在どのように見えるかproduct-tabsです:



template: `
  <div>    
    <ul>
      <span class="tab"
            :class="{ activeTab: selectedTab === tab }" 
            v-for="(tab, index) in tabs" 
            @click="selectedTab = tab"
      >{{ tab }}</span>
    </ul> 
    <div>
      <p v-if="!reviews.length">There are no reviews yet.</p>
      <ul>
        <li v-for="review in reviews">
        <p>{{ review.name }}</p>
        <p>Rating: {{ review.rating }}</p>
        <p>{{ review.review }}</p>
        </li>
      </ul>
    </div>
  </div>
`


レビューのリストの上に<h2><font color="#3AC1EF">タイトルを表示する必要がなくなったため 、タグ削除したことに注意してくださいReviewsこのタイトルの代わりに、対応するタブのタイトルが表示されます。



ただし、テンプレートコードを移動するだけでは、フィードバックを提供するのに十分ではありません。reviewsレビューの表示にデータが使用される配列は、コンポーネントデータの一部として保存されますproductコンポーネントのproduct-tabs小道具メカニズムを使用して、この配列をコンポーネントに渡す必要があります。作成時に使用されるオプションを使用して、オブジェクトにproduct-tabs以下を追加しましょう



props: {
  reviews: {
    type: Array,
    required: false
  }
}


テンプレートで次の構成を使用して、コンポーネントreviewsからコンポーネントproduct 配列渡します。product-tabsproduct



<product-tabs :reviews="reviews"></product-tabs>


次に、ユーザーがタブのタイトルをクリックした場合にページに何を表示する必要があるかを考えてみましょうMake a Reviewもちろん、これはフィードバックを送信するためのフォームです。プロジェクトをさらに作業する準備をするために、コンポーネント接続コードproduct-reviewをコンポーネントテンプレートproductからテンプレート転送しましょうproduct-tabs<div>レビューのリストを表示するために使用される要素の下に次のコードを配置しましょう



<div>
  <product-review @review-submitted="addReview"></product-review>
</div>


ここでアプリケーションページを見ると、レビューのリストとフォームがタブヘッダーの下に表示されていることがわかります。





ページでの作業の中間段階



この場合、見出しをクリックすると、選択につながりますが、ページの他の要素にはまったく影響しません。さらに、フォームを使おうとすると、正常に動作しなくなったことがわかります。これらはすべて、アプリケーションに加えた変更の予想される結果です。作業を続けて、プロジェクトを稼働状態にしましょう。



ページ要素の条件付き表示



コンポーネントテンプレートの主要な要素を準備したproduct-tabsので、ユーザーがクリックしたタブタイトルに基づいてさまざまなページ要素を表示できるシステムを作成します。



コンポーネントデータにはすでにプロパティがありますselectedTabこれをディレクティブで使用して、v-show各タブに属するものを条件付きでレンダリングできます。



したがって、<div>レビューのリストを生成するためのコードを含むタグに、次の構造を追加できます。



v-show="selectedTab === 'Reviews'"


彼女のおかげで、タブがアクティブになるとレビューのリストが表示されますReviews



同様に、<div>コンポーネント接続コードを含むタグにproduct-review以下追加します。



v-show="selectedTab === 'Make a Review'"


これにより、タブがアクティブな場合にのみフォームが表示されますMake a Review



これは、コンポーネントテンプレートが現在どのように見えるかproduct-tabsです:



template: `
  <div>    
    <ul>
      <span class="tab"
            :class="{ activeTab: selectedTab === tab }" 
            v-for="(tab, index) in tabs" 
            @click="selectedTab = tab"
      >{{ tab }}</span>
    </ul> 
    <div v-show="selectedTab === 'Reviews'">
      <p v-if="!reviews.length">There are no reviews yet.</p>
      <ul>
        <li v-for="review in reviews">
        <p>{{ review.name }}</p>
        <p>Rating: {{ review.rating }}</p>
        <p>{{ review.review }}</p>
        </li>
      </ul>
    </div>
    <div v-show="selectedTab === 'Make a Review'">
      <product-review @review-submitted="addReview"></product-review>
    </div>
  </div>
`


ページを見てタブをクリックすると、作成したメカニズムが正しく機能していることを確認できます。





タブをクリックすると、一部のアイテムが非表示になり、他のアイテムが表示されます。



フォームからフィードバックを送信しても機能しません。問題を調査して修正しましょう。



フィードバックの送信に関する問題の解決



ここでブラウザ開発者ツールコンソールを見ると、警告が表示されます。





コンソールの警告



明らかに、システムはメソッドを検出できませんaddReview。彼はどうなりましたか?



この質問に答えるにaddReviewは、それがコンポーネントで宣言されているメソッドであることを忘れないでproductください。コンポーネントproduct-review(およびこれはコンポーネントの子コンポーネントですproduct)がイベントを生成する場合に呼び出す必要がありますreview-submitted



<product-review @review-submitted="addReview"></product-review>


これは、上記のコードスニペットをコンポーネントに転送する前にすべてがどのように機能したかですproduct-tabsそして今、コンポーネントproduct子コンポーネントproduct-tabsであり、product-review今では「子」、コンポーネントproductではなく、その「孫」です。



これで、コードはproduct-review親コンポーネントと対話するように設計されましたしかし、今ではコンポーネントではなくなりましたproductその結果、フォームが正しく機能するためには、プロジェクトコードをリファクタリングする必要があることがわかりました。



プロジェクトコードのリファクタリング



孫コンポーネントとその「祖父母」との通信を確保するため、または同じレベルのコンポーネント間の通信を確立するために、グローバルイベントバスと呼ばれるメカニズムがよく使用されます。



グローバルイベントバスは、コンポーネント間で情報を転送するために使用できる通信チャネルです。実際、オプション付きのオブジェクトを渡さずに作成されるのは、Vueインスタンスだけです。イベントバスを作成しましょう:



var eventBus = new Vue()


このコードはファイルのトップレベルに移動しますmain.js



イベントバスをバスと考えると、この概念を理解しやすいかもしれません。その乗客は、一部のコンポーネントが他のコンポーネントに送信するデータです。この例では、他のコンポーネントによって生成されたイベントに関する情報を1つのコンポーネントに転送することについて話しています。つまり、「バス」はコンポーネント間product-review移動productし、フォームが送信されたという情報を伝達し、フォームデータをからproduct-review配信productます。



これで、コンポーネントproduct-reviewのメソッドにonSubmit、次のような行があります。



this.$emit('review-submitted', productReview)


eventBus代わり に使用して、次のものに置き換えましょうthis



eventBus.$emit('review-submitted', productReview)


その後、review-submittedコンポーネントイベントをリッスンする必要はありませんproduct-reviewしたがって、コンポーネントテンプレート内のこのコンポーネントのコードを次のように変更product-tabsします。



<product-review></product-review>


これで、productメソッドをコンポーネントから削除できますaddReview代わりに、次の構造を使用します。



eventBus.$on('review-submitted', productReview => {
  this.reviews.push(productReview)
})


以下のコンポーネントでの使用方法について説明しますが、ここでは、その中で何が起こるかを簡単に説明します。この構成はeventBus、イベントを生成するときにreview-submitted、このイベントで渡されたデータ(つまり、- productReview)を取得して、reviewsコンポーネント配列に配置する必要があることを示していますproduct実際、これはaddReview、私たちがもはや必要としない方法これまでに行われたことと非常に似ています。上記のコードスニペットは矢印関数を使用していることに注意してください。この瞬間は、より詳細な報道に値します。



矢印機能を使用する理由



ここでは、ES6で導入され矢印関数構文使用しています重要なのは、矢印関数のコンテキストが親コンテキストにバインドされているということです。つまり、この関数内でthisキーワードthisを使用すると、矢印関数を含むエンティティに対応するキーワードと同等になります。



このコードは矢印関数を使用せずに書き直すことができますが、バインディングを整理する必要がありますthis



eventBus.$on('review-submitted', function (productReview) {
  this.reviews.push(productReview)
}.bind(this))


プロジェクトの完了



ほぼ目標を達成しました。あとは、イベントへの応答を提供するコードの場所を見つけるだけreview-submittedです。product関数は、コンポーネント内でそのような場所になる可能性がありますmounted



mounted() {
  eventBus.$on('review-submitted', productReview => {
    this.reviews.push(productReview)
  })
}


この機能は何ですか?これは、コンポーネントがDOMにマウントされた後に1回呼び出されるライフサイクルフックです。これで、コンポーネントproductがマウントされた後、イベントが発生するのを待ちますreview-submittedこのようなイベントが生成された後、このイベントで渡されたものがコンポーネントデータに追加されますproductReviewつまり、-です。



フォームを使用して製品に関するレビューを残そうとすると、このレビューが本来あるべき場所に表示されていることがわかります。





フォームは正常に機能します



イベントバスは、コンポーネントを通信するための最適なソリューションではありません



イベントバスはよく使用され、さまざまなプロジェクトで見つかる可能性がありますが、これはアプリケーションコンポーネントの接続の問題に対する最善の解決策にはほど遠いことに注意してください。



アプリケーションが大きくなるにつれて、Vuexに基づく状態管理システムが非常に便利になります。これは、アプリケーションの状態管理パターンおよびライブラリです。



ワークショップ



タブShippingをプロジェクトに追加しますDetails。これらはそれぞれ、購入品の配送コストと商品に関する情報を表示します。



  • これは、この問題を解決するために使用できるテンプレートです。
  • これが問題解決策です。


結果



このチュートリアルで学んだことは次のとおりです。



  • 条件付きレンダリングツールを使用して、タブのメカニズムを整理できます。
  • , Vue, .
  • — . . — Vuex.


このVueコースを受講した後、あなたが望むものを学び、このフレームワークについてもっと多くの新しくて興味深いことを学ぶ準備ができていることを願っています。



このコースを修了したばかりの方は、感想をお聞かせください。



Vue.js初心者レッスン1:インスタンスVue

初心者向けVue.js、レッスン2:バインディング属性

Vue.js初心者レッスン3:条件付きレンダリング

Vue.js初心者レッスン4:リストレンダリング

Vue初心者向け.jsレッスン5:イベント処理

初心者向けVue.js、レッスン6:クラスとスタイルのバインド

初心者向けVue.js、レッスン7:計算プロパティ

初心者向けのVue.js、レッスン8:コンポーネント

初心者向けのVue.js、レッスン9:カスタムイベント

初心者向けのVue.js、レッスン10:フォーム






All Articles