四、过滤器和混合器

在本章中,我们将展示如何使用过滤器在不更改基础数据的情况下更改屏幕上呈现的内容。我们还将介绍 mixin,这是一种扩展组件并遵守编程枯燥规则的实用方法。

更具体地说,在本章中,我们将讨论以下内容:

  • 使用过滤器:
    • 使用全局和本地筛选器
    • 用过滤器替换条件指令
    • 将过滤器链接在一起
  • 使用 mixin:
    • 避免 mixin 方法中的代码重复
    • 使用数据选项为我们的混音添加更多功能
    • 在 mixin 中使用生命周期挂钩

使用过滤器

过滤器只是一个函数。它接受一些数据(作为参数传递给 filter 函数),并对这些数据执行一些简单的操作。所执行操作的结果将从过滤功能返回,并显示在应用程序的适当位置。需要注意的是,过滤器不会影响底层数据;它们只影响数据在屏幕上的显示方式。

与组件一样,过滤器也可以注册为全局或本地。注册全局筛选器的语法如下所示:

Vue.filter('justAnotherFilter', function(someData1, someData2, someDataN) {
  // the filter function definition goes here (it takes N number of arguments)
});

除了全局注册外,我们还可以在本地注册过滤器,如下所示:

filters: {
  justAnotherFilter(someData1, someData2, someDataN) {
    // the filter function is defined here...
  }
}

正如我们在这里看到的,在本地注册的情况下,过滤器作为一个选项添加到 Vue 组件中。

对学生成绩进行取整的筛选器示例

假设我们有一个朋友是教授,他们需要帮助学生进行测试。学生参加的一种考试,其设置方式总是以十进制数字的形式给出分数。学生在该测试中可以得到的分数范围在 0 到 100 之间。

作为我们的好朋友,我们将制作一个简单的 Vue 应用程序,该应用程序带有一个过滤器,可将十进制分数四舍五入为整数。我们也会站在学生一边犯错,这意味着我们将永远总结结果

此示例的代码可在中找到 https://codepen.io/AjdinImsirovic/pen/MqBNBR

我们的过滤器的函数将非常简单:它将接受一个浮点,并根据接收到的浮点返回一个向上取整的整数。过滤函数将被称为pointsRoundedUp,如下所示:

  filters: {
    pointsRoundedUp(points){
      return Math.ceil(parseFloat(points));
    }
  }

因此,我们的pointsRoundedUp函数从我们应用程序的data()函数中获取points实例,并返回那些包含 JavaScript 内置parseFloat()Math.ceil()函数的points实例,这些函数调用points值。

要在 HTML 中使用过滤器,我们采用以下语法:

{{ points| pointsRoundedUp }}

points值是应用程序中存储的实际数据。pointsRoundedUp是我们用来格式化从 Vue 组件的数据选项接收的数据的过滤器。

一般来说,我们可以说所有过滤器的基本逻辑如下:

{{ data | formattedData }}

