Vue 开发入门 Vue 是一个专注于构建 Web 用户界面 的 JavaScript 库。
1.1 为什么需要另外一个前端框架 Vue 易用、灵活、速度快、还提供了许多功能和可选工具,这使得开发者能够快速地构建一个现代 web 应用。Vue 的作者尤雨溪将其称为渐进式框架 。
Vue 遵循渐进增量的设计原则,其核心库专注于用户界面,使得现有的项目可以方便地集成使用 Vue。
Vue 既可以构建出很小的原型,又可以构建出复杂的大型 web 应用。
Vue 非常容易上手 —— 初学者能轻松掌握 vue,而已经熟悉 Vue 的开发者则可以在实际项目中快速发挥出它的作用。
Vue 整体上遵循 MVVM (Model-View-ViewModel,模型 - 视图 -视图模型)架构,也就是说 View(用户界面或视图)和 Model(数据)是独立的,ViewModel(Vue)是 View 和 Model 交互的桥梁。Vue 对 View 和 Model 之间的更新操作做了自动化处理,并且已经为开发者进行了优化。
Vue 还吸收了其他类似框架( 如 React、Angular、Polymer)的精华。
Vue 核心功能概述
一个响应式的数据系统,能通过轻量级的虚拟 DOM 引擎 和最少的优化工作来自动更新用户界面。
灵活的视图声明,包括优雅友好的 HTML 模板、JSX(在 JavaScript 中编写 HTML 的技术)以及 hyperscript 渲染函数(完全使用 JavaScript )。
由可维护、可复用组件组成的组件化用户界面。
官方的组件库提供了路由、状态管理、脚手架以及更多高级功能,使 Vue 成为了一个灵活且功能完善的前端框架。
Weex、NativeScript
1.1.2 兼容性需求 Vue 不支持 Internet Explorer 8 以下版本,因为 Vue 使用了 JavaScript 中相对较新的特性,比如 Object.defineProperty, 而它们在老版本的浏览器中是无法 polyfill 的。
编译器 Babel ,它编译过的代码可以很好地运行在老版本浏览器中。
1.3 创建一个应用 整个库都是基于 Vue 实例的,而实例是 View 和 Model(数据)交互的桥梁。因此需要创建一个新的 Vue 实例来启动应用。
1 2 3 4 5 6 7 8 9 10 11 var app = new Vue({ el: '#root' , data ( ) { return { message: 'Hello Vue.js' } } })
使用关键字 new 调用 Vue 构造器创建了一个新的实例。Vue 构造器有一个参数 —— option 对象。该参数可以携带多个属性(称为选项)。
通过 el 选项,我们使用 CSS 选择器告知 Vue 将实例添加(挂载)到 Web 页面的哪个 DOM 元素中。 也可以 Vue 实例的 $mount 的方法代替el选项: app.$mount(‘#root’)。
1 2 3 4 5 6 7 8 9 var app = new Vue({ data ( ) { return { message: 'Hello Vue.js' } } }) app.$mount("#root" );
Vue 实例的大多数特殊方法和属性都是以美元符号($)开头的。
在单个 Web 页面中,开发者可以添加任意多个 Vue 应用。只需要为每个应用创建出 新的 Vue 实例并挂载到不同的 DOM 元素即可。当想要将 Vue 集成到已有的项目中时,这非常方便。
Vue 开发者工具
Vue 有一个官方调试工具,在 Chrome 中以扩展的方式呈现,名为 Vue.js devtools。通过该工具可以看到应用的运行情况,这有助于调试代码 。可以在 Chrome 网上应用商品下载;如果使用 Firefox,则可以到 Firefox 附加组件 下载。 使用 Chrome 版本的话,还需要进行额外的设置。在扩展设置中,启用 Allow access to file URLs 选项。这样调试工具就能在从本地磁盘打开的 Web 页面上检测 Vue 了。
可以将 devtools 选项卡拖放到喜欢的位置。建议将其放在靠前的位置,因为当 Vue 不处于开发模式或没有运行时,该选项卡在页面中是隐藏起来的
可以通过 name 选项修改 Vue 实例的名字:
1 2 3 4 var app = new Vue({ name : 'MyApp' , })
当一个页面中有多个 Vue 实例时,这有助于直观地在开发者工具中找到具体的某个实例。
1.4 借助模板实现 DOM 的动态性 模板是描述 View 最简单的方法,只需要少量额外的语法就能轻松实现 DOM 的动态更新。
1.4.1 文本显示 文本插值用于在 Web 页面中显示动态的文本。文本插值的语法是在双花括号内包含单个任意类型的 JavaScript 表达式。当 Vue 处理模板时,该 JavaScript 表达式的结果将会替换掉双花括号标签。
1 2 3 <div id ="root" > {{ message }} </div >
DOM 和 数据连通了
1 app.message = 'Awesome!'
数据绑定,每当数据有改变时, Vue 都能够自动更新 DOM,Vue 框架中包含一个非常强大且高效的响应系统,能对所有的数据进行跟踪,并且能在数据发生改变时按需自动更新 View。
1.4.2 利用指令添加基本的交互 Vue 中所有的指令命名都是带 V- 前缀的,并遵循短横线分隔式(kebab-case)语法。这个意味着要用短横线将单词分开。HTML 属性是不区分大小写的。
1 2 3 4 5 <div id="root"> <p>{{message}}}</p> <!-- 添加一个文本输入框 --> <input v-model = "message"> </div>
双向数据绑定 v-model
项目 1:Markdown 笔记 v-model 指令不限于文本输入使用。它同样可以用于其他元素,例如勾选框、单选按钮,甚至自定义组件。
2.1.3 预览面板 计算属性 通过它可以定义一个新的属性,而该属性可以结合任意多个属性,并做相关转换操作。
计算属性的值基于它的依赖进行缓存,因此如果没有必要是不会重新运行函数的,从而有效防止无用计算;
当函数中用到的某个属性发生了改变,计算属性的值也会根据需要自动更新;
计算属性可以如其他普通属性一起使用(可以在其他计算属性中使用计算属性);
计算属性只有真正用于应用中时,才会进行计算操作。
在应用内,不建议使用 v-html 指令对用户提供的内容做 HTML 插值。这是因为用户可能会在标签中编写不怀好意、会被执行的 JavaScript 代码。当然对普通文本做插值是安全的,因为 HTML 不会被执行。
元素中的任意内容将被 v-html 指令的值替代。可以利用这一点来放置占位符内容。
对于文本插值,v-text 是一个与 v-html 等效的指令。它的行为与 v-html 类似,只不过会对 HTML 标签做转译处理,形同典型的文本插值。
2.1.4 保存笔记 localStorage 保存笔记
侦听改变
监听器 watch 选项是一个字典,把被侦听属性的名字作为键,把侦听选项对象作为值。这个对象必须要一个 handler 属性,该属性可以是一个函数,也可以是一个方法的名字。这个处理函数将接收两个参数:被侦听属性的新值和旧值。
1 2 3 4 5 6 7 8 9 10 new Vue({ // 侦听 content 数据属性 watch: { content: { handler(val,oldVal){ console.log('new note:', val , 'old note' , oldVal) } } } })
还有另外两个选项可以和 handler 一起使用。
deep 是一个布尔类型,告诉 Vue 以递归的方式侦听嵌套内部值的变化。
immediate 也是一个布尔类型,会立即触发调用处理函数,而不用等到属性第一次变化时才调用。
这两个选项的默认值都是 false,所以不需要使用的时候,可以完全忽略它们。
当不需要其他选项(例如 deep 或 immediate)时,这是侦听器中最常用的语法。
1 2 3 4 watch: { content(val,oldVal){ } }
保存笔记
1 localStorage .setItem('content' ,val)
复用方法 良好的编程准则之一:不要重复自己(DRY) ,也称为一次仅且一次 。开发者应该遵守这个准则。因此可以把一些逻辑写在 可复用的函数里面:methods。
1 2 3 4 5 6 7 8 9 10 11 12 new Vue({ watch:{ content: 'saveNote' }, methods:{ saveNote(val){ console.log('saving note:',this.content); localStorage.setItem('content',thie.content) } } })
访问 Vue 实例
在 methods 内部,可以通过 this 关键字访问 Vue 实例,还可以访问 Vue 实例的其他属性或特殊函数。
基本上可以在任意函数(方法、处理函数或其他钩子)其中使用 this 关键字访问 Vue 实例。
生命周期钩子
生命周期钩子
每个 Vue 实例都严格遵守一个生命周期,包括多个环节:创建、挂载到页面、更新,最终被销毁。例如,在创建实例阶段,Vue 会将实例数据变成响应式数据。
钩子是一组特殊的函数,会在某个时间点被自动调用。这就允许我们自定义框架的逻辑。例如在创建 Vue 实例时调用一个方法。
在每个环节之中或之前,有很多钩子可以用于执行逻辑。
beforeCreate:在 Vue 实例被创建时(例如使用 new Vue({ }))、完成其他事项之前调用。
created:在实例准备就绪之后调用。注意,此时实例还没有挂载到 DOM 中。
beforeMount:在挂载(添加)实例到 web 页面之前调用。
mounted:在实例被挂载到页面并且 DOM 可见时调用。
beforeUpdate:当实例需要更新时(一般来说,是当某个数据或计算属性发生改变时)调用。
updated:在把数据变化应用到模板之后调用。注意此时 DOM 可能还没有更新。
beforeDestroy:在实例销毁之前调用。
destroyed:在实例完全销毁之后调用。
在 JavaScript 中,如果值为 false、0、空字符传、null、undefined 或 NaN(不是一个数),则它就是假值。在浏览器的本地存储数据中,如果对应的键不存在, localStorage.getItem() 方法会返回 null。
在数据中直接初始化
1 2 3 4 5 6 7 new Vue({ data(){ return{ content:localStorage.getItem('content') || 'You can write in **markdown**' } } })
2.2 多条笔记 2.2.1 笔记列表 添加新建笔记的方法 每一条笔记都是具体如下数据的对象。
id :笔记的唯一标识符。
title :笔记的标题,用来显示在笔记列表中。
content : 笔记的 Markdown 格式内容。
created : 笔记创建的日期。
favorite : 这是一个布尔型,用于表示是否收藏了笔记,已收藏的笔记显示在笔记列表的顶部。
选择当前时间(也就是从 1970 年 1 月 1 日 00:00:00 UTC 开始经过的毫秒数)作为区分笔记的唯一标识符,这是一种不错的方式。
用 v-on 实现按钮的单击事件
1 <button v-on:click="addNote"></button>
绑定事件 v-on: 简写 @
1 <button @:click="addNote"></button>
用 v-bind 绑定属性
1 <button v-bind:title="notes.length + ' note(s) already'">
属性绑定 v-bind: 简写 :
1 <button :title="notes.length + ' note(s) already'">
当需要更新属性值的时候,用 v-bind 指令绑定的 JavaScript 表达式会自动重新运算。
用 v-for 显示列表
1 2 3 <div class="notes"> <div class="note" v-for="note of notes">{{note.title}}</div> </div>
2.2.2 选择一条笔记 动态 CSS 类
1 <div :class = "{ one: note === selectedNote }" > </div >
可以把静态与动态的 class 属性结合起来。建议将非动态的类放到静态的属性中,Vue 会对静态值做优化处理。
条件指令 v-if v-else-if v-else
标签不会出现在 DOM 中。用于对实际的元素进行重新组合。
侦听器默认值侦听目标对象的直接变化:赋值、数组添加、删除、移动。 例如:
1 2 3 4 5 6 7 8 9 this .selectedId = 'abcd' this .selectedId.push({...})this .selectedId.splice(index,1 );this .notes.sore(...)
操作不会触发侦听器
1 2 3 4 5 6 this .myObject.someAttribute = 'abcd' ;this .myObject.nestedObject.otherAttribute = 42 this .notes[0 ].content = 'new content'
这种情况下,需要在侦听器上添加 deep 选项
1 2 3 4 5 6 7 watch: { notes: { handle : 'saveNotes', // 需要使用这个选项来监听数组中每个笔记属性的变化 deep: true } }
按照收藏、时间排序
1 2 3 4 5 6 7 computed: { sortedNotes(){ return this.notes.slice() .sort((a,b) => a.created - b.created) .sort((a,b) => (a.favorite === b.favorite) ? 0 : a.favorite ? -1 : 1) } }
由于 sort 方法会直接修改源数组,这里使用 slice 方法创建新的副本。这样可以防止 notes 监听器。
创建日期过滤器
(1)引入 momentjs
1 <script src ="https://unpkg.com/moment" > </script >
(2) 使用 Vue.filter 全局方法(不在 Vue 实例的创建代码中,比如位于文件开头)注册过滤器
1 Vue.filter('date' ,time => mement(time).format('DD/MM/YY, HH:mm' ))
1 <span class ="value" > {{ selectedNote.created | date }}</span >
文本统计
1 2 3 4 5 6 7 8 9 10 11 12 str.split(/\r\.|\r|\n/ ).length str.replace(/\n/g , '' ); str.replace(/(^\s*)|(\s*$)/gi , '' ) str.replace(/\s\s+/gi , ' ' ); str.split(' ' ).length; str.split('' ).length
项目 2:城堡决斗游戏 在 Vue 实例构造器之后,添加一个事件监听器到 window 对象中,监听浏览器窗口大小的变化。
1 2 3 4 window .addEventListener('resize' , () => { state.wordRatio = getWorldRatio(); }
3.3.3 万能的组件 组件是构建应用的基础模块,是 Vue 应用的核心概念。组件是视图的一个个小部分,因此相对来说应用比较小、可复用,并且尽可能地自给自足。采用组件构建应用有助于应用的维护和升级,特别是当应用规模变大之后。实际上,这已经成为了高效、可控地开发大型 Web 应用的标准方法。
可以使用全局函数 Vue.component() 来注册组件。该函数接受两个参数:一个是注册组件的名称,另一个则是组件的定义对象本身,它与 Vue 实例使用相同的选项。
1 2 3 Vue.component('top-bar' ,{ template:`<div class='top-bar'>Top bar</div>` })
使用 prop 进行父组件到子组件的通信
利用 props 选型可以将 prop 添加到组件中。
1 2 3 4 5 6 7 8 Vue.component('top-bar' ,{ props: ['players' ,'currentPlayerIndex' ,'turn' ], created ( ) { console .log(this .players); } }) <top-bar :turn="turn" :current-play-index="currentPlayIndex" :players="players" >
注意:由于 HTML 是不区分大小写的,建议对 prop 的名字使用短航线命名方法(kebab-case),而在 JavaScript 代码中使用驼峰式命名方法(camel-case)。
模板中的 prop
通过 players prop 显示玩家姓名。
1 2 3 4 5 6 template:` <div class="top-bar"> <div class="player p0">{{ players[0].name}}</div> <div class="player p1">{{ players[1].name}}</div> </div> `
在组件上监听原生事件 为监听到组件的 click 事件,需要对 v-on 指令使用 .native 修饰符。
1 <card :def ="testCard" @click.navtive ="handlePlay" >
使用自定义事件进行子组件到父组件的通信 在组件内部,使用 $emit 这个特殊方法触发的事件可以被父组件捕获到。该方法接收一个固定的参数,即事件类型:
1 this .$emit('play' ,'orange' ,42 )
可以使用名为 $on 的特殊方法监听自定义事件:
1 2 3 4 5 6 7 8 9 10 11 this .$on('play' , () => { console .log('Caught a play event!' ); }) <card v-on:play="handlePlay" />> <card @play="handlePlay" /> handlePlay (color,number ) { console .log('handle play event' ,'color = ' , color ,'number=' ,number) }
Vue 的自定义事件与浏览器事件系统是完全分开的。方法 $on 和 $emit 并不是 addEventListener 和 dispatchEvent 的别名。这也解释了为什么在组建中需要使用 .native 修饰符来监听浏览器事件(如 click)。
大多数情况下,最好使用自定义事件完成组件之间的通信。
hand 组件的动画过度效果
动画效果:CSS 过渡结合 <transition>
组件。当添加或移除元素时,使用 v-if 或 v-show 指令来帮助实现 CSS 过渡。
1 2 3 4 5 var state = { activeOverlay: null }
1 2 3 <transition> <hand v-if="!activeOverlay"/> </transition>
<transition>
特殊组件不会显示在 DOM 中。
当元素被添加到 DOM 时(进入阶段),<transition>
组件会自动将下列 CSS 类应用到元素中。
v-enter-active: 当进入过渡状态被激活时,会应用该类。在元素插入 DOM 之前,添加该类到元素中,并在动画结束时移除它。应该在这类中添加一些 transition CSS 属性并定义其过渡时长。
v-enter: 元素进入过渡的开始状态。在元素插入 DOM 之前,添加该类到元素中,并在元素被插入的下一帧移除。例如,你可以在这个类中设置透明为 0。
v-enter-to: 元素进入过渡的结束状态。在元素插入 DOM 后的下一帧添加,同时 v-enter 被移除。当动画完成后,v-enter-to 会被移除。
当元素从 DOM 中移除时(离开阶段),<transition>
组件会自动将下列 CSS 类应用到元素中。
v-leave-active:当离开过渡状态被激活时,会应用该类。当离开过渡触发时,添加该类到元素中,并在从 DOM 中移除元素时移除它。应该在这个类中添加一些 transition CSS 属性并定义其过渡时长。
v-leave:元素被移除时的开始状态。当离开过渡触发时,添加该类到元素中,并在下一帧移除。
v-leave-to:元素离开过渡的结束状态。在离开过渡触发后的下一帧添加,同时 v-leave 被移除。当从 DOM 中移除元素时,该类也会被移除。
在离开阶段,并不会立即从 DOM 中移除元素。当过渡结束后,才会将其移除,这样用户可以看到动画效果。
进入 v-enter-active v-enter v-enter-to 透明度0 -> 透明度1
离开 v-leave-active v-leave v-leave-to 透明度1 -> 透明度0
<transition>
组件会自动检测应用在元素上的 CSS 过渡效果的持续时间。
复用动画
1 2 3 <transition name="fade"> <hand v-if="!activeOverlay" :cards="testHand"> </transition>
1 2 3 4 5 6 7 8 9 10 .fade-enter-active ,.fade-leave-acteve { transition : opacity 1s ; } .fade-enter ,.fade-leave-to { opacity : 0 ; }
现在只需要通过 <transition name="fade">
标签就可以在任意元素上复用这个动画了。
贝塞尔曲线缓动函数,可以使得动画更加平滑。
1 2 3 4 5 6 7 8 9 10 .hand-enter-active .wrapper ,.hand-leave-active .wrapper { transition : transform .8s cubic-bezier (.08 ,.74 ,.34 ,1 ); transform-origin : bottom center; } .hand-enter .wrapper ,.hand-leave-to .wrapper { transition : rotate (90deg ); }
为元素列表添加动画效果,需要使用另外一个特殊的组件 <transition-group>
。当元素被添加、移除和移动时,该组件将对它的子元素做出动画效果。 跟 <transition>
元素不同的是,<transition-group>
默认情况下会作为<span>
元素出现在 DOM 中。可以使用 tap prop 修改这个 HTML 元素。
1 2 3 <transition-group tag="ul"> <li v-for="item of items" /> </transition-group>
<transition-group>
的子元素必须由唯一的 key 做标识。
1 2 3 4 // 指定过渡效果名称为 card <transition-group name="card" tag="div" class="cards"> <card v-for="card of cards"> </transition-grou
特殊的 key 属性 当 Vue 更新存在于 v-for 循环中的 DOM 元素列表时,会尽量最小化应用于 DOM 的操作,例如添加和移除元素。大多数情况下,这是更新 DOM 的一种非常高效的方法,并且对性能的提升也有帮助。 为了做到这一点,Vue 会尽可能地复用元素,并仅对 DOM 中需修改的地方进行最小范围的修改,以达到理想的结果。这意味着重复的元素会被打包到一起,不会在添加或移除列表中的项时移动它们。不过,这也意味着对其应用过渡不会有动画效果。
key 属性为元素指定唯一标识符。
使用插槽分发内容
1 2 3 4 5 <div class="overlay" @click="handleClick"> <div class="content"> <slot /> </dvi> </div>
1 2 3 <overlay> Hello world! </overlay>
Vue 提供了一个特殊的组件可以把其转换为任意的组件:component 组件。只需要将它的 is prop 设置为一个组件名或组件定义对象,甚至是一个 HTML 标签,component 组件就会变为响应的内容:
动态修改组件
1 2 3 <overlay v-if="activeOverlay"> <component :is="'overlay-content-'+activeOverlay" :player="currentPlayer" > </overlay>
当 targetHeight 属性发生改变时,就开始播放动画
1 2 3 4 5 6 7 8 9 10 11 12 watch:{ targetHeight(newValue,oldValue){ const vm = this; new TWEEN.Tween({value:oldValue}) .easing(TWEEN.Easing.Cubi.InOut) .to({value: newValue},500) .onUpdata(function(){ vm.height = this.value.toFixed(0) }) .start() } }
高级项目设置 4.1 设置开发环境 安装 Node.js 和 npm
4.1.1 安装官方命令行工具 vue-cli 安装 vue-cli 并将其作为一个全局的包:
1 2 3 4 5 npm install -g vue-cli // 打印 vue-cli 打印版本 vue -V vue --version
项目脚手架 推荐的官方模板是 webpack 模板,具有使用 Vue 创建整个 SPA(单页面应用)所需的全部功能。使用 webpack-simple 并逐步引入功能。
渲染函数
Vue 使用一个虚拟 DOM 的实现,用树状结构的 JavaScript 对象来构建虚拟 DOM。然后 Vue 将虚拟 DOM 应用到真实浏览器的 DOM 上,所用方法是计算两者之间的差距。这尽可能地避免了 DOM 操作,因为 DOM 操作通常是主要的性能瓶颈。
按照惯例,h 是 createElement 的别名,这是编写 JSX 时非常常见和必须的。它得名于使用 JavaScript 描述 HTML 的技术 - Hyperscript。
createElement(或称 h)方法最多需要 3 个参数, (1)第一个参数是元素类型。它可以是一个 HTML 标签名称(比如 div),在应用中注册过得组件名称,或者直接就是一个组件定义对象。 (2)第二个参数是可选的。它是一个定义了属性、prop、事件监听器等的数据对象。 (3)第三个参数也是可选的。它可以是简单的纯文本,也可以是一个用 h 创建的其他元素的数据。
示例:
1 2 3 4 5 6 7 8 9 10 11 render (h ) { return h('ul' ,{'class' :'movies' },[ h('li' ,{'class' : 'movie' },'Start Wars' ), h('li' ,{'class' :'movie' },'Blade Runner' ), ]) } <ul class ='movies' > <li class ='movie' >Star Wars</li> <li class ='movie' >Blade Runner</li> </ul>
4.2.4 配置 Babel Babel 是一个 JavaScript 代码编辑工具,以便我们在旧版和最新版的浏览器中使用新特性(如 JSX 或箭头函数)。建议在所有正式的 JavaScript 项目中使用 Babel。
polyfill 是用于检查特性在浏览器中是否可用的代码;如果不可用,它将实现这个特性,使其可以像原生的一样工作。
1.Babel Vue 预览
安装并使用 babel-preset-vue
1 npm i -D babel-preset-vue
主要的 Babel 配置是在项目根目录下已存在的 .babelrc JSON 文件中完成的。
打开这个 .babelrc 文件并将 vue 预览添加到相应的列表中:
1 2 3 4 5 6 7 { "presets" : { ["env",{"modules": false}], "stage-3", "vue" } }
polyfill
添加 Babel polyfill,以便在旧浏览器中使用新的 JavaScript 特性。
在开发依赖中安装 babel-polyfill 包。
在 src/main.js 文件的开头将其导入
import ‘babel-polyfill’
这将为浏览器启用所有必要的 polyfill。
4.2.5 更新依赖 1、手动更新
1 2 3 4 // 要检查项目中使用的包是否有新的版本,可以在根文件夹中运行以下命令: npm outdated npm install
不要忘记阅读你所更新包的更改日志!可能会有你希望了解的破坏性改变或改善。
2.自动更新
该命令只会更新与 package.json 文件中所指定版本兼容的版本。如果你想将包更新为其他版本,则需要手动执行。
3.更新 Vue
更新包含核心库的 Vue 包时,你也应该更新 vue-template-compiler 包。它是使用 webpack (或其他构件工具)时编译所有组件模板的包。
这两个包必须始终处于相同的版本。例如,如果你使用 vue 2.5.3 ,那么 vue-template-complier 也应该是版本 2.5.3.
4.2.6 为生产而构建
默认情况下,使用 webpack-simple 模板时,它会将 JavaScript 文件输出到项目的 /dist 文件夹中。
4.3 单文件组件 Vue 具有自己的格式,名为单文件组件(SFC) 。该格式由 Vue 团队创建,文件扩展名为 .vue。允许每个文件编写一个组件,将模板以及该组件的逻辑和样式集中在一个位置,这里的主要优势在于,每个组件都明显独立,更易于维护,易于共享。
单文件组件使用类似 HTML 的语法描述 Vue 组件。它可以包含 3 种类型的根块
1 2 3 <template>,使用我们已经用过的模板语法描述组件的模板; <script>,其中包含组件的 JavaScript 代码; <style>, 其中包含组件使用的样式。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <template> <div> <p>{{ message }}</p> <input v-model="message" />> </div> </template> <script> export default { data() { return { message: "Hello world" }; } }; </script> <style> p { color: grey; } </style>
编辑 main.js 文件,并使用 import 关键字导入单文件组件;
1 import Test from './Test.vue'
移除 render 选项,使用对象展开运算符复制 Test 组件的定义:
1 2 3 4 new Vue({ el: '#app' , ...Test })
组件添加到应用程序的方法:使用 JavaScript 展开运算符,因此 ...App表达式将属性复制到应用定义对象。它的主要优点是,在开发工具中不再用无用的顶层组件;它会成为我们的根组件。
4.3.1 模板 标签包含组件的模板。带有 Vue 特殊语法的 HTML。
如果没有在单文件组件中放置一个 标签,则要编写一个渲染函数,否则你的组件将无效。
使用 Pug
Pug 是一种编译到 HTML 的语言。可以在 lang 属性设置为 ‘pug’ 的 标签内使用它:
1 2 3 4 5 <tempate lang="pug"> ul.movies li.movie Star Wars li.movie Blade Runner </tempate>
为了能够编译单文件组件中的 Pug 代码,需要安装这些包:
1 npm install --save-dev pug pug-loader
开发所需的包称为开发依赖,应该使用 --save-dev 标志进行安装。应用运行需要的直接依赖应该使用 --save 标签进行安装。
JSX
JSX 是在 JavaScript 代码中用来表示 HTML 标记的特殊符号。它使负责描述视图的代码在更接近纯 HTML 语法的同时,仍具有 JavaScript 的全部功能。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <script> export default{ data(){ return{ movies: [ {title: 'Star Wars'}, {title: 'Blade Runner'}, ], } } render (h){ const itemClass = 'movie' return <ul class='movies'> {this.movies.map(movie => <li class={itemClass}>{movie.title}</li>)} </ul> }, } </script>
4.3.3 样式 有作用域的样式
可以使用 style 标签的 scoped 属性将标签内的 CSS 作用域限定在当前组件中。这个 CSS 只会应用于这个组件模板里的元素。
示例:
1 2 3 4 5 6 7 <style scoped> .movie:not(:last-child){ padding-bottom: 6px; margin-bottom: 6px; border-bottom: 1px solid rgba(0,0,0,0.1); } </style>
这要归功于 PostCSS 应用到模板和 CSS 的一个特殊属性。
示例:
1 2 3 4 5 6 7 8 <template> <h1 class="title"> Hello </h1> </template> <style scoped> .title{ color: blue; } </style>
等价
1 2 3 4 5 6 7 8 <template> <h1 class="title" data-v-02ad4e58> Hello </h1> </template> <style> .title[data-v-02ad4e58]{ color: blue; } </style>
有了有作用域的样式并不意味不再需要类。由于浏览器渲染 CSS 的方式,选择带有属性的元素时可能会出现性能损失。例如,当样式作用域限定到组件时, li { color: blue; } 会比 .movie { color: blue; } 慢许多倍。
添加预处理器
Sass 要在组件中启用 Sass,请安装以下包:
1 npm install --save-dev node-sass sass-loader
示例:
1 2 3 4 5 <style lang="sass" scoped> .article .title border-bottom: 3px solid rgba(red,.2) </style>
如果你想使用 Sass 的 SCSS 语法变体,需要使用 lang = 'scss'。
Less
使用 Less,需要安装以下包:
1 npm install --save-dev less less-loader
示例:
1 2 3 4 5 <style lang="less" scoped> .article .title border-bottom: 3px solid rgba(red,20%) </style>
Stylus
使用 Stylus ,需要安装这些包:
1 npm install --save-dev stylus stylus-loader
示例:
1 2 3 4 5 <style lang="stylus" scoped> .article .title border-bottom: 3px solid rgba(red,0.2) </style>
4.3.4 组件内的组件 要在另一个组件中使用组件,我们需要导入它并将它暴露在模板中。
示例: Movie.vue 组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <template> <li class='movie'> {{movie.title}} </li> </template> <script> export default{ props:['movie'], } </script> <style scoped> .movie:not(:last-child){ padding-bottom: 6px; margin-bottm: 6px; border-bottom: 1px solid rgba(0,0,0,.1); } </style>
Movies.vue 组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <template> <ul> <Movie v-for="movie of movies" :key="movie.id" :movie= 'movie' > </ul> </template> <script> import Movie from './Movie.vue'; export default{ components:{ // 相当于 "Movie: Movie," Movie, } data(){ return{ movies:[ {id: 0, title: 'Star Wars'}, {id: 1, title: 'Blade Runner'}, ] } } } </script>
如果使用 JSX,则不需要 components 选项。这是因为如果以大写字母开头,则可以直接使用组件定义:
1 2 3 4 5 6 7 8 import Movies from './Movies.vue' export default{ render (h){ return <Movies/> // 无须通过 components 选项注册 Movies } }
项目 3: 支持中心 5.1 通用应用结构 5.1.1 项目设置 (1)用命令生成一个 Vue 项目。
1 2 3 4 vue init webpack-simple support-center cd support-center npm install npm install --save babel-polyfill
(2)安装编译 stylus 代码所需的包
1 npm install --save-dev stylus stylus-loader
使用 --save-dev 标志将开发工具保存在 package.json 文件的开发依赖中。
(3)移除 src 文件夹中的内容,将在其中放置应用的所有源代码。 (4)然后创建一个 main.js 文件,包含创建 Vue 应用所需的代码:
1 2 3 4 5 6 7 import 'babel-polyfill' ;import Vue from 'vue' new Vue({ el: '#app' , render: h => h('div' , 'Support center' ), })
可以尝试使用 npm run dev 命令运行应用了。
5.1.2 路由和页面 主要页面:
主页 公共 FAQ 页面 登陆页面 工单页面 发送新工单的页面 显示工单详情和对话的页面
路由是表示应用 state 的路径,通常以页面的形式显示。每个路由都与一个 URL 模式相关联,后者在地址匹配时触发路由。然后,相应的页面将呈现给用户。
Vue 插件
(1)下载 vue-router 包:
1 npm install --save vue-router
(2)创建 router.js 文件,并从相应的包中导入 Vue 库 和 VueRouter 插件:
1 2 import Vue from 'vue' ;import VueRouter from 'vue-router'
(3)将该插件安装到 Vue 中:
使用 vue-router 创建第一个路由
使用 router-view 进行布局 在添加路由之前,我们需要为应用设置一个布局。这是将要渲染路由组件的地方。
(1)在 src 目录中新建一个 components 文件夹,并在文件夹中创建一个名为 AppLayout.vue 的组件。
(2)编写组件的模板 (3)添加 stylus 文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <template> <div class="app-layout"> <header class="header"> <div> <img class="img" src="../assets/logo.png" alt="" /> </div> </header> <router-view /> </div> </template> <style lang="stylus"> @import '../style/main'; </style> <style lang="stylus" scoped> .header{ .img{ width: 64px; height: 64px; } } </style>
为了提高性能,建议在范围样式中使用 class。
导入并渲染在 Vue 根实例上:
1 2 3 4 5 6 7 8 import router from './router' import AppLayout from './components/AppLayout.vue' new Vue({ el: '#app' render: h => h(AppLayout), router, })
创建路由 创建一个 Home.vue 组件
1 2 3 4 5 6 7 8 9 <template> <main class="home"> <h1>Welcome to our support center</h1> <p> We are here to help! Please read the <a>F.A.Q</a> first, and if you don't find the answer to your question, <a>send us a ticket!</a> </p> </main> </template>
创建一个 FAQ.vue 组件
1 2 3 4 5 <template> <main class="faq"> <h1>Frenquently Asked Questions</h1> </main> </template>
在 router.js 文件中,导入两个组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import Home from '../components/Home.vue' import FAQ from "../components/FAQ.vue" const routes = [ { path: '/' , name: 'home' , component: Home }, { path: '/faq' , name: 'faq' , component: FAQ } ] const router = new VueRouter({ mode: 'history' , base: process.env.BASE_URL, routes }) export default router
路由名称是可选的,但我强烈建议使用它。它允许你指定路由的名称而不是路径,以便在移动和更换路由时不会导致链接失效。
安装的这个插件也是路由器的构造函数,所以我们使用相同的 VueRouter 变量。
不要忘记 URL 中的 # 字符。在不改变真实网页的情况下伪造路由更改时,它是必须的。这是默认的路由器模式,称为 hash。该模式可以与任何浏览器和服务器一起使用。
路由模式
可以在构造器选项中使用 mode 参数更改路由器模式,可以是 hash(默认)、history 或 abstract。
hash 模式是默认模式。这是“最安全”的选择,因为它与任何浏览器和服务器都兼容。它使用 URL 的 hash 部分(指 # 符号后面的 部分),并对其进行更改或响应其变化。最大的好处是,改变 hash 部分不会改变应用运行的真实网页(改变真实网页是非常不好的)。显而易见的缺点则是,它迫使我们不那么优雅的 # 符号将 URL 分成两部分。
感谢 HTML5 的 history.pushState API,我们可以摆脱这个 # 符号,并为应用获得一个真实的 URL!我们需要在构造函数中将模式更改为 history:
1 2 3 4 const router = new VueRouter({ mode: 'history' , routes })
两个问题: 浏览器需要支持这个 HTML5 API,这意味着它不能在 Internet Explorer 9 或更低版本上工作(所有其他主流浏览器都已经支持它一段时间了)。 服务器必须配置为当访问诸如 /faq 之类的路由时发送主页而不是抛出 404 错误,因为它并不真实存在(没有名为 faq.html 文件)。这意味着我们不得不自己实现 404 页面。
abstract,可以在任何 JavaScript 环境中使用(包括 Node.js)。如果没有可用的浏览器 API,路由器将被迫使用此模式。
3.创建导航菜单
新建 NavMenu.vue 文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <template> <nav class="menu"> <router-link :to="{ name: 'home' }">Home</router-link> <router-link :to="{ name: 'faq' }">FAQ</router-link> </nav> </template> <script> export default {}; </script> <style lang="stylus" scoped> @import '../style/imports'; .router-link-active { border-bottom-color : $primary-color; } </style>
添加到布局中。在 AppLayout 中导入新组建:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <template> <header class="header"> <div> <img class="img" src="../assets/logo.png" alt="" /> <div>My shirt shop</div> </div> </header> <NavMenu /> </header> </tempate> <script> import NavMenu from "./NavMenu"; export default { components: { NavMenu } }; </script>
路由器链接
vue-router 插件为我们提供了特殊组件 router-link。当这个组件被点击时,就会变为指定路由,这要归功于它的 to prop。默认情况下,它将是一个 HTML a 元素, 但可以使用 tag prop 来自定义。
示例:
1 2 3 4 <router-link to="/faq">FAQ</router-link> to prop 也可以使用包含 name 属性的对象而不是路径: <router-link :to="{ name: 'faq' }">FAQ</router-link>
这将动态地为路由生成正确的路径。我建议使用第二种方法,而不是只指定路径 —— 这样,这样,如果更改路由的路径,导航链接仍然可以工作。
当使用对象记法时,不要忘记用 v-bind 或者 : 简写来绑定 to prop,否则 router-link 组件会得到一个字符串,并不会理解它是一个对象。
active class 路由器链接在与其关联的路由当前处于激活状态时获取 active class。默认情况下,组件使用 router-link-active CSS 类。
默认情况下,active class 匹配行为是包容的!这意味着如果路径为 /fag 或以 /fag/开头,router-link to = ‘/faq’ 都将获得 active class 。 为了防止发生这种情况,有一个 exact prop,它是布尔值。如果设置其为 true,则仅在当前路径完全匹配时,链接才能获得 active class。
示例:
1 <router-link :to="{ name: 'home' }" exact>Home</router-link>
5.2.2 使用 fetch fetch API 是基于 Promise 的,使用起来非常简单。以下是 fetch 用法的示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 fetch(url).then(response => { if (response.ok){ return response.json() }else { return Promise .reject('error' ); }).then(result => { console .log('JSON' ,result); }).catch(e => { console .error(e); })
加载动画
创建全局组件 新建 Loading.vue 文件:
1 2 3 4 5 <template> <div class="loading"> <div></div> </div> </template>
新建 global-components.js。使用 Vue.component() 方法全局地注册 Loading 组件;
1 2 3 4 import Vue from 'vue' import Loading from './components/Loading.vue' Vue.component('Loading' , Loading);
在 main.js 文件中导入 global-components.js 模块:
1 import './global-components'
回到 FAQ.vue 组件,使用动画
1 2 3 <template> <Loading v-if='loading' /> </tempalte>
5.2.3 用自己的插件扩展 Vue 1.创建一个插件
要创建插件,只有一个规则 —— 插件应该是一个带有 install 方法的对象,该方法接收 Vue 构造函数作为第一个参数以及一个可选的 options 参数。该方法将通过修改构造函数为框架添加新特性。
(1)在 src 文件夹中新建 plugins 文件夹。 (2)在 plugins 文件夹中新建 fetch.js 文件。 (3)导入一个带 install 对象的方法,尝试创建插件:
1 2 3 4 5 export default { install (Vue) { console .log('Installed!' ) } }
(4) 导入插件
1 2 import VueFetch from './plugins/fetch' Vue.use(VueFetch)
2.插件选项
(1)编辑 install 方法
1 2 3 4 5 export default { install (Vue,options) { console .log('Installed!' ,options) } }
(2)添加 baseUrl 属性到配置中:
1 2 3 Vue.use(VueFetch,{ baseUrl: 'http://localhost:3000/' })
(3)将 baseUrl 存储到一个变量中
1 2 3 4 5 6 7 8 9 let baseUrl;export default { install (Vue,options){ console .log('Installed' ,options) baseUrl = options.baseUrl } }
3.$fetch 方法 (1)使用 fetch 实现 $fetch 方法:
1 2 3 4 5 6 7 8 9 10 11 export async function $fetch (url ) { const response = await fetch(`${baseUrl} ${url} ` ) if (response.ok){ const data = await response.json() return data }else { const error = new Error ('error' ) throw error; } }
(2) 为了所有组件中可用,只需要将其添加到 Vue 的原型(这是用于创建组件的构造函数)中即可:
1 2 3 4 5 6 export default { install (Vue, options){ baseUrl = options.baseUrl Vue.prototype.$fetch = $fetch } }
(3)重构 FAQ 组件
1 2 this.loading = true; try{ this.questions = await this.$fetch('questions'); } catch(e){ this.error = e } this.loading = false
使用 mixin 复用代码 mixin 是可应用于其他定义对象(包括其他 mixin)的组件定义对象。
(1)导出一个具有数据属性的定义
1 2 3 4 5 6 7 export default { data (){ return { remoteDataLoading: 0 , } } }
remoteDataLoading 属性将用于计算当前正在加载请求的数量,以帮助我们显示加载动画。
(2)使用 mixin
1 2 3 4 5 6 7 import RemoteData from '../mixins/RemoteData' export default { mixins: [ RemoteData, ] }
mixin 被应用合并到了 FAQ.vue 组件定义中。意味着 data 钩子被调用两次:首先在 mixin 中调用,然后在 FAQ 定义中添加了一个新属性!
Vue 会自动合并标准选项,如钩子、数据、计算属性、方法和侦听器,如果有一个方法的属性具有相同名称,最后应用的那个将覆盖之前的那些。
data、created、mounted 等钩子都按它们应用于最终定义的顺序逐一调用。
这意味着最后的组件定义钩子将被最后调用。
1.获取远程数据
(1)使用一个带有 resources 参数的函数封装的对象:
1 2 3 4 5 6 7 8 9 export default function (resources ) { return { data (){ return { remoteDataLoading: 0 , } } } }
(2)改变在 FAQ.vue 组件中使用 mixin 的方式
1 mixins:[ RemoteData({ questionList: 'questions' }) ]
(3)数据属性初始化,以便 Vue 设置响应式属性:
1 2 3 4 5 6 7 8 9 10 data ( ) { let initData = { remoteDataLoading:0 } for (const key in resources){ initData[key] = null } return initData }
(4)创建新的 fetchResource 方法获取资源并更新数据
1 2 methods: { async fetchResource (key,url){ try{ this.$data[key] = await this.$fetch(url) }catch(e){ console.error(e); } } }
(5)在 created 钩子中自动调用它
1 2 created(){ for(const key in resources){ let url = resources[key] this.fetchResource(key,url) } }
(6)更改模板
1 <article v-for="question of questionList">
2.加载管理
(1)增加两个语句,分别递增和递减计数器:
1 2 3 4 5 6 7 8 9 10 11 async fetchResource (key, url ) { this .$data.remoteDataLoading++; this .$data.remoteErrors[key] = null ; try { this .$data[key] = await this .$fetch(url); } catch (e) { console .error(e); this .$data.remoteErrors[key] = e; } this .$data.remoteDataLoading--; }
(2)添加计算属性
1 2 3 4 5 6 7 8 9 10 11 computed: { remoteDataBusy ( ) { return this .$data.remoteDataLoading !== 0 ; }, hasRemoteErrors ( ) { return Object .keys(this .$data.remoteErrors).some( key => this .$data.remoteErrors[key] ); } }
(3)改变 Loading 组件
1 <Loading v-if="remoteDataBusy" />>
3.错误管理 (1)错误存储
1 2 3 4 5 6 initData.remoteErrors = {} for (const key in resources){ initData[key] = null initData.remoteErrors[key] = null }
(2)fetchResource 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 async fetchResource (key, url ) { this .$data.remoteDataLoading++; this .$data.remoteErrors[key] = null ; try { this .$data[key] = await this .$fetch(url); } catch (e) { console .error(e); this .$data.remoteErrors[key] = e; } this .$data.remoteDataLoading--; }
(3)检查
1 2 3 4 5 6 7 computed: { hasRemoteErrors ( ) { return Object .keys(this .$data.remoteErrors).some( key => this .$data.remoteErrors[key] ); } }
(4)替换 error 属性
1 <div class="error" v-if="hasRemoteErrors">
5.3 支持工单 5.3.1 用户认证 1.将用户存储在一个集中式 state 里
(1)新建 state.js 文件,用于导出 state 对象
1 2 3 export default { user: null }
(2)导入 state: (3)根实例的数据,以便 Vue 使其成为响应式的:
1 2 3 4 5 6 7 8 import state from './state' new Vue({ el: "#app" , data: state, router, render: h => h(AppLayout), })
另一个插件
(1)在 plugins 文件夹中,创建新插件 state.js 文件:
1 2 3 4 5 6 7 export default { isntall (Vue, state ) { Object .defineProperty(Vue.prototype, "$state" , { get: () => state }); } };
使用 JavaScript Object.defineProperty()方法在 Vue 原型上设置一个 getter,每个组件都会继承它!
(2)在 main.js 文件中,导入新的插件: (3)使用 state 对象作为选项参数进行安装:
1 2 import VueState from "./plugins/state" ;Vue.use(VueState, state);
2.登录表单
(1)在 components 文件夹中创建一个新的 SmartForm.vue 组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <template> <form @submit.prevent="submit"> <section class="content"> <h2>{{ title }}</h2> <slot /> <div class="actions"> <slot name="actions" /> </div> <div class="error" v-if="error"> {{ error }} </div> </section> <template name="fade"> <Loading v-if="busy" class="overlay" /> </template> </form> </template>
在 form 元素上,submit 事件上设置了一个事件监听器。它使用 prevent 修饰符阻止浏览器的默认行为(重新加载页面)。
SmartForm 组件有 3 个属性
title:显示在 h2 元素中。 operation:提交表单时调用的异步函数,它应该返回一个 Promise。 valid:布尔值,以防止表单在无效时调用操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <script> export default { props: { title: { type: String, required: true }, operation: { type: Function, required: true }, valid: { type: Boolean, required: true } } }; </script>
声明 prop —— 通过使用对象,可以指定 prop 的更多细节。Vue 检查的类型。
busy:布尔值,用于切换加载动画的显示。 error:错误消息,没有则为 null。
(3)添加到 data 钩子里:
1 data() { return { error: null, busy: false }; }
(4)编写提交表单
1 2 3 methods: { async submit() { if (this.valid && !this.busy) { this.error = null; this.busy = true; try { await this.operation(); } catch (e) { this.error = e.message; } this.busy = false; } } }
(5)注册
1 2 import SmartForm from './components/SmartForm.vue' Vue.component('SmartForm',SmartForm)
不要忘记 name 属性 ,它将允许浏览器自动补全字段。
1 2 3 4 5 6 7 <style lang='stylus' scoped> .formm { >>> .content{ max-width : 400px ; } } </style >
连结符允许将模板中使用的组件内的元素作为目标,同时限定 CSS 选择器其余部分的作用域。
生成的示例:
1 2 3 .form [data-v-0e596401] .content { max-width : 400px ; }
不使用连结符,则会
1 2 3 .form .content [data-v-0e596401] { max-width : 400px ; }
如果使用 SASS , 则需要使用 /deep/ 选择器。
带导航守卫的私有路由 路由元属性
1 {path : '/tickets' , , meta: { private : true }}
路由器导航守卫
beforeEach to 是当前的目标路由; from 是以前的路由; next 是为了完成解析不得不在某个时刻调用的函数。
(1)在导出路由器实例之前,添加 beforeEach 导航守卫
1 2 3 4 5 router.beforeEach((to,from ,next )=> { console .log('to' ,to.name); next() })
(2)现在需要确定目标路由是否为私有路由: 导航到期望的路由
1 2 3 4 5 6 7 8 9 10 if (to.meta.private && !state.user){ next({ name : 'login' , params: { wantedRoute: to.fullPath, } }) return }
router.replace() 方法与 router.push()方法相似,区别在于前者浏览器历史记录中的当前条目替换为新路由,而不是添加新条目。
项目 5:在线商店以及扩展 高级开发流程
1 2 3 4 5 6 vue init webpack-simple e-shop cd e-shopnpm install npm install -S babel-polyfill npm i -D stylus stylus-loader npm i -S axios vue-router vuex vuex-router-sync
本地 API
添加脚本启动 JSON 服务器
1 "db": "json-server --watch db.json"
使用 PostCSS 自动添加前缀 vue-loader 已经包含了 PostCSS
在项目根目录添加 postcss.config.js 配置文件。
1 2 3 4 5 module .exports = { plugins: [ require ('autoprefixer' ), ], }
通过 browserslist 指定浏览器
1 2 3 > 1% last 2 versions Firefox ESR
通过 ESLint 提升代码质量和风格 使用 StandardJS 预设规则以及 eslint-plugin-vue
1 2 3 4 5 npm i -D eslint eslint-config-standara eslint-plugin-vue@beta // 安装 eslint-config-standard 的平级依赖 npm i -D eslint-plugin-import eslint-plugin-node eslint-plugin-promise eslint-plugin-standard // 为了使 ESLint 解析文件时支持使用 Babel 的 JavaScript 代码 npm i -D babel-eslint
配置 ESLint
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 module .exports = { root: true , parser: 'vue-eslint-parser' , parserOptions: { 'parser' : 'babel-eslint' , 'ecmaVersion' : 2017 , 'sourceType' : 'module' }, env: { browser: true , es6: true , jest: true , }, extends : [ 'standard' , 'plugin:vue/recommended' , ], rules: { 'no-use-before-define' : 'off' , 'comma-dangle' : ['error' , 'always-multiline' ], }, }
Webpack 中使用 ESLint
1 npm i -D eslint-loader friendly-errors-webpack-plugin
1 2 3 4 5 6 7 8 9 module : { rules: [ { test: /\.(jsx?|vue)$/ , loader: 'eslint-loader' , enforce: 'pre' , }, ] }
Jest 单元测试 1 npm i -D jest vue-test-utils vue-jest jest-serializer-vue vue-server-renderer babel-jest babel-plugin-dynamic-import-node
配置 Jest 在项目根目录中创建 jest.config.js 配置 Jest;
1 2 3 4 5 6 7 8 9 10 module .exports = { transform: { '.+\\.jsx?$' : '<rootDir>/node_modules/babel-jest' , '.+\\.vue$' : '<rootDir>/node_modules/vue-jest' , }, snapshotSerializers: [ '<rootDir>/node_modules/jest-serializer-vue' , ], mapCoverage: true , }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 import BaseButton from './BaseButton.vue' import { shallow } from 'vue-test-utils' import { createRenderer } from 'vue-server-renderer' describe('BaseButton' , () => { test('click event' , () => { const wrapper = shallow(BaseButton) wrapper.trigger('click' ) expect(wrapper.emitted().click).toBeTruthy() }) test('icon prop' , () => { const wrapper = shallow(BaseButton, { propsData: { icon: 'add' , }, }) expect(wrapper.contains('.icon' )).toBe(true ) const icon = wrapper.find('.icon' ) expect(icon.text()).toBe('add' ) }) test('snapshot' , () => { const renderer = createRenderer() const wrapper = shallow(BaseButton, { propsData: { icon: 'add' , disabled: true , badge: '3' , }, slots: { default : '<span>Add Item</span>' , }, }) renderer.renderToString(wrapper.vm, (err, str ) => { if (err) throw new Error (err) expect(str).toMatchSnapshot() }) }) })
在 package.json 添加运行脚本
1 2 "jest" : "jest" "jest:update" : "jest --updateSnapshot"
vue-i18n 国际化
1 2 3 4 5 6 7 import Vue from 'vue' import Http from './utils/http' import VueI18n from 'vue-i18n' Vue.use(Http) Vue.use(VueI18n)
项目 6:使用 Meteor 开发实时仪表盘 安装 Meteor 并设置项目 Meteor Meteor 是一个用于构建 Web 应用的全栈 JavaScript 框架 。
Meteor 栈主要由以下元素构成:
Web 客户端(可以使用任意前端库,如 React 或 Vue),包含一个名为 minimongo 的客户端数据库;
基于 Node.js 的服务器,支持现代的 ES2015+ 特性,包括 import / export 语法;
服务器使用 MongoDB 实时数据库;
客户端和服务器的通信是抽象的,客户端和服务端数据库能方便地实时同步;
可选的混合移动应用(Android 和 IOS),能用一条命令构建;
完整的开发者工具,如功能强大的命令行实用程序和易用的构建工具;
Meteor 专用包(也可以实用 npm 包)。
Meteor 没有实用 Webpack。
安装 Meteor 我有单独 Meteor 文章可以那里有安装详细
1 2 // 查看当前 Meteor 版本 meteor --version
创建项目 1 2 meteor create --bare <folder> cd <folder>
–bare 参数告诉 Meteor 创建一个空的项目。
1 2 3 4 5 6 // 添加 编译 vue 组件,编译 stylus 专用包 meteor add akryum:vue-component akryum:vue-stylus // 安装 vue 和 vue-router 包 meteor npm i -S vue vue-router // 启动应用 meteor
meteor 会启动一个 HTTP 代理,一个 MonoDB 和 Node.js 服务器。
1 2 3 4 5 6 7 8 9 10 11 import { Meteor } from 'meteor/meteor' import Vue from 'vue' import App from './components/App.vue' Meteor.startup(() => { new Vue({ el: '#app' , router, ...App, }) })
在 Meteor 应用中,建议在 Meteor.startup 钩子内创建你的 Vue 应用,这样可以保证整个 Meteor 系统在启动前端之前准备完毕。
使用 Meteor 方法将数据保存到 Meteor 集合(collection) 中 将 Meteor 集合集成到应用中,用于应用的自动更新。
1 meteor npm i -S vue-meteor-tracker
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import { Meteor } from 'meteor/meteor' import Vue from 'vue' import App from './components/App.vue' import VueMeteorTracker from 'vue-meteor-tracker' import router from './router' import 'vue-progress-path/dist/vue-progress-path.css' import VueProgress from 'vue-progress-path' Vue.use(VueMeteorTracker) Vue.use(VueProgress, { defaultShape: 'semicircle' , }) Meteor.startup(() => { new Vue({ el: '#app' , router, ...App, }) })
设置 Meteor 集合,用于存储测量记录数据。
1 2 3 4 import { Mongo } from 'meteor/mongo' export const Measures = new Mongo.Collection('measures' )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import { Meteor } from 'meteor/meteor' import { Measures } from './collections' Meteor.methods({ 'measure.add' (measure) { Measures.insert({ ...measure, date: new Date (), }) }, }) Meteor.call('measure.add' , measure);
Measures 集合发布
1 2 3 4 5 6 7 import { Meteor } from 'meteor/meteor' import { Measures } from '../lib/collections' Meteor.publish('measures' , function ( ) { return Measures.find({}) })
相关资料关于vuex-router-sync的作用,或者可以解决什么问题 √ browserslist jest Vue 组件单元测试指南