记得当那天使用 CakePHP 开发的时候,我很喜欢它简易入门的特性。其文档不仅结构严密,详尽,而且对用户友好。多年以后,我在 Vue.js 上找到了同样的感觉。然而,与 Cake 相比,Vue 文档还有一个缺点:(缺乏)真实的项目教程。
不管框架的文档有多好,对与所有人来说都是不够的。阅读有关的概念并不是总能帮助你了解更多东西,也不能帮助你理解如何使用它们来实际做出某些事情。如果你和我一样,你会在实践过程中学到更多,在你编码的时候参考文档,因为你需要它们。
在本教程中,我们将构建一个星级评分系统组件。我们将在需要时介绍几个 Vue.js 概念,并介绍为什么要使用它们。
TL;DR: 这篇文章详细的介绍了如何使用 vue.js 和为什么使用 vue.js 。它旨在帮助掌握 Vue.js 的一些核心概念,并教你如何为未来的项目做出设计决策。如果你想了解整个思维过程,请继续阅读。否则,你可以直接查看 CodeSandbox 上的最终代码。
入门指南
Vue.js(正确地)以一个简单的脚本引入足以开始运行,但是当你想使用single-file components,情况会有所不同。 现在,你不必这样构建组件。 你可以很容易地用 Vue.component 定义一个全局组件。
问题在于,这样做需要权衡使用字符串模板,没有 CSS 支持,也没有构建步骤(所以没有预处理器)。 然而,我们想要更深入地学习如何构建一个真正的在项目中使用的实际组件。出于这些原因,我们将使用由 Webpack 提供支持的实际设置。
为了保持简单并减少配置时间,我们将使用 vue-cli 和简单的 webpack-simple Vue.js模板。
首先,你需要全局安装 vue-cli。启动你的终端并键入以下内容:
npm install -g vue-cli
你现在可以通过几个按键生成随时可用的 Vue.js 样板。然后继续输入:
vue init webpack-simple path/to/my-project
你会碰到几个问题。 选择除“使用sass”之外的所有默认值,你应该回答 yes(y)。然后,vue-cli 将初始化项目并创建 package.json 文件。完成后,可以导航到项目目录,安装依赖关系,然后运行项目:
cd path/to/my-project npm install npm run dev
就这么简单!Webpack 将开始在端口 8080(如果可用)上为你的项目提供服务并在浏览器中启动它。如果一切顺利,你应该看到这样的欢迎页面。
我们做到了吗?
可以说我们做到了!为了正确调试你的Vue.js组件,你需要正确的工具。 继续并安装Vue.js devtools浏览器扩展(Firefox/Chrome/Safari)。
你的第一个组件
Vue.js最好的功能之一是single-file components(SFC)。 它们允许您在一个文件中定义组件的结构,样式和行为,而不存在混合HTML,CSS和JavaScript的常见缺陷。
SFC以.vue扩展名结尾,并具有以下结构:
<template> <!-- Your HTML goes here --> </template>
<script> /* Your JS goes here */ </script>
<style> /* Your CSS goes here */ </style>
让我们开始创建我们的第一个组件:在/src/components中创建一个Rating.vue文件,然后复制/粘贴上面的代码片段。然后,打开/src/main.js并调整现有的代码:
import Vue from 'vue' import Rating from './components/Rating'
new Vue({ el: '#app', template: '<Rating/>', components: { Rating } })
最后,添加一些HTML代码到你的Rating.vue文件:
<template> <ul> <li>One</li> <li>Two</li> <li>Three</li> </ul> </template>
现在看看你的浏览器中的页面,你应该看到列表。Vue.js会将您的<Rating>组件附加到index.html中的#app元素。如果检查HTML,则应该看不到#app元素的符号:Vue.js将其替换为组件。
旁注:你有没有注意到你甚至不需要重新加载页面?这是因为Webpack的vue-loader带有一个热加载功能。与实时重新加载或浏览器同步相反,每次更改文件时,热重新加载都不会刷新页面。而是监视组件更改,只刷新它们,保持状态不变。
现在,我们已经花了一些时间来设置,是时候真正写出有意义的代码了。
模板
我们将使用 vue-awesome,一个用 Font Awesome icons 构建的 Vue.js 的 SVG 图标组件。我们可以只加载我们需要的图标,使用 npm(或 Yarn)进行安装:
npm install vue-awesome
然后编辑你的组件,如下所示:
<template> <div> <ul> <li><icon name="star"/></li> <li><icon name="star"/></li> <li><icon name="star"/></li> <li><icon name="star-o"/></li> <li><icon name="star-o"/></li> </ul> <span>3 of 5</span> </div> </template>
<script> import 'vue-awesome/icons/star' import 'vue-awesome/icons/star-o' import Icon from 'vue-awesome/components/Icon' export default { components: { Icon } } </script>
好吧,让我们慢一点,解释一下。
Vue.js 使用原生 ES6 模块来处理依赖和导出组件。<script>块中的前两行分别导入图标,所以最终捆绑包中不需要图标。第三个图标是从 vue-awesome 导入的 Icon 组件,所以你可以在你的项目中使用它。
图标是一个 Vue.js SFC,就像我们正在构建的这一个。如果你打开这个文件,你会发现它和我们的结构完全一样。
export default 模块将对象文字导出为我们组件的视图模型。我们在组件属性中注册了 Icon 组件,所以我们可以在本地使用它。
最后,我们在 HTML <template> 中使用了 Icon,并传递了一个 name 属性来定义我们想要的图标。通过将组件转换为 kebab-case(例如:MyComponent 变成 <my-component>),可以将组件用作自定义 HTML 标记。我们不需要在组件内嵌入任何东西,所以我们使用了一个自闭合标签。
旁注:你有没有注意到我们在 HTML 中添加了一个<div>标签?这是因为我们还在根级别的<span>中添加了一个计数器,Vue.js 中的组件模板只接受一个根元素。如果你不遵守,会得到一个编译错误。
样式
如果你已经使用过 CSS,你应该知道一个主要的挑战就是要处理它的全局性。嵌套一直被认为是解决这个问题的方法。但现在我们知道它很快就会导致特殊性问题,使得样式难以覆盖,不能被重用,并且这将是一个难以衡量的噩梦。
于是发明了像 BEM 这样的方法来绕过这个问题,并且通过命名空间类来保持低的特异性。有一段时间,这是编写干净和可扩展的 CSS 的理想方法。然后,像 Vue.js 或 React 这样的框架和库就出现了,并将 scoped styling 引入表中。
React 具有样式化的组件,Vue.js 具有 scoped styling CSS。它可以让你编写特定组件的 CSS,而不必拿出一些技巧来保持它的包含结构。您使用“普通”类名编写常规 CSS,Vue.js 通过将数据属性分配给 HTML 元素并将其附加到编译样式来处理范围限定。
让我们在组件上添加一些简单的类:
<template> <div class="rating"> <ul class="list"> <li class="star active"><icon name="star"/></li> <li class="star active"><icon name="star"/></li> <li class="star active"><icon name="star"/></li> <li class="star"><icon name="star-o"/></li> <li class="star"><icon name="star-o"/></li> </ul> <span>3 of 5</span> </div> </template>
和 css 样式:
<style scoped> .rating { font-family: 'Avenir', Helvetica, Arial, sans-serif; font-size: 14px; color: #a7a8a8; } .list { margin: 0 0 5px 0; padding: 0; list-style-type: none; } .list:hover .star { color: #f3d23e; } .star { display: inline-block; cursor: pointer; } .star:hover ~ .star:not(.active) { color: inherit; } .active { color: #f3d23e; } </style>
看到那个scoped属性了吗? 这是告诉 Vue.js 去范围化样式,所以他们作用范围不会涵盖到其他地方。 如果您在 index.html 中正确地复制/粘贴 HTML 代码,您将注意到您的样式不适用:这是因为它们的作用域是组件。
那么预处理器呢?
Vue.js 使得从简单的 CSS 切换到您最喜欢的预处理器变得轻而易举。你所需要的只是适当的 Webpack 加载器和<style>块上的简单属性。我们在生成项目时对“使用sass”选择“是”,所以 vue-cli 已经为我们安装并配置了 sass-loader。现在,我们需要做的就是将 lang="scss" 添加到开始的<style>标签中。
现在我们可以使用 Sass 编写组件级样式,导入变量,颜色定义或混合等部分。如果您更喜欢缩进语法(或“sass”符号),只需在 lang 属性中将 scss 切换 sass 即可。
行为
现在我们的组件看起来不错,现在是时候让它开始工作了。目前,我们有一个硬编码的模板。让我们设置一些初始的模拟状态,并调整模板,使其显示出来:
<script> ... export default { components: { Icon }, data() { return { stars: 3, maxStars: 5 } } } </script> /* ... */ <template> <div class="rating"> <ul class="list"> <li v-for="star in maxStars" :class="{ 'active': star <= stars }" class="star"> <icon :name="star <= stars ? 'star' : 'star-o'"/> </li> </ul> <span>3 of 5</span> </div> </template>
我们在这里所做的是使用 Vue 的数据来设置组件状态。你在 data 中定义的每个属性都是有响应性的:如果它发生变化,它将反映在视图中。
我们正在创建一个可重用的组件,因此 data 需要成为工厂函数而不是对象文字。这样我们就得到了一个新的对象,而不是一个可以跨几个组件共享的现有对象。
我们的 data 工厂返回两个属性:stars,当前“活动”的 star 数和 maxStars,还有一个就是组件中 star 的总数。因为我们会适配我们的模板规则,所以它反映了组件的实际状态。Vue.js 带有一堆指令,可以让您将演示逻辑添加到模板中,而无需将其与纯 JavaScript 代码混合。v-fordirective 遍历任何可迭代的对象(数组,对象文字,映射等)。它也可以把一个数字作为一个范围重复 x 次、这就是我们用 v-for="star in maxStars" 所做的,所以我们对组件中的每个星星都有一个<li>。
您可能已经注意到一些属性以冒号为前缀,这是 v-bind 指令的缩写,它将属性动态绑定到表达式。我们可以把它写成长的形式,v-bind:class。
当 star 处于活动状态时,我们需要在 <li> 元素上添加 active 类。在我们的项目下,这意味着每个 <li> 的索引小于 stars 应该有 active 类。我们在 :class 指令中使用了一个表达式,当当前 star 小于总 star 数时,才会追加 active。同样条件下我们使用三元运算符来定义 Icon 组件使用的什么样的图标:star 或 star-o。
那计数器呢?
现在我们的 star 列表是绑定到实际的数据,现在我们是时候对计数器也执行相同的操作。最简单的方法是使用带有 mustache 语法的文本插值:
<span>{{ stars }} of {{ maxStars }}</span>
很简单,不是吗? 现在在这种况下,这是诀窍。 但是,如果我们需要一个更复杂的 JavaScript 表达式,最好将其抽象到一个计算属性中。
export default { ... computed: { counter() { return `${this.stars} of ${this.maxStars}` } } } /* ... */ <span>{{ counter }}</span>
在这里,这是矫枉过正。 我们可以避开模板内表达式,并保持可读性。然而,当你不得不处理更复杂的逻辑时,记住计算的属性。
另一件我们需要做的是提供一种方法来隐藏计数器,如果我们不需要它的时候。 最简单的方法是使用带有布尔值的 v-if 指令。
<span v-if="hasCounter">{{ stars }} of {{ maxStars }}</span> /* ... */ export default { ... data() { return { stars: 3, maxStars: 5, hasCounter: true } } }
交互
我们差不多完成了,但是我们仍然需要实现组件中最有趣的部分:响应性。我们将使用 v-on,这是处理事件和方法的 Vue.js 指令,可以附加所有方法的 Vue.js 属性。
<template> ... <li @click="rate(star)" ...> ... </template> /* ... */ export default { ... methods: { rate(star) { // do stuff } } }
我们在 <li> 上添加了 @click 属性,这是 v-on:click 的简写。该指令包含对我们在组件的 methods 属性中定义的 rate 方法的调用。
“等一下...这看起来非常像熟悉的 HTML 的 onclick 属性。在 HTML 中使用内联 JavaScript 不是一个过时和不好的做法吗?“
确实如此,但是即使语法看起来很像 onclick,但比较两者是一个错误。当你构建一个 Vue.js 组件时,你不应该把它看作是分离的 HTML/CSS/JS,而应该是一个使用多种语言的组件。当项目在浏览器中开启服务或编译生产时,所有的 HTML 和指令都被编译成普通的 JavaScript。如果您检查已渲染的 HTML,您将看不到您的指令的任何标志,也没有任何 onclick 属性。Vue.js 会编译好你的组件并创建合适的绑定。
这也是为什么您可以从模板访问组件的上下文的原因:因为指令绑定到视图模型。与具有单独 HTML 的传统项目相反,模板是组件的组成部分。
回到我们的 rate 方法。我们需要将 stars 变为 clicked 元素的索引,所以我们通过 @click 指令的索引,可以做到以下几点:
export default { ... methods: { rate(star) { this.stars = star } } }
去查看您的浏览器页面,并尝试点击 star:它运行成功了!
如果你打开浏览器开发者工具栏中的 Vue 面板并选择 <Rating> 组件,当你点击 star 时,你会看到数据的变化。这表明你的 star 属性是响应性的:当你改变它的时候,它会把它的改变指派给视图。 这个概念被称为数据绑定,如果您使用过 Backbone.js 或 Knockout 之类的框架,您应该熟悉这个概念。 不同之处在于,Vue.js 和 React 一样,只能在一个方向上进行:这就是所谓的单向数据绑定。不过这个话题值得写一篇单独的文章。
在这一点上,我们可以认为已完成 —— 但我们可以做更多的工作来改善用户体验。
现在,我们实际上不能给出 0 的等级,因为点击一个 star 会将它的比率设置为它的索引。更好的方案是重新点击同一颗 star,并切换至其当前状态,而不是保持 active 状态。
export default { ... methods: { rate(star) { this.stars = this.stars === star ? star - 1 : star } } }
现在,如果点击的 star 的索引等于 star 当前值,我们就减少它的值。 否则,我们给它分配 star 值。
如果我们想要彻底解决,我们还应该添加一个控制层,以确保 star 从来没有被赋予一个没有意义的值。我们需要确保 star 永远不会小于 0,也绝不会比 maxStars 更大,而且它是一个合适的数字。
export default { ... methods: { rate(star) { if (typeof star === 'number' && star <= this.maxStars && star >= 0) { this.stars = this.stars === star ? star - 1 : star } } } }
传递 props 属性
现在,组件的数据在数据属性中被硬编码。如果我们希望我们的组件实际上是可用的,我们需要能够从其实例传递自定义数据。在 Vue.js 中,我们用 props 做到这一点。
export default { props: ['grade', 'maxStars', 'hasCounter'], data() { return { stars: this.grade } }, ... }
和在 main.js 文件里:
new Vue({ el: '#app', template: '<Rating :grade="3" :maxStars="5" :hasCounter="true"/>', components: { Rating } })
这里有三件事要注意:
首先,我们使用 v-bind 简写从组件实例传递 props 属性:这就是 Vue.js 所谓的动态语法。当你想要传递一个字符串值时,你不需要知道它的具体值,为此,字面值语法(没有 v-bind 的普通属性)将起作用。但对我们而言,由于我们正在传递数字和布尔值,所以这很重要。
props 和数据属性在编译时被合并,所以我们不需要改变在视图模型或模板中调用属性的方式。出于同样的原因,我们不能在 props 数据属性中使用相同的名称。
最后,我们定义了一个级别属性,并将其作为 star 数值属性中的值传递给它。我们之所以这样做,不是直接使用级别属性,而是因为级别改变,值会发生变化。在 Vue.js 中,props 从父级传递给子级,而不是反过来传递,所以你不会改变父级的状态。这将违背 单向数据流 的原则,使事情难以调试。这就是为什么你不应该试图改变子组件内的 prop。相反,定义一个使用 props 的初始值作为自声的本地数据属性。
最后的润色
在这一天马上过去之前,我们应该了解 Vue.js 最后一个惊奇的地方:prop 的验证。
Vue.js 允许你在传递给组件之前控制 prop。您可以执行四个主要的事情:检查类型,要求定义一个 prop 属性,设置默认值,并执行自定义验证。
export default {
props: {
grade: {
type: Number,
required: true
},
maxStars: {
type: Number,
default: 5
},
hasCounter: {
type: Boolean,
default: true
}
},
...
}
我们使用类型检查来确保将正确类型的数据传递给组件。这将对我们忘记使用动态语法来传递非字符串值的错误特别有用。我们也确保通过要求它填写 grade 属性。对于其他 props 属性,我们定义了默认值,所以即使没有传递自定义数据,组件也能正常工作。
现在我们可以简单地通过执行以下操作来实例化组件:
<Rating :grade="3"/>
就是这样!您刚刚创建了第一个 Vue.js 组件,并探索了许多概念
本文作者:凉凉_
本文发布时间:2018年01月29日
本文来自云栖社区合作伙伴CSDN,了解相关信息可以关注csdn.net网站。