面向初学者的Vue.js课程11:选项卡,全局事件总线

今天,在结束本Vue基础知识教程的第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。初学者第9课JS:自定义事件

初学者第10课的Vue.js:表单



本课目的



我们希望在应用程序页面上有一个标签,其中一个标签允许访问者撰写有关产品的评论,另一个标签允许访问者查看现有的评论。



初始码



这是在此工作阶段文件内容的外观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);
    }
  }
})


这就是应用程序现在的样子。





申请页面



任务



当前,评论和用于提交评论的表单彼此相邻显示在页面上。这是一个相当有效的结构。但是随着时间的推移,预计越来越多的评论会出现在页面上。这意味着用户可以更方便地与自己选择显示表单或评论列表的页面进行交互。



问题的解决



为了解决我们的问题,我们可以向页面添加选项卡系统。其中一个标题为的Reviews将显示评论。第二个标题为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-fortabs创建一个元素<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;
}


如果我们用简单的语言解释上面的构造,那么结果表明,在类等于activeTab的情况下,为类指定的样式将应用到选项卡由于写入了用户刚刚单击的选项卡名称,因此样式将专门应用于活动选项卡。 换句话说,当用户单击第一个选项卡时,将定位,相同的内容将被写入结果,该样式将应用于第一个选项卡 现在页面上的标签标题将如下所示。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-tabs使用product以下建筑模板



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


现在,让我们考虑一下,如果用户单击选项卡的标题,则需要在页面上显示什么Make a Review当然,这是一种提交反馈的表格。为了准备项目以进行进一步的工作,让我们将组件连接代码product-review从组件模板product传输到template 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是在component中声明的方法product。如果组件product-review(这是该组件的子组件product)生成一个事件,则应该调用它review-submitted



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


这就是在将上述代码片段传输到组件之前,一切工作的方式product-tabs现在,组件product子组件product-tabsproduct-review现在它不是“子”组件product,而是它的“孙子”。



现在,我们的代码旨在product-review与父组件进行交互但是现在它不再是一个组成部分product结果,为了使表单正常工作,我们需要重构项目代码。



重构项目代码



为了确保孙子组件与其“祖父母”的通信,或为了在相同级别的组件之间建立通信,通常使用一种称为全局事件总线的机制。



全局事件总线是可用于在组件之间传递信息的通信通道。实际上,它只是创建的Vue实例,而没有向其传递带有选项的对象。让我们创建一个事件总线:



var eventBus = new Vue()


此代码将转到文件的顶层main.js



如果您将事件总线视为总线,则可能会更容易理解这个概念。它的乘客是某些组件发送给其他组件的数据。在我们的案例中,我们正在谈论将有关其他组件生成的事件的信息传输到一个组件。也就是说,我们的“总线”将在组件之间product-review移动product,携带已提交表单的信息并将表单数据从product-review传送到product



现在在componentproduct-review的method中onSubmit,有这样一行:



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


让我们用下一个替换它,eventBus改为使用this



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


之后,您不再需要侦听review-submittedcomponent event 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-submittedproduct函数可以在组件中变成这样的位置mounted



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


这个功能是什么?这是一个生命周期挂钩,在将组件安装在DOM中之后将调用一次。现在,在product安装组件之后,它将等待事件发生review-submitted生成此类事件后,该事件中传递的内容将添加到组件数据中,即- productReview



如果您现在尝试使用该表单对产品进行评论,那么该评论会显示在应有的位置。





表单可以正常工作



事件总线不是交流组件的最佳解决方案



尽管经常使用事件总线,并且虽然可以在各种项目中找到它,但是请记住,对于连接应用程序组件的问题,这并不是最佳的解决方案。



随着应用程序的增长,基于Vuex的状态管理系统可能会派上用场它是一个应用程序状态管理模式和库。



作坊



在项目中添加标签ShippingDetails,分别显示采购的交付成本和商品信息。



  • 这是可以用来解决此问题模板。
  • 这是解决问题的方法。


结果



这是您在本教程中学到的:



  • 您可以使用条件渲染工具来组织选项卡的机制。
  • , 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