这个一般原则可以这样理解:为了格式化返回的数据,我们使用管道符号(|,然后调用该数据的特定过滤器。

让我们检查一下应用程序的完整代码。HTML 将如下所示:

<div id="app">
 <h1>A simple grade-rounding Vue app</h1>
 <p>Points from test: {{ points }}</p>
 <p>Rounded points are: {{ points | pointsRoundedUp }}</p>
</div>

JS 也很简单:

new Vue({
  el: "#app",
  data() {
    return {
      points: 74.44
    }
  },
  filters: {
    pointsRoundedUp(points){
      return Math.ceil(parseFloat(points));
    }
  }
});

应用程序将在屏幕上输出以下内容:

A simple grade-rounding Vue app
Points from test: 74.44
Rounded points are: 75

应用程序现在已完成。

然而,过了一段时间,我们的朋友要求我们帮个忙:根据分数计算学生的分数。最初,我们意识到这只是一个很小的计算,我们可以简单地将其放入条件指令中

更新示例的代码可在此处找到:https://codepen.io/AjdinImsirovic/pen/XPPrEN

基本上,我们在这个新示例中所做的是用几个条件指令扩展 HTML。尽管这解决了这个问题,但我们的 HTML 还是很混乱,而 JS 却没有改变。更新的 HTML 代码如下所示:

<div id="app">
  <h1>A simple grade-rounding Vue app</h1>
  <p>Points from test: {{ points }}</p>
  <p>Rounded points are: {{ points | pointsRoundedUp }}</p>
  <p v-if="points > 90">Final grade: A</p>
  <p v-else-if="points > 80 && points <= 90">Final grade: B</p>
  <p v-else-if="points > 70 && points <= 80">Final grade: C</p>
  <p v-else-if="points > 60 && points <= 70">Final grade: D</p>
  <p v-else-if="points > 50 && points <= 86">Final grade: E</p>
  <p v-else="points <= 50">Final grade: F</p>
</div>

我们的问题解决了。此测试的分数为 94.44,应用程序成功地将以下信息打印到屏幕上:

A simple grade-rounding Vue app
Points from test: 94.44
Rounded points are: 95
Final grade: A

然而,我们意识到我们的 HTML 现在是杂乱无章的。幸运的是,我们可以利用过滤器来减少混乱。

使用筛选器替换条件指令

在本节中,我们将使用过滤器为学生返回正确的分数。

更新后的应用程序代码可在此处找到:https://codepen.io/AjdinImsirovic/pen/LJJPKm

我们对此版本的应用程序 HTML 所做的更改如下:

<div id="app">
  <h1>A simple grade-rounding Vue app</h1>
  <p>Points from test: {{ points }}</p>
  <p>Rounded points are: {{ points | pointsRoundedUp }}</p>
  <p>Final grade: {{ points | pointsToGrade }}</p>
</div>

我们将条件功能移到了 JavaScript 中,即移到了一个名为pointsToGrade的新过滤器中:

new Vue({
  el: "#app",
  data() {
    return {
      points: 84.44
    }
  },
  filters: {
    pointsRoundedUp(points){
      return Math.ceil(parseFloat(points));
    },
    pointsToGrade(points){
      if(points>90) {
        return "A"
      } else if(points>80 && points<=90) {
        return "B"
      } else if(points>70 && points<=80) {
        return "C"
      } else if(points>60 && points<=70) {
        return "D"
      } else if(points>50 && points<=60) {
        return "E"
      } else {
        return "F"
      }
    }
  }
});

为了快速测试我们更新的代码是否有效,我们还将分数更改为 84.44,这将成功地从[T0]过滤器返回 B 级。

然而,并非完全出乎意料的是,我们的朋友再次回来,并要求我们再帮一个忙:再次扩展应用程序。这一次,我们需要以以下格式显示正确格式的学生姓名:

Last name, First name, year of study, grade.

这意味着我们必须用附加功能扩展我们的应用程序。幸运的是,这并不难,因为我们可以使用过滤器的另一个很好的特性:链接。

Vue 中的链接过滤器

我们的应用程序的要求已经更新,现在我们需要在屏幕上显示一些额外的、格式良好的数据。

由于需求发生了变化,我们还需要更新数据。

本节的代码可在此笔中找到:https://codepen.io/AjdinImsirovic/pen/BOOazy

这是更新后的 JavaScript。首先,我们将添加eldata选项:

new Vue({
  el: "#app",
  data() {
    return {
      firstName: "JANE",
      lastName: "DOE",
      yearOfStudy: 1, 
      points: 84.44,
      additionalPoints: 8
    }
  },

仍然在 JS 中,我们将添加过滤器:

  filters: {
    pointsRoundedUp(points){
      return Math.ceil(parseFloat(points));
    },
    pointsToGrade(points){
      if(points>90) {
        return "A"
      }
      else if(points>80 && points<=90) {
        return "B"
      }
      else if(points>70 && points<=80) {
        return "C"
      }
      else if(points>60 && points<=70) {
        return "D"
      }
      else if(points>50 && points<=60) {
        return "E"
      }
      else {
        return "F"
      }
    },
    yearNumberToWord(yearOfStudy){
      // freshman 1, sophomore 2, junior 3, senior 4 
      if(yearOfStudy==1) {
        return "freshman"
      } else if(yearOfStudy==2){
        return "sophomore"
      } else if(yearOfStudy==3){
        return "junior"
      } else if(yearOfStudy==4){
        return "senior"
      } else {
        return "unknown"
      }
    },
    firstAndLastName(firstName, lastName){
      return lastName + ", " + firstName
    },
    toLowerCase(value){
      return value.toLowerCase()
    },
    capitalizeFirstLetter(string) {
        return string.charAt(0).toUpperCase() + string.slice(1);
    }
  }
});

更新后的 HTML 如下所示:

<div id="app">
  <h1>A simple grade-rounding Vue app</h1>
  <p>Points from test: {{ points }}</p>
  <p>Rounded points are: {{ points | pointsRoundedUp }}</p>
  <p>Student info: 
  <!--
  <p>Name: {{ firstName, lastName | firstAndLastName | toLowerCase | capitalizeFirstLetter}}</p>
  -->
  <p>
    Name: 
    {{ lastName | toLowerCase | capitalizeFirstLetter }}, 
    {{ firstName | toLowerCase | capitalizeFirstLetter }}
  </p>
  <p>Year of study: {{ yearOfStudy | yearNumberToWord }}</p>
  <p>Final grade: <strong>{{ points | pointsToGrade }}</strong></p>
</div>

使用这些链式过滤器,我们通过获取数据(出现在所有大写字母中)并通过两个过滤器将其传输到toLowerCasecapitalizeFirstLetter,从而实现了学生姓名的正确格式。

我们还可以看到一个注释掉的段落,显示了一种不成功的方法,即只大写姓氏的第一个字母,而不大写姓氏的第一个字母。这是因为firstAndLastName过滤器在应用时将全名合并为单个字符串。

请注意,过滤器不会缓存,这意味着它们将始终运行,就像方法一样。

有关过滤器的更多信息,请参阅中的官方文档 https://vuejs.org/v2/guide/filters.html

使用 mixin

mixin 是我们在 Vue 代码中抽象出可重用功能的一种方法。mixin 的概念由 Sass 在前端世界流行起来,现在出现在许多现代 JavaScript 框架中。

当我们有一些功能想要跨多个组件重用时,最好使用 mixin。在下面的示例中,我们将创建一个非常简单的 Vue 应用程序,它将在页面上显示两个引导警报。当用户单击任一警报时,浏览器的视口尺寸将注销到控制台。

为了使这个例子起作用,我们需要从引导框架中获得一些简单的 HTML 组件。

有关此引导组件的正式文档可在以下链接中找到:https://getbootstrap.com/docs/4.1/components/alerts/

请务必注意,引导组件和 Vue 组件是不同的东西,不应混淆。

应用程序运行时将产生以下结果:

此示例的代码可在此处找到:https://codepen.io/AjdinImsirovic/pen/jvvybq

构建在不同组件中具有重复功能的简单应用程序

首先,让我们构建简单的 HTML:

<div id="app">
  <div class="container mt-4">
    <h1>{{heading}}</h1>
    <primary-alert></primary-alert>
    <warning-alert></warning-alert>
  </div>
</div>

我们正在使用 Bootstrap 的 CSS 类containermt-4。常规 HTMLh1标记也会获得一些特定于引导的样式。我们还在前面的代码中使用了两个 Vue 组件:primary-alertwarning-alert

在我们的 JavaScript 代码中,我们将这两个组件定义为primaryAlertwarningAlert,然后将它们列在其父组件的components选项中:

const primaryAlert = {
  template: `
    <div class="alert alert-primary" role="alert" v-on:click="viewportSizeOnClick">
      A simple primary alert—check it out!
    </div>`,
    methods: {
    viewportSizeOnClick(){
      const width = window.innerWidth;
      const height = window.innerHeight;
      console.log("Viewport width:", width, "px, viewport height:", height, "px");
    }
  }
}
const warningAlert = {
  template: `
    <div class="alert alert-warning" role="alert" v-on:click="viewportSizeOnClick">
      A simple warning alert—check it out!
    </div>`,
    methods: {
    viewportSizeOnClick(){
      const width = window.innerWidth;
      const height = window.innerHeight; 
      console.log("Viewport width:", width, "px, viewport height:", height, "px");
    }
  }
}

现在,仍然在 JS 中,我们可以指定构造函数:

new Vue({
  el: '#app',
  data() {
    return {
      heading: 'Extracting reusable functionality into mixins in Vue'
    }
  },
  components: {
    primaryAlert: primaryAlert,
    warningAlert: warningAlert
  }
})

要查看这个小应用程序的结果,请打开控制台并单击两个警报组件中的任意一个。控制台输出类似于以下内容:

Viewport width: 930 px, viewport height: 969 px

正如我们在 JavaScript 代码中看到的,我们还在primaryAlertwarningAlert组件的methods选项中定义了viewportSizeOnClick 方法。这种功能上不必要的重复是将其抽象为 mixin 的最佳选择,我们将在下一步做这件事。

用调味品保持干燥

改进后的应用程序代码可在此处找到:https://codepen.io/AjdinImsirovic/pen/NLLgWP

在本例中,虽然我们的 HTML 保持完全相同,但更新后的 JavaScript 代码如下所示:

const viewportSize = {
    methods: {
      viewportSizeOnClick(){
        const width = window.innerWidth;
        const height = window.innerHeight;
        console.log("Viewport width:", width, "px, viewport height:", height, "px");
      }
  } 
}
const primaryAlert = {
  template: `
    <div class="alert alert-primary" role="alert" v-on:click="viewportSizeOnClick">
      A simple primary alert—check it out!
    </div>`,
  mixins: [viewportSize]
}
const warningAlert = {
  template: `
    <div class="alert alert-warning" role="alert" v-on:mouseenter="viewportSizeOnClick">
      A simple warning alert—check it out!
    </div>`,
  mixins: [viewportSize]
}
new Vue({
  el: '#app',
  data() {
    return {
      heading: 'Extracting reusable functionality into mixins in Vue'
    }
  },
  components: {
    primaryAlert: primaryAlert,
    warningAlert: warningAlert
  }
})

如图所示,我们已经从两个组件中删除了methods选项,并添加了一个名为viewportSize的新对象。在这个对象中,我们移动了共享methods选项:

const viewportSize = {
    methods: {
      viewportSizeOnClick(){
        const width = window.innerWidth;
        const height = window.innerHeight;
        console.log("Viewport width:", width, "px, viewport height:", height, "px");
      }
  } 
}

methods选项仅保留viewportSizeOnClick功能。

作为旁注,vieportSizeOnClick方法的名称有点误导。如果仔细查看第二个组件(warningAlert组件)的代码,您会注意到我们更新了指令,因此它使用的是v-on:mouseenter,而不是v-on:click。这意味着需要将方法的名称更改为更合适的名称。因此,我们将该方法重命名为logOutViewportSize

另外,让我们设想一下,我们需要另一种方式来显示视口信息。例如,我们可能会在警报框中显示它,而不是将其记录到控制台。这就是为什么我们要介绍另一种方法,alertViewportSize

随着所有这些小变化的积累,现在正是看到我们的小应用程序的另一个更新版本的好时机。可以在以下 URL 找到新笔:https://codepen.io/AjdinImsirovic/pen/aaawJY

与之前的更新类似,更新后的示例也只对 JS 进行了如下更改。我们从viewportSize开始:

const viewportSize = {
    methods: {
      logOutViewportSize(){
        const width = window.innerWidth;
        const height = window.innerHeight; 
        console.log("Viewport width:", width, "px, viewport height:", height, "px");
      },
      alertViewPortSize() {
        const width = window.innerWidth;
        const height = window.innerHeight;
        alert("Viewport width: " + width + " px, viewport height: " + height + " px");
      }
  }
}

接下来,我们将设置警报:

const primaryAlert = {
  template: `
    <div class="alert alert-primary" role="alert" v-on:click="alertViewPortSize">
      A simple primary alert—check it out!
    </div>`,
  mixins: [viewportSize]
}
const warningAlert = {
  template: `
    <div class="alert alert-warning" role="alert" v-on:mouseenter="logOutViewportSize">
      A simple warning alert—check it out!
    </div>`,
  mixins: [viewportSize]
}

最后,让我们用指定 Vue 构造函数来结束它:

new Vue({
  el: '#app',
  data() {
    return {
      heading: 'Extracting reusable functionality into mixins in Vue'
    }
  },
  components: {
    primaryAlert: primaryAlert,
    warningAlert: warningAlert
  }
})

在下一节中,我们将看一看如何通过重构来进一步改进我们的 mixin。

重构我们的 viewportSize mixin

在本节中,我们将探讨进一步改进混音器的方法。虽然我们的代码既可读又易于掌握,但在const声明中有一些代码重复。此外,我们还将利用这个机会来研究实现 mixin 重构的方法。更新的代码将包括一些基本的事件处理。

有关可用事件的列表,请参阅此链接:https://developer.mozilla.org/en-US/docs/Web/Events

由于我们还将使用 JavaScript 的内置addEventListener()方法,因此最好在 MDN 上获得更多关于它的信息,网址如下:https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener

在开始重构之前,我们将利用混合功能插入 Vue 的生命周期功能(就像组件一样)。此外,在 mixin 的这个迭代中,我们在 mixin 本身中引入了除methods之外的另一个选项。我们使用的选项是data。实际上,为了避免在 mixin 的methods选项中重复const声明,我们将在data选项中存储要使用的值。

虽然 HTML 仍然保持不变,但我们的 JavaScript 文件看起来将完全不同。让我们从设置数据开始:

const viewportSize = {
    data(){
      return {
        viewport: {
          width: 0,
          height: 0
        }
      }
    },

接下来我们将添加方法,即getViewportSizelogOutViewportSizealertViewportSize

    methods: {
      measureViewportSize(){
        this.viewport.width = window.innerWidth;
        this.viewport.height = window.innerHeight;
      },
      logOutViewportSize(){
        console.log("Viewport width:", this.viewport.width, "px, viewport height:", this.viewport.height, "px");
      },
      alertViewPortSize() {
        alert("Viewport width: " + this.viewport.width + " px, viewport height: " + this.viewport.height + " px");
      }
  },

接下来,我们添加created

created() {
    this.listener =
      window.addEventListener('mousemove',this.measureViewportSize); 
    this.measureViewportSize();
 }
}

现在我们可以设置primaryAlert

const primaryAlert = {
  template: `
    <div class="alert alert-primary" role="alert" v-on:click="alertViewPortSize">
      A simple primary alert—check it out!
    </div>`,
  mixins: [viewportSize]
}

我们将继续添加warningAlert

const warningAlert = {
  template: `
    <div class="alert alert-warning" role="alert" v-on:mouseenter="logOutViewportSize">
      A simple warning alert—check it out!
    </div>`,
  mixins: [viewportSize]
}

最后,让我们添加 Vue 构造函数:

new Vue({
  el: '#app',
  data() {
    return {
      heading: 'Extracting reusable functionality into mixins in Vue'
    }
  },
  components: {
    primaryAlert: primaryAlert,
    warningAlert: warningAlert
  }
})

本节代码可在以下代码笔中找到:https://codepen.io/AjdinImsirovic/pen/oPPGLW

我们在重构的 mixin 中的选项有datamethodscreatedcreated函数是一个生命周期钩子,我们使用这个钩子监听mousemove事件。当此类事件发生时,我们运行 mixin 的this.getViewportSize方法,该方法更新注销或显示在警报框中的视口维度。

Never use global mixins! Global mixins affect all of the components of your apps. There are not that many use cases for such a scenario, so usually it is best to avoid using global mixins.

至此,我们结束对 Vue 中 mixin 的简要讨论。有关此主题的更多信息,请访问此官方链接:

https://vuejs.org/v2/guide/mixins.html

总结

在本章中,我们介绍了 Vue 中的过滤器和混合器。我们讨论了使用过滤器有意义的情况,并研究了使用全局和局部过滤器。我们还讨论了如何使用过滤器替换条件指令,并研究了如何将过滤器连接在一起。

我们还探讨了如何通过将可重用功能从组件移动到 mixin 来抽象可重用功能,并探讨了避免 mixin 内部代码重复的方法。我们用一个在 mixin 中使用生命周期挂钩的例子来结束它。

在下一章中,我们将介绍如何构建自己的自定义指令。