650 | ```
651 |
652 | 好棒!q(≧▽≦q) 现在我们有了在番茄钟状态变化时自动变化的标题了:
653 |
654 | 
655 |
656 | 基于计时器状态自动更新的标题
657 |
658 | 很棒吧!
659 |
660 | ## 使用计算属性制造左间距
661 |
662 | 现在解决第二个问题。 我们添加两个计算属性, *min* 和 *sec*, 在 *data* 选项的计算部分并运用简单的算法在零的左边制造一个边距。 当然, 我们可以使用一个非常著名的项目 ( https://github.com/stevemao/left-pad ), 但是为了更简单并不破坏整个互联网 ( http://www.theregister.co.uk/2016/03/23/npm_left_pad_chaos/ ), 我们自己添加一个简单的逻辑:
663 |
664 | ```js
665 | computed: {
666 | title: function () {
667 | return this.pomodoroState === POMODORO_STATES.WORK ? 'Work!' :
668 | 'Rest!'
669 | },
670 | min: function () {
671 | if (this.minute < 10) {
672 | return '0' + this.minute;
673 | }
674 | return this.minute;
675 | },
676 | sec: function () {
677 | if (this.second < 10) {
678 | return '0' + this.second;
679 | }
680 | return this.second;
681 | }
682 | }
683 | ```
684 |
685 | 并在 HTML 内使用这些属性值:
686 |
687 | ```html
688 |
689 | {{ min }}:{{ sec }}
690 |
691 | ```
692 |
693 | 刷新页面查看效果:
694 |
695 | 
696 |
697 | 在 Vue.js 用计算属性制造左边距
698 |
699 | ## 用开始, 暂停, 停止按钮保持状态
700 |
701 | 所以呢, 现在来修复第三个问题, 我们引入三个方程式状态, *started, paused, stopped*, 我们来创建三个方法来切换这个三个状态。 我们已经拥有了开始方法, 所以我们只需要添加一点逻辑调整状态到 *started*。 我们也添加两个额外的方法, *pause, stop*, 它们将改变番茄钟对应的状态:
702 |
703 | ```js
704 | const POMODORO_STATES = {
705 | WORK: 'work',
706 | REST: 'rest'
707 | };
708 | const STATES = {
709 | STARTED: 'started',
710 | STOPPED: 'stopped',
711 | PAUSED: 'paused'
712 | };
713 | //<...>
714 | new Vue({
715 | el: '#app',
716 | data: {
717 | state: STATES.STOPPED,
718 | //<...>
719 | },
720 | //<...>
721 | methods: {
722 | start: function () {
723 | this.state = STATES.STARTED;
724 | this._tick();
725 | this.interval = setInterval(this._tick, 1000);
726 | },
727 |
728 | pause: function () {
729 | this.state = STATES.PAUSED;
730 | clearInterval(this.interval);
731 | },
732 |
733 | stop: function () {
734 | this.state = STATES.STOPPED;
735 | clearInterval(this.interval);
736 | this.pomodoroState = POMODORO_STATES.WORK;
737 | this.minute = WORKING_TIME_LENGTH_IN_MINUTES;
738 | this.second = 0;
739 | },
740 | //<...>
741 | }
742 | });
743 | ```
744 |
745 | 在我们的 HTML 内添加两个按钮并添加 *click* 监听器, 调用相应的方法:
746 |
747 | ```html
748 |
752 |
755 |
759 | ```
760 |
761 | 现在我们的方程式看起来是这样的:
762 |
763 | 
764 |
765 | 根据方程式的状态切换开始, 暂停, 停止按钮
766 |
767 | 在 JSFiddle 查看效果 https://jsfiddle.net/chudaol/b6vmtzq1/1/
768 |
769 | 做了这么多工作后, 你应该好好休息一下了, 这里有很多可爱的小猫, 来看看吧(●'◡'●) http://thecatapi.com/:
770 |
771 | 
772 |
773 | ## 练习
774 |
775 | 在本章的最后, 我将布置一个小练习。 我们在前面构建的番茄钟毫无疑问是很棒的, 但是依旧缺乏一些功能。 一个非常棒的功能就是从 http://thecatapi.com/ 在休息状态下显示一只小猫。 你能实现吗? 当然, 你可以的! 但别被小猫萌翻哦(●'◡'●)。
776 |
777 | ## 总结
778 |
779 | 我非常高兴你看到这里了, 这意味着你已经知道 Vue.js 是什么, 如果有人问你它是一个工具还是库还是框架时, 你可以准确地找出答案。 你也知道如何用 Vue.js 来启动一个方程式并在已有的项目内使用 Vue 的功能。 你看了那些用 Vue.js 写的项目并开始开发一个自己的项目! 现在你在采购时有了一个自己用 Vue.js 创建的购物清单方程式了! 也有了自己的番茄钟。 还有最重要的是, 你可以在番茄钟内放入一只随机的猫咪了(>^ω^<)。
780 |
781 | 在下一章, 我们将探究 Vue 的幕后, 它是如何运作的, 它使用的架构模式又是怎样的。 对于事例中的每个概念我们将以 demo 的形式来演示它。 然后我们将深入代码并完善我们的方程式, 让它们变得更棒。
782 |
--------------------------------------------------------------------------------
/chapter-1/Thumbs.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-1/Thumbs.db
--------------------------------------------------------------------------------
/chapter-1/imgs/1-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-1/imgs/1-1.png
--------------------------------------------------------------------------------
/chapter-1/imgs/1-10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-1/imgs/1-10.png
--------------------------------------------------------------------------------
/chapter-1/imgs/1-11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-1/imgs/1-11.png
--------------------------------------------------------------------------------
/chapter-1/imgs/1-12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-1/imgs/1-12.png
--------------------------------------------------------------------------------
/chapter-1/imgs/1-13.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-1/imgs/1-13.png
--------------------------------------------------------------------------------
/chapter-1/imgs/1-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-1/imgs/1-2.png
--------------------------------------------------------------------------------
/chapter-1/imgs/1-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-1/imgs/1-3.png
--------------------------------------------------------------------------------
/chapter-1/imgs/1-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-1/imgs/1-4.png
--------------------------------------------------------------------------------
/chapter-1/imgs/1-5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-1/imgs/1-5.png
--------------------------------------------------------------------------------
/chapter-1/imgs/1-6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-1/imgs/1-6.png
--------------------------------------------------------------------------------
/chapter-1/imgs/1-7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-1/imgs/1-7.png
--------------------------------------------------------------------------------
/chapter-1/imgs/1-8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-1/imgs/1-8.png
--------------------------------------------------------------------------------
/chapter-1/imgs/1-9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-1/imgs/1-9.png
--------------------------------------------------------------------------------
/chapter-1/imgs/Thumbs.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-1/imgs/Thumbs.db
--------------------------------------------------------------------------------
/chapter-1/imgs/qr1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-1/imgs/qr1.png
--------------------------------------------------------------------------------
/chapter-1/imgs/qr2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-1/imgs/qr2.png
--------------------------------------------------------------------------------
/chapter-2/Fundamentals.md:
--------------------------------------------------------------------------------
1 | # Fundamentals – Installing and Using
2 | 在前一章, 我们熟悉了 Vue.js 。我们已经在两个不同的程序中使用了它。 我们学习了如何在项目中应用 Vue.js。 我们实践了响应式数据绑定。
3 |
4 | 现在, 你可能会问自己: Vue 是怎样运作的? 它又是如何在数据模型变化时快速响应 UI 变化的? 你可能决定在项目中使用它, 想知道用不用遵循一些架构模式或者范例。 在本章我们将探索这些关键的概念, 理解隐藏在 Vue.js 背后的特性。 我们将分析所有安装 Vue.js 的方法, 同时为我们的方程式创建一个骨架, 然后在后续章节中不断地增强改进。 我们也会学习调试、 测试我们的方程式。
5 |
6 | 所以呢, 在本章,我们将学习如下内容:
7 | * 什么是 MVVM 架构, 在 Vue.js 中如何体现
8 | * 什么是声明式视图
9 | * Vue.js 如何定义属性, getters、 setters
10 | * 响应式和数据绑定如何运作
11 | * 什么是脏检查机制、 DOM、 虚拟 DOM
12 | * Vue.js 1.0 和 2.0 的区别
13 | * 什么是可重用的组件
14 | * 插件、 指令、 自定义插件、 自定义指令如何在 Vue.js 中运作
15 | * 如何安装、 运行、 调试 Vue 方程式
16 |
17 | ## MVVM 架构模式
18 | 你还记得我们是怎么创建 Vue 实例的吗? 我们以关键字 new Vue({...}) 来创建实例。 你一定记得在配置项中, 我们可以把 data 对象传递给绑定的元素。 data 对象是我们的模型, 而 DOM 元素是 Vue 实例绑定的地方。
19 |
20 | 
21 |
22 | Vue 中的经典视图模型事例
23 |
24 | 同时, 我们的 Vue 实例帮助我们把模型绑定到视图中, 反之亦然。 我们的方程式因此遵循着 Model-View-ViewModel(MVVM) 模式。
25 |
26 | 
27 |
28 | Model-View-ViewModel 模式简化图
29 |
30 | 我们的模型包含数据和一些基本逻辑, 视图对之响应。 视图模型控制数据绑定, 保证在模型数据的变化会迅速反应在视图中, 反之亦然。
31 |
32 | 我们的视图因此完全是数据驱动的。 视图模型响应了对数据流的控制, 这样数据绑定的声明才管用。
33 |
34 | ## 定义属性, getters、 setters
35 | 数据一旦传入 Vue 实例后将发生什么呢? 为什么这些被 Vue 应用的转换会自动地绑定到视图呢?
36 |
37 | 我们来分析一下我们需要做什么, 我是说, 每当我们需要给一个字符串应用一些变化给 DOM 元素时,我们该怎样应用这个监听函数? 在 *var stringVar = 'hello' ; stringVar.onchange(doSomething)* 中有太多的工作要做。
38 |
39 | 我们可以包装字符串的值, 设置一些 setting 或者 getting 函数, 当每次更新字符串的时候, DOM 也随之更新。 你会怎样实现这个功能呢? 当你思考这个问题时, 我将准备一些有趣而简短的例子。
40 |
41 | 在你的购物清单方程式中打开你的开发者工具, 写下如下代码:
42 |
43 | ```js
44 | var obj = {};
45 | var text = '';
46 | ```
47 |
48 | 将我们 DOM 元素赋值给 h2
49 |
50 | ```js
51 | var h2 = document.getElementsByTagName('h2')[0];
52 | ```
53 |
54 | 如果我们把 *text* 的值赋给 *obj.text* 属性, 我们怎样才能追踪 *h2* 中每一次的属性变化呢?
55 |
56 | 我们可以使用 *Object.defineProperty* 方法(https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/define)。
57 |
58 | 这个方法允许我们创建一个 *getter、 setter* 函数, 因此可以获取这些属性的变化。
59 |
60 | ```js
61 | Object.defineProperty(obj, 'text', {
62 | get: function () {
63 | retuen text;
64 | }
65 | set: function (newVal) {
66 | text = newVal;
67 | h2.innerHTML = text;
68 | }
69 | });
70 | ```
71 |
72 | 现在在控制台改变 *obj.text* 的属性, 观察标题
73 |
74 | 
75 | object.defineProperty 在每次属性变化时被调用
76 |
77 | Vue.js 就是应用了这个机制。 当数据被传入到 Vue 实例中时, 它的所有属性都将通过 *object.defineProperty* 方法。 在页面中的每个指令,都被安排了一个 *set* 方法。 打开 *vue.js* 文件并搜索 *set: function reactiveSetter(newVal)* 。增加一个断点并调试购物列表的输入, 直到找到在函数中最后一个叫 *dep.notify()* 的调用:
78 |
79 | 
80 | 在 setter 函数中调用观察方法处的断点
81 |
82 | 在这个函数中, 你可以看到它正在遍历每一个属性的观察器, 并更新它们。 如果你跳过这个调用, 你将不会看到 DOM 的更新。 因为更新上执行的事件轮询被放在了一个定期执行的队列里。
83 |
84 | 找到 *runBatcherQueue* 函数, 并放入断点。 再次尝试改变标题的值。 如你所见, 这个函数将遍历正在队列里等待的观察器, 然后调用它们的 *run* 方法。 观察这个方法, 你可以看到它在比较新值和旧值。
85 |
86 | ```js
87 | if (value !== this.value || ...)
88 | ```
89 |
90 | 然后会启动一个回掉
91 |
92 | ```js
93 | this.cb.call(this.vm, value, oldValue);
94 | ```
95 |
96 | 如果你再仔细研究一个这个回掉函数, 你会发现它会在最后更新 DOM 值。
97 |
98 | ```
99 | update: function update(value) {
100 | this.el[this.attr] = _toString(value);
101 | }
102 | ```
103 |
104 | 很简单不是吗!
105 |
106 | ### 注意
107 | 在这里用的是 Vue.js 1.0版本。
108 |
109 | 所以呢, Vue.js 的响应式机制非常简单。 观察器被赋到所有指令和数据属性上, 然后在 *Object.defineProperty* 的 *set* 方法上, 依次通知更新每个 DOM 或数据。
110 |
111 | 
112 |
113 | 数据对象到 DOM 间的数据流
114 |
115 | 那些拥有指令的 **DOM** 元素都被附着上了监听器, 来依次通知更新每次调用的数据值。
116 |
117 | ## 和其他框架的比较
118 |
119 | 当你尝试一个新工具时, 你一定想和其它工具或框架相比较一下。 你可以在 Vue.js 的官方页面找到一些深度地比较: http://vuejs.org/guide/comparison.html 。 在这些框架中我将在这里列出一些我认为重要的说明。
120 |
121 |
122 | ### React
123 | React 和 Vue 很像。 他们都使用了虚拟 DOM , 拥有可重用的组件, 响应式数据。 它值得一提, 但是, Vue 从 2.0 才开始使用虚拟 DOM。 2.0 之前它使用的是真实 DOM。 2.0 版本性能远超 1.0 同时也超过 React ( http://vuejs.org/guide/comparison.html#Performance-Profiles )。
124 |
125 | 两个框架最大的差别在于使用组件的方式。 你可能已经知道, 在 React 中所有一切都是 JavaScript。 用 JavaScript 开发所有的东东, 甚至是模板, 方程式一直运行在相同的作用域里,渲染变得非常有弹性。
126 |
127 | 但是, 一些需要快速原型的初级设计者, 或者是那些不想学习 JSX 的人来说, 这真是个痛点。 在 Vue 组件中, 你也可以使用 JSX, 也可以使用一般的 web 开发结构: 在 style 标签中写 CSS, 在 template 中写 HTML,在 script 标签中写 JavaScript 。 例如比较在 React 中写的渲染函数和在 Vue 组件的模板, 我将展示一个简单的事例来说明这些不同:
128 |
129 |
130 | ```js
131 | render () {
132 | return (
133 |
143 | )
144 | }
145 | ```
146 |
147 | 使用 Vue , 你只需写一些 HTML 代码在 *template* 标签中:
148 |
149 | ```html
150 |
151 |
160 |
161 | ```
162 |
163 | 我个人喜欢把这些东西都分离开, 因此我发现 Vue 这种方式更对口。
164 |
165 | 另一件很棒的事是 Vue 允许你在组件中使用带有作用域的样式, 只需要在 *style* 标签加上 scoped 属性。
166 |
167 | ```html
168 |
170 | ```
171 |
172 | 有了这个样式, 当你使用预处理器时,你可以在所在组件定义全局变量并创建或重定义样式。
173 |
174 | 值得一提的还有两个框架的学习曲线。 学习 React 你可能需要学习 JSX 和 ES2015 的语法, 因为大多数 React的官方事例都在用。 学习 Vue 你完全不需要这些, 就像使用 jQuery 一样,使用 Vue 模型和数据绑定非常简单。 然后选择对你有用的部分就行。
175 |
176 | 如果你想再深入地比较两个框架, 去看看文档, 精心制作相似的事例, 再看看哪个框架适合你。
177 |
178 | ### Angular
179 | Angular 1 和 Angular 2 差别很大。 Angular 2 和 Angular 1 完全不一样。 它拥有更好的表现, API 不同了, 底层实现也被重写了。
180 |
181 | 这两个版本是如此不同以至于在 Vue 的官方文档中分别对这两个框架进行了比较。 但是横向对比这两个版本的 Angular ,你会发现 Vue 比它们更加友好。 对比 Angular 和 Vue 的 hello world 方程式 (https://angular.io/docs/js/latest/quickstart.html )
182 | (http://vuejs.org/guide/index.html#HelloWorld)
183 |
184 | > "即使没有 TypeScript, Angular 的快速指南也需要 ES2015, 18 个 NPM 依赖, 4 个文件, 3000 多行代码来输出 Hello World."
185 |
186 | --http://vuejs.org/guide/comparison.html#Learning-Curve
187 |
188 | 如果你依然在使用 Angular 1, 值得一提的差别在于 Angular 的脏检查机制, 在存在大量观察器时, Angular 性能会明显下降, 而 Vue 只会重解析变化属性的观察器, 其它观察器不会变化。
189 |
190 | ### Vue
191 | 我没写错, 这也值得比较。 Vue 最近已经升级到了更快更轻的第二版, 如果你还在用第一版, 绝对值得升级。 你可以在这里查看 2016 年 4 月份发布的相关信息 https://vuejs.org/2016/04/27/announcing-2.0/
192 |
193 |
194 | ## Vue.js 基础
195 |
196 | 在开始编码之前,让我们来回顾一下 Vue 的特性。 分析下什么是可重用的组件, 如何控制方程式的状态, 谈谈插件, 过滤器, 混入。 在这一部分, 我们将稍微浏览一下这些特性。 后面再深入学习。
197 |
198 | ### 可重用的组件
199 | 既然你知道如何使用数据绑定, 也知道它如何运转, 是时候介绍另一项杀手级功能了。 Vue.js 创建的组件可以像盖房的砖块一样重用。 每个组件都拥有自己属于自己的样式和模板, 完全独立于其它组件。
200 |
201 | 创建组件的语法和创建 Vue 实例的语法很相似,你应该使用 *Vue.extend* 而非 *Vue* :
202 |
203 | ```js
204 | var customComponent = Vue.extend({...})
205 | ```
206 |
207 | 
208 |
209 | Vue.js 中的自定义组件
210 |
211 | 例如,把我们的购物列表拆分成组件。 如你所知, 我们的购物列表有三个基本部分: 列表项, 输入项, 标题变更项:
212 |
213 | 
214 |
215 | 我们购物清单方程式的三个基本部分
216 |
217 | 我们可以把三个基本部分变为组件
218 |
219 | ```js
220 | var data ={
221 | items: [{text: 'Bananas', checked: true},
222 | {text: 'Apples', checked: false}
223 | ],
224 | title: 'My Shopping List',
225 | newItem: ''
226 | };
227 | new Vue({
228 | el: '#app',
229 | data: data,
230 | methods: {
231 | addItem: function () {
232 | var text;
233 | text = this.newItem.trim();
234 | if(text) {
235 | this.items.push({
236 | text: text,
237 | checked: false
238 | });
239 | this.newItem = '';
240 | }
241 | }
242 | }
243 | });
244 | ```
245 |
246 | 现在我们来创建三个组件: ItemsComponent, ChangeTitleComponent, AddItemComponent。 它们都需要 *data* 属性。 AddItem 方法将从主要 Vue 实例转移到 ChangeTitleComponent。 所有必需的 HTML 将从 *index.html* 转移到每个组件。 所以最后,我们的脚本就像下面这样:
247 |
248 | ```js
249 | var data = {
250 | items: [{text: 'Bananas', checked: true},
251 | {text: 'Apples', checked: false}
252 | ],
253 | title: 'My Shopping List',
254 | newItem: ''
255 | };
256 | // Declaring components
257 |
258 | var ItemsComponents = Vue.extend({
259 | data: function () {
260 | return data;
261 | },
262 | template: '
'
271 |
272 | });
273 |
274 | var ChangeTitleComponent = Vue.extend({
275 | data: function () {
276 | return data;
277 | },
278 | template: '
'
279 | });
280 |
281 | var AddItemComponent = Vue.extend({
282 | data: function () {
283 | return data;
284 | },
285 | methods: {
286 | addItem: function () {
287 | var text;
288 | text = this.newItem.trim();
289 | if(text) {
290 | this.newItem.push({
291 | text: text,
292 | checked: false
293 | });
294 | this.newItem = "";
295 | }
296 | }
297 | },
298 | template:
299 | '
' +
300 | '' +
301 | '' +
302 | ' ' +
303 | '' +
304 | '
'
305 | });
306 |
307 | // 注册 components
308 |
309 | Vue.component('items-component', ItemsComponents);
310 | Vue.component('change-title-component', ChangeTitleComponent);
311 | Vue.component('add-item-component', AddItemComponent);
312 |
313 | // 实例化 Vue
314 |
315 | new Vue({
316 | el: '#app',
317 | data: data
318 | });
319 | ```
320 |
321 | 我们需要怎样在视图中使用这些组件呢? 我们只需用组件名替代相应的标记标签。 看起来像这样:
322 |
323 | 
324 |
325 | 组件化的购物清单
326 |
327 | 第一个高亮区域我们将以 *
* 标签来替换, 第二个拿 ** 标签替换, 第三个拿 ** 标签替换。 因此最终是这个样子的:
328 |
329 | ```html
330 |
331 |
{{ title }}
332 |
333 |
334 |
339 |
340 | ```
341 |
342 | 我们将在后续章节继续深入组件, 学习用更棒的方式来组织它们。
343 |
344 | ### Vue.js 指令
345 | 在前面的章节, 你已经学习了用指令来增强方程式的行为。
346 |
347 | 你已经学习了很多指令来绑定数据到视图(*v-model, v-if, v-show...* )。 在这些指令外, Vue.js 还允许你创建自己的自定义指令。 自定义指令机制允许你自定义 DOM 与 data 映射间的行为。
348 |
349 | 当注册一个自定义指令时, 你可以提供三个函数: *bind, update, unbind*。 在 *bind* 函数内, 你可以向元素附加一个事件监听器, 监听任何你需要的东东。 在 *update* 函数内, 它接收新值和旧值作为参数, 你可以在数据变化时自定义行为。 *unbind* 方法解绑所有需要解除的操作。
350 |
351 | ### Tip
352 | 在 Vue 2.0 中, 指令管的事大大减少了 -- 现在它只用于底层 DOM 操作。 Vue 也更改了先前在自定义指令上的指南 ( https://github.com/vuejs/vue/issues/2873) 。
353 |
354 | 因此呢, 全新版本的自定义指令应该是这个样子地:
355 |
356 | ```js
357 | Vue.directive('my-directive', {
358 | bind: function() {
359 | //在绑定元素上执行一些预备工作
360 | },
361 | update: function (newValue, oldValue) {
362 | //基于更新值的一些操作
363 | },
364 | unbind: function () {
365 | //执行一些解绑操作
366 | }
367 | })
368 | ```
369 |
370 | 在精简版本中, 万一你想在数据变化时搞些动作, 可以只使用 *update* 方法, 它可以直接以第二个参数的形式传入指令方程:
371 |
372 | ```
373 | Vue.directive('my-directive', function (el, binding) {
374 | //对绑定值操作
375 | })
376 | ```
377 |
378 | 理论很棒, 但是不来点实例就没意思了。 所以呢, 我们来看一个简单的例子, 当一个数字改变时, 计算它的平方。
379 |
380 | ```js
381 | Vue.directive('square', function (el, binding) {
382 | el.innerHTML = Math.pow(binding.value, 2);
383 | })
384 | ```
385 |
386 | 在你的模板中这样用哦,加上 *v-* 前缀:
387 |
388 | ```html
389 |
390 | ```
391 |
392 | 以 *item* 的值来实例化 Vue 。 你可以看到在 *div* 中的元素会立即更新。 完整代码在这里 https://jsfiddle.net/chudaol/we07oxbd/ 。
393 |
394 | ### Vue.js 中的插件
395 |
396 | 作为 Vue 的核心功能, 它提供对数据绑定的声明及组件编译。 主要通过一系列功能插件来增强。 有这么几类插件:
397 |
398 | * 增加全局的属性或者方法(vue-element)
399 | * 例如增加全局能力的插件(vue-touch)
400 | * 在 Vue 属性上增加 Vue实例
401 | * 提供一些扩展功能或 API (vue-router)
402 |
403 | 插件必须通过一个可以增强或改进的全局的 Vue 对象来提供一个实例方法。 为了更好地使用插件, Vue 使用了 *use* 方法来接收插件实例( Vue.use(SomePlugin))。
404 |
405 | ### Tip
406 | 你可以写自己的插件来使 Vue 实例拥有自定义的行为。
407 |
408 | 我们这就以前面的自定义指令来创建一个简化版的插件吧。 创建一个叫 *VueMathPlugin.js* 的文件,然后这样写哦:
409 |
410 | ```js
411 | export default {
412 | install: function (Vue) {
413 | Vue.directive('square', function (el, binding) {
414 | el.innerHTML = Math.pow(binding.value, 2);
415 | });
416 | Vue.directive('sqrt', function (el, binding) {
417 | el.innerHTML = Math.sqrt(binding.value);
418 | });
419 | }
420 | };
421 | ```
422 | 现在我们创建一个 *script.js* 文件。 加点代码。 在这个脚本中, 我们将导入 Vue 实例和 VueMathPlugin , 使用 *use* 方法来引用插件。
423 |
424 | ```js
425 | import Vue form 'vue/dist/vue.js';
426 | import VueMathPlugin from './VueMathPlugin.js'
427 |
428 | Vue.use(VueMathPlugin);
429 |
430 | new Vue({
431 | el: '#app',
432 | data: {item: 49}
433 | });
434 | ```
435 |
436 | 现在创建一个 *index.html* 文件来引入 *main.js* 文件(当然我们需要 Browserify 和 Babelify)。 在这个文件中,我们在 input 上增加一个 *v-model* 指令, 用于输入值。 创建两个 span 来使用 *v-square* 和 *v-sqrt* 指令:
437 |
438 | ```html
439 |
440 |
441 |
442 |
443 |
Square:
444 |
Root:
445 |
446 |
447 |
448 | ```
449 |
450 | 创建一个 *package.json* 文件来引入我们需要的依赖。
451 |
452 | ```json
453 | {
454 | "name":"vue-custom-plugin",
455 | "scripts": {
456 | "build" : "browserify script.js -o main.js -t
457 | [babelify --presets [ es2015 ]]"
458 | },
459 | "version": "0.0.1",
460 | "devDependencies": {
461 | "babel-preset-es2015": "^6.9.0",
462 | "babelify": "^7.3.0",
463 | "browserify": "^13.0.1",
464 | "vue": "^2.0.3"
465 | }
466 | }
467 | ```
468 | 现在安装依赖, 构建项目
469 |
470 |
471 | **npm install**
472 | **npm run build**
473 |
474 |
475 | 在浏览器中打开 *index.html*。 尝试改变输入框中的值。 观察效果。
476 |
477 | 
478 |
479 | 数据被立刻响应在自定义插件中的指令上
480 |
481 | ### 练习
482 | 用三角函数(sine, cosine, tangent)来增强 MathPlugin 。
483 | 当然你也可以使用 *Annexes*。
484 |
485 | ## 方程式状态和 Vuex
486 | 当方程式达到一定体积时, 有必要来管理全局的的状态了。 受到 Flux( https://facebook.github.io/flux/) 的启发,我们有了 Vuex 来管理共享 Vue 组件中的状态。
487 |
488 | ### Tip
489 | 别以为这很难理解哦(●'◡'●)。 实际上呢,就是些数据。 每个组件拥有它自己的数据, "方程式状态" 指的就是可以在组件中共享的数据!
490 |
491 | 
492 |
493 | Vuex 是如何管理状态更新的
494 |
495 | 就像其它插件, 你需要通知 Vue 来 use 它。
496 |
497 | ```js
498 | import Vuex from 'vuex';
499 | import Vue from 'vue';
500 |
501 | Vue.use(Vuex);
502 |
503 | var store = new Vuex.Store({
504 | state: { <...>},
505 | mutations: {<...>}
506 | });
507 | ```
508 |
509 | 然后初始化组件, 声明实例化 store
510 |
511 | ```js
512 | new Vue({
513 | components: components,
514 | store: store
515 | });
516 | ```
517 |
518 | 现在呢, 主程序和它所有的组件已经知道了 store , 并可以访问它了, 也可以在方程式的生命周期里制动。 我们将在后面的章节仔细研究它。
519 |
520 | ## vue-cli
521 |
522 | 是的, 没错 Vue 拥有自己的命令行工具。 它可以帮助我们初始化任何我们想配置的 Vue 方程式。 你可以使用 Webpack 模板, Browserify 模板, 或者只创建一个简单的 **HTML** 文件。
523 |
524 | *npm* 安装:
525 |
526 | **npm install -g vue-cli**
527 |
528 | 各种初始化方程式的方法:
529 |
530 | ```
531 | vue init webpack
532 | vue init webpack-simple
533 | vue init browserify
534 | vue init browserify-simple
535 | vue init simple
536 | ```
537 |
538 | 为了查看区别, 我们通过运行 *vue init* 来创建一个简单的模板和 Webpack 模板, 看看生成的结构有什么区别。
539 |
540 | 
541 |
542 | vue init simple 和 vue init webpack 的输出
543 |
544 | 下面是方程式结构的区别:
545 |
546 | 
547 |
548 | vue init simple 和 vue init webpack 生成的文件结构差别
549 |
550 | *index.html* 文件只是一个包含 Vue.js 的简单配置文件, 所以如果你只是想搞个快速原型的话, 就用这个吧。
551 |
552 | 如果你想搞个可以测试及热加载的复杂单页应用的话, 使用 Webpack 或者 Browserify 配置。
553 |
554 | ## IDEs 的 Vue 插件
555 | 这里有许多 Vue 语法高亮插件:
556 |
557 | 
558 |
559 | ## 安装, 使用, 调试 Vue.js 方程式
560 |
561 | 在这部分我们要分析所有安装 Vue.js 的方法, 我们会创建一个在后续章节中继续开发的应用的骨架。 我们也会学习调试测试方程式的几种方法。
562 |
563 | ### 安装 Vue.js
564 | 这里有一堆方法去安装 Vue.js ,下载脚本后引入 HTML 文件中的 script 标签中, 使用 bower , npm 或者 Vue 的 命令行工具都可以启动整个方程式。
565 |
566 | 我们选一种自己最喜欢的就行, 我们开始吧。
567 |
568 | ### 独立安装
569 |
570 | 下载 *vue.js*。 这里有多个版本, 简化版和开发版。 开发版在这 ( https://vuejs.org/js/vue.js) 。 精简版在这 (https://vuejs.org/js/vue.min.js ) 。
571 |
572 | ### Tip
573 | 如果你是在开发环境中, 请用非压缩的版本。 你会爱上这些在控制台打出的小提示和警告的。
574 |
575 | 在 script 标签内引入 vue.js :
576 |
577 | ```html
578 |
579 | ```
580 |
581 | Vue 已经被注册为全局变量了, 你可以直接使用它:
582 |
583 | ```html
584 |
585 |
{{ message }}
586 |
587 |
588 |
597 | ```
598 |
599 | ### CDN
600 | Vue.js 可从下面 CDN 获取
601 | * jsdeliver: https://cdn.jsdelivr.net/vue/2.0.3/vue.js
602 | * cdnjs: https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.3/vue.js
603 | * unpkg: https://unpkg.com/vue@2.0.3/dist/vue.js (推荐)
604 |
605 | 在你的 *script* 中加入路径即可使用
606 |
607 | ```html
608 |
610 | ```
611 |
612 | ### Tip
613 |
614 | CDN 版本有可能不与最新版同步。
615 |
616 | 我们的例子有可能与独立安装的版本不一致, 我们使用了 CDN。
617 |
618 | ### Bower
619 |
620 | 如果你已经使用了 Bower 来管理方程式, 也不想用别的工具, 这里也可以使用 Bower 下载。
621 |
622 | ```
623 | # 最新版
624 | bower install vue
625 | ```
626 |
627 | 我们的事例就像前两个一样, 只不过已经换成在 bower 文件中啦(●'◡'●)。
628 |
629 | ```html
630 |
631 | ```
632 |
633 | ### CSP-compliant
634 |
635 | 内容安全协议是一项标准协议, 所有的方程式应该遵守从而避免安全攻击。 如果你是为浏览器开发方程式, 你一定对这条协议很熟悉。
636 |
637 | 对那些要求兼容 CSP 的脚本环境, 这里有个特殊版本的 Vue.js https://github.com/vuejs/vue/tree/csp/dist 。
638 |
639 | 我们来示范一下在 Chrome 方程式中的关于 Vue.js 的 CSP-compliant!
640 |
641 | 首先为我们的事例创建一个文件夹。 在 Chrome 方程式中最重要的是 *manifest.json* 文件, 它用于描述你的方程式。 创建并写入下面信息:
642 |
643 | ```json
644 | {
645 | "manifest_version": 2,
646 | "name": "Learning Vue.js",
647 | "version": "1.0",
648 | "minimum_chrome_version": "23",
649 | "icons": {
650 | "16": "icon_16.png",
651 | "128": "icon_128.png"
652 | },
653 | "app": {
654 | "background": {"scripts": ["main.js"]
655 | }
656 | }
657 | }
658 | ```
659 |
660 | 下一步来创建我们的 *main.js* 文件, 它作为 Chrome 方程式的入口。 脚本应该监听方程式的启动并打开指定大小的新窗口。 我们指定窗口大小为 500 x 300 , 在 *index.html* 打开。
661 |
662 | ```js
663 | chrome.app.runtime.onLaunched.addListener(function () {
664 | // 窗口居中
665 | var screenWidth = screen.availWidth;
666 | var screenHeight = screen.availHeight;
667 | var width = 500;
668 | vai height = 300;
669 |
670 | chrome.app.window.create("index.html", {
671 | id: "learningVueID",
672 | outerBounds: {
673 | widthL width,
674 | height: height,
675 | left: Math.round((screenWidth - width) / 2),
676 | top: Math.round((screenHeight - height) / 2)
677 | }
678 | });
679 | });
680 | ```
681 |
682 | 现在,Chrome-specific 方程式的神奇魔法已经结束, 我们需要创建我们的 *index.html* 文件, 就像前面一样写入相同的代码。
683 |
684 | ```html
685 |
686 |
687 |
688 | Vue.js - CSP-compliant
689 |
690 |
691 |
692 |
{{ message }}
693 |
694 |
695 |
696 |
697 |
698 | ```
699 | 下载好 CSP-compliant 版本的 Vue.js 并把它添加到 assets 文件夹。
700 |
701 | 好了, 我们一起来创建 *app.js* 文件吧。
702 |
703 | ```js
704 | var data = {
705 | message: "Learning Vue.js"
706 | };
707 | new Vue({
708 | el: "#app",
709 | data: data
710 | });
711 | ```
712 |
713 | 把它放到 assets 文件夹中。
714 |
715 | 别忘了创建两个 16 像素和 128 像素的 icon 哦。
716 |
717 | 你的代码应该和我的会很像:
718 |
719 | 
720 |
721 | 使用 vue.js 为 Chrome 方程式提供的代码结构
722 |
723 | 当然最重要的事是来测试一下哦!
724 |
725 | 1. 在你的 Chrome浏览器中打开 chrome://extensions/url
726 | 2. 切换到开发者模式
727 | 3. 点击安装扩展, 找到我们刚创建的文件夹
728 | 4. 你的方程式就会出现在列表中了! 点击启动。
729 |
730 | 
731 |
732 | 用 vue.js 制作的简单 Chrome 方程式
733 |
734 | 恭喜! 你已经制作了一个 Chrome 方程式。
735 |
736 | ### npm
737 | npm 安装方式用于大规模应用。 启动 *npm install* 就像下面这样:
738 |
739 | ```
740 | #最新稳定版
741 | npm install vue
742 | # 最新稳定 的CSP-compliant 版本
743 | npm install vue@csp
744 | ```
745 |
746 | 然后引用它:
747 |
748 | ```js
749 | var Vue = require("vue");
750 | ```
751 |
752 | 如果你喜欢 ES2015 ,可以这样引入
753 |
754 | ```js
755 | import Vue from "vue"
756 | ```
757 |
758 | 我们的 **HTML** 文件看起来是这样的:
759 |
760 | ```html
761 |
762 |
763 |
764 | Vue.js - NPM Installation
765 |
766 |
767 |
768 |
{{ message }}
769 |
770 |
771 |
772 |
773 | ```
774 |
775 | 现在创建 *script.js* 它应该就像独立版或 CDN 安装的那样, 只是在 require 这里不一样。
776 |
777 | ```js
778 | var Vue = require('vue/dist/vue.js');
779 | var data = {
780 | message: 'Learning Vue.js'
781 | };
782 |
783 | new Vue({
784 | el: "#app",
785 | data: data
786 | });
787 | ```
788 |
789 | 我们需要安装 Vue 和 Browserify 来把我们的 *script.js* 编译到 *main.js* 中:
790 |
791 | ```
792 | npm install vue --save-dev
793 | npm install browserify --save-dev
794 | ```
795 |
796 | 在 *package.json* 文件中, 加点脚本来启动 Browserify 。我们的 *package.json* 就像下面这样:
797 |
798 | ```json
799 | {
800 | "name": "learningVue",
801 | "scripts": {
802 | "build": "browserify script.js -o main.js"
803 | },
804 | "version": "0.0.1",
805 | "devDependencies": {
806 | "browserify": "^13.0.1",
807 | "vue": "^2.0.3"
808 | }
809 | }
810 | ```
811 |
812 | 现在运行一下命令:
813 |
814 | **npm run build**
815 |
816 |
817 | 打开 *index.html*
818 |
819 | 我有个朋友在这时说: 啥? 这么多步骤, 安装, 命令行, 说明....结果就是这样的? 手动再见。
820 |
821 | 如果你也这么想, 当然你以一种复杂的方法在做一件简单的事, 但是当你的方程式有更大规模的时候, 你会发现用上这些工具会把那些复杂的事会变得更加简单, 哈哈哈, 该休息下了!
822 |
823 | ### vue-cli
824 | 正如我们前面提及的, Vue 提供了自己的命令行工具, 它允许我们以我们想要的工作流来启动单页方程式。 它也提供了热重载及测试开发环境。 安装完 *vue-cli* 后, 运行 *init <模板> <项目名>* 就行了。
825 |
826 | ```
827 | # 安装 vue-cli
828 | $ npm install -g vue-cli
829 | # 创建项目
830 | $ vue init webpack learn-vue
831 | # 安装运行
832 | $ cd learn-vue
833 | $ npm install
834 | $ npm run dev
835 | ```
836 |
837 | 现在打开 *loaclhost:8080* 。 打开源文件, 你可以看到 *app.vue* 文件, 你还记得我们说组件就像构建我们方程式的砖块一样吗? 记得我们在 *main.js* 中创建注册组件, 我提示你说我们将学习更加优雅地构建组件吗? 哈哈, 你现在就会知道如何以一种更棒的方法来构建组件了!
838 |
839 | 找到 *import Hello from './components/Hello'* 这行。 这正好说明了组件如何在另外的组件中被使用。看看上面的模板文件, 它包含 标签。 在 **HTML** 文件中 *hello* 组件就是这样呈现的。 看看这个组件; 它在 *src/components* 文件夹中。 如你所见, 这确实和我们之前做的很像。 我们来修改一下:
840 |
841 | ```html
842 |
851 | ```
852 |
853 | 在 *App.vue* 组件里移除除了 *hello* 标签外的模板:
854 |
855 | ```html
856 |
857 |
858 |
859 |
860 |
861 | ```
862 |
863 | 现在返回方程式, 你将看到以下图片:
864 |
865 | 
866 |
867 | 用 vue-cli 启动 Vue 方程式
868 |
869 | ### Tip
870 | 除了 Webpack 模板外,你还可以选择下面几种配置
871 |
872 | * webpack-simple: 精简版, Webpack + vue-loader ,适合快速原型
873 | * browserify: 完整版, Browserify + Vueify + hot-reload, linting, init-testing
874 | * browserify-simple: 精简版, Browserify + Vueify , 适合快速原型
875 | * simple: 最简单的版本
876 |
877 | ### 开发构建
878 |
879 | 我亲爱的读者, 你已经知道了如何安装使用 Vue.js ,也明白了它的运行机制, 你一定跃跃欲试了吧!
880 |
881 | 俺明白。 你需要从 GitHub 下载开发版的 Vue.js。
882 |
883 | 我们这就来构建一个事例。 创建像 *dev-build* 之类的文件夹, 把所有 npm 文件拷贝进去。
884 |
885 | ```
886 | cd /node_modules
887 | rm -rf vue
888 | git clone https://github.com/vuejs/vue.git
889 | cd vue
890 | npm install
891 | npm run build
892 | ```
893 |
894 | 现在构建我们的方程式
895 |
896 | ```
897 | cd
898 | npm run build
899 | ```
900 |
901 | 打开 *index.html* ; 我们看到了 *Learing Vue.js* 的标题。
902 |
903 | 我们再来修改一点 vue.js 的源码! 找到 *node_modules/vue/src/compiler/parser* 文件夹, 打开 *text-parser.js* 文件, 找到下面这行:
904 |
905 | ```js
906 | const defaultTagRE = /\{\{((?:.|\n)+?)\}\}/g
907 | ```
908 |
909 | 实际上这个正则定义了 **HTML** 模板中默认的分隔符。 分隔符里面的东东被认为是 Vue 数据 或 JavaScript 代码。 我们来修改一下! 我们用 *% %* 来替代 *{}* :
910 |
911 | ```js
912 | const defaultTagRE = /\%\%((?:.|\n)+?)\%\%/g
913 | ```
914 |
915 | 现在重新构建 Vue 源码, 刷新浏览器, 瞧瞧我们发现了什么?
916 |
917 | 
918 |
919 | 在改变 Vue 源码后, 大括号不能正常执行了!
920 |
921 | 那些在大括号内的信息已经不能被 Vue 识别了, 事实上, 它被当成了 **HTML** 的一部分。
922 |
923 | 现在我们打开 *index.html* , 用 *%%* 替代 大括号 :
924 |
925 | ```html
926 |
927 |
%% message %%
928 |
929 | ```
930 |
931 | 重新构建,刷新浏览器, 现在怎么样了? 又好了! 哈哈, 我敢肯定你现在有一大堆想法来定制 Vue.js ,还等什么, 马上出发吧!
932 |
933 | ## 调试你的 Vue 方程式
934 |
935 | 你可以像调试其它网络方程式一样调试 Vue。 使用你自己的开发者工具 (firebug), breakpoints, debugger statements.... 如果你想仔细研究 Chrome 的调试工具可以看看这个文档 https://developer.chrome.com/devtools。
936 |
937 | 我们也提供 Vue.js 的开发者工具, 它很容易调试 Vue 方程式。 你可以从 Chrome 商店里下载安装 https://chrome.google.com/webstore/detail/vuejsdevtools/nhdogjmejiglipccpnnnanhbledajbpd 。
938 |
939 | 扫兴的是, 它不能调试本地打开的文件, 搭建一些简单的服务器就行拉(例如 https://www.npmjs.com/package/http-server )。
940 |
941 | 然后安装, 打开,我们的购物清单方程式, 打开开发者工具, 你可以看到 Vue 选项卡已经出现啦:
942 |
943 | 
944 |
945 | Vue 开发者工具
946 |
947 | 在这里, 我们只有一个组件 -- Root 。 你可以想象, 当我们有一堆组件时, 它们将会出现在 Vue 开发工具的调试盘上。 点击 Root 组件并检查。 你可以看到所有绑定到这个组件上的数据。 如果你想改变一些, 例如, 增加一个列表项, 切换复选框, 改变标题....所有的改变都将被传播到 Vue 开发者工具上。 你可以在右手边看到变化。 我们这就来试试, 增加一条列表项。
948 |
949 | 
950 |
951 | 模型内的变化将迅速同步到 Vue 开发者工具中。
952 |
953 | ## 搭建我们方程式
954 |
955 | 你还记得在第一章我们开始的方程式吗? 购物清单和番茄钟。 在这个部分, 我们将使用 *vue-cli* 来搭建我们方程式, 让它包含可重用的组件, 可测试, 易于部署。
956 | 一旦我们启动了方程式, 我们便会一直使用它到本书最后。 好吧, 我们开始吧!
957 |
958 | ### 生成购物清单脚手架
959 | 我们将使用 *vue-cli* 生成 Webpack 配置的购物清单脚手架。
960 |
961 | ### Tip
962 |
963 | 记得先安装 *vue-cli* : **npm install -g vue-cli**
964 |
965 | 如果你已经安装了 *vue-cli*, 打开你要创建方程式的文件夹, 输入:
966 |
967 | ```
968 | vue init webpack shopping-list
969 | ```
970 |
971 | 所有的问题都回答 yes ! 然后你的方程式就启动了:
972 |
973 | 
974 |
975 | 用 vue-cli 启动购物清单方程式
976 |
977 | 切换到购物清单文件夹, 运行 *npm install, npm run dev*。 在你的浏览器中打开 *loaclhost:8080* , 你将看到 **Hello World** 页面。
978 |
979 |
980 | 
981 |
982 | 新创建的 Hello World 视图
983 |
984 | 我们来清空启动代码, 从而添加上我们自己的代码。 进入 *App.vue* 文件,删除所有东东, 只留下下面的结构:
985 |
986 | * template
987 | * script
988 | * style
989 |
990 | 在最后我们的 *App.vue* 文件应该像这样:
991 |
992 | ```html
993 |
994 |
995 |
996 |
997 |
998 |
1000 |
1001 |
1003 | ```
1004 |
1005 | 打开文件看看效果, 嗯,很好。 其实你啥事都没做, 一个空白页。
1006 |
1007 | 我们往 template 标签内加点东西, 查看页面, 它自动更新啦。 这是因为 *vue-hot-reload* 插件发现了你在 Vue 组件中改动, 它自动重构建了项目, 而且重新刷新了浏览器页面。 试试在 script 标签内写点东西, 比如一个未定义的变量:
1008 |
1009 | ```html
1010 |
1013 | ```
1014 | 页面不会刷新, 在你的命令行里看看, 它显示了一个错误:
1015 |
1016 | 
1017 |
1018 |
1019 | 每次文件变化, lint 也会被启动。
1020 |
1021 | 这是由于 ESLint 插件的原因, 所以会在代码变化时执行 lint 。
1022 |
1023 | 有了它, 我们可以保证代码的质量。
1024 |
1025 | 说到质量, 我们应该准备让我们的方程式运行单元测试。
1026 |
1027 | 幸好, Webpack 版的 *vue-cli* 已经为我们安排好了。 运行 *npm run unit* 就可以运行单元测试了, 运行 *npm e2e* 运行端对端测试。 因为端对端测试和方程式用了相同的端口, 它俩不能同时启动。 所以, 如果你想在开发时跑测试, 你应该在 *config/index.js* 里改变端口, 或者暂停方程式。 在测试后, 我们发现测试失败了。 这是因为它检查了那些我们移除的具体元素。 打开 *test/e2e/specs* 文件夹, 清除所有我们不需要的断言。 看起来应该是这样的:
1028 |
1029 | ```js
1030 | module.exports = {'default e2e tests': function (browser) {
1031 | browser
1032 | .url('http://localhost:8080')
1033 | .waitForElementVisible('#app', 5000)
1034 | .end()
1035 | }
1036 | }
1037 | ```
1038 |
1039 | 返回测试。 现在能通过了。 从现在开始, 我们需要在我们的方程式里增加一些代码, 然后再写些单元测试和端对端测试。
1040 |
1041 | ### 启动你的番茄钟
1042 |
1043 | 运行 *vue init webpack pomodoro* 重复必要的步骤, 开始运行。
1044 |
1045 | ## 练习
1046 | 将番茄钟打造成一个 Chrome 方程式! 你只需要使用 CSP-compliant 版本的 Vue.js 并添加 *manifest.json* 文件。
1047 |
1048 | ## 总结
1049 | 在本章,我们分析了 Vue.js 幕后的东东。 你已经知道了响应式数据是如何实现的。 你看到了 Vue.js 使用 *Object.defineProperty* 的 *getters 和 setters* 来传播数据变化。 你看到了 Vue.js 的关键概念,像是可重用的组件, 插件系统, 状态管理。 我们已经启动了在后续章节持续开发的方程式。
1050 |
1051 | 在下一章, 我们将深入 Vue 的组件系统。 我们将在方程式中使用组件。
1052 |
--------------------------------------------------------------------------------
/chapter-2/imgs/2-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-2/imgs/2-1.png
--------------------------------------------------------------------------------
/chapter-2/imgs/2-10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-2/imgs/2-10.png
--------------------------------------------------------------------------------
/chapter-2/imgs/2-11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-2/imgs/2-11.png
--------------------------------------------------------------------------------
/chapter-2/imgs/2-12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-2/imgs/2-12.png
--------------------------------------------------------------------------------
/chapter-2/imgs/2-13.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-2/imgs/2-13.png
--------------------------------------------------------------------------------
/chapter-2/imgs/2-14.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-2/imgs/2-14.png
--------------------------------------------------------------------------------
/chapter-2/imgs/2-15.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-2/imgs/2-15.png
--------------------------------------------------------------------------------
/chapter-2/imgs/2-16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-2/imgs/2-16.png
--------------------------------------------------------------------------------
/chapter-2/imgs/2-17.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-2/imgs/2-17.png
--------------------------------------------------------------------------------
/chapter-2/imgs/2-18.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-2/imgs/2-18.png
--------------------------------------------------------------------------------
/chapter-2/imgs/2-19.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-2/imgs/2-19.png
--------------------------------------------------------------------------------
/chapter-2/imgs/2-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-2/imgs/2-2.png
--------------------------------------------------------------------------------
/chapter-2/imgs/2-20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-2/imgs/2-20.png
--------------------------------------------------------------------------------
/chapter-2/imgs/2-21.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-2/imgs/2-21.png
--------------------------------------------------------------------------------
/chapter-2/imgs/2-22.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-2/imgs/2-22.png
--------------------------------------------------------------------------------
/chapter-2/imgs/2-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-2/imgs/2-3.png
--------------------------------------------------------------------------------
/chapter-2/imgs/2-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-2/imgs/2-4.png
--------------------------------------------------------------------------------
/chapter-2/imgs/2-5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-2/imgs/2-5.png
--------------------------------------------------------------------------------
/chapter-2/imgs/2-6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-2/imgs/2-6.png
--------------------------------------------------------------------------------
/chapter-2/imgs/2-7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-2/imgs/2-7.png
--------------------------------------------------------------------------------
/chapter-2/imgs/2-8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-2/imgs/2-8.png
--------------------------------------------------------------------------------
/chapter-2/imgs/2-9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-2/imgs/2-9.png
--------------------------------------------------------------------------------
/chapter-2/imgs/Thumbs.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-2/imgs/Thumbs.db
--------------------------------------------------------------------------------
/chapter-3/Components.md:
--------------------------------------------------------------------------------
1 | # Components – Understanding and Using
2 |
3 | 在前面一章, 你已经知道了 Vue.js 是如何运作的。 你探究了 Vue 的幕后并改动了一点 Vue.js 源码。 你学习了一些 Vue 的关键概念, 也尝试了几种安装 Vue 的方法。 我们已经启动了项目; 我们将继续开发完善。 我们也知道了如何调试测试我们的方程式。
4 |
5 | 在第一章, 我们讨论了组件甚至创建了一些。 在这章, 我们将继续使用组件并实践一些有趣的指令。 换句话说, 我们要:
6 |
7 | * 重返组件话题回顾组件到底是什么
8 | * 为我们的方程式创建组件
9 | * 学习什么是单文件组件
10 | * 学习如何用特性去达到响应式的 CSS 变换
11 |
12 | ## 回顾组件
13 |
14 | 正如你在前面章节所记得, 组件是 Vue 方程式中拥有自己作用域, 数据, 方法的特殊部分。 方程式可以使用组件并重用它。 前一章, 你知道了组件以 *Vue.extend({...})* 方法创建, 以 *Vue.component()* 语法注册。 所以为了创建使用组件我们需要这样做:
15 |
16 | ```js
17 | //创建组件
18 | var HelloComponent = Vue.extend({
19 | template: 'Hello
'
20 | });
21 | //注册组件
22 | Vue.component('hello-component', HelloComponent);
23 | //初始化 Vue 方程式
24 | new Vue({
25 | el: '#app'
26 | });
27 | ```
28 |
29 | 我们在 **HTML** 中这样使用组件:
30 |
31 | ```html
32 |
33 |
34 |
35 | ```
36 |
37 | ### Tip
38 | 初始化及注册可以写成单个 Vue 组件的规范写法:
39 |
40 | ```js
41 | Vue.component('hello-component', {template: ' Hello
'});
42 | ```
43 |
44 | ### 使用组件的好处
45 | 在深入探究组件重写方程式前我们了解一些东东。 在本章, 我们将覆盖以下内容: 在组件中控制 *data* 及 *el* 属性, 组件模板, 作用域和预处理器。
46 |
47 | ### 在 HTML 中声明模板
48 |
49 | 在我们这个例子中, 我们创建了一个用字符串重写的 Vue 组件。 它非常简单, 因为我们需要的都在其中。 现在, 想象一下我们的组件有一个更为复杂的 HTML 结构。 用字符串模板编写复杂组件容易出错, 冗余而且违反最佳实践。
50 |
51 | ### Tip
52 |
53 | 我指的最佳实践是说简洁可维护的代码。 用字符串写的复杂的 **HTML** 完全不具可维护性。
54 |
55 | Vue 可以通过 template 标签在 **HTML** 声明模板。
56 |
57 | 在 **HTML** 中重写我们的事例。
58 |
59 | ```html
60 |
61 | Hello
62 |
63 | ```
64 |
65 | 然后再里面加上我们的组件, 我们需要一个 ID 来指定模板。
66 |
67 | ```js
68 | Vue.component('hello-component', {
69 | template: '#hello'
70 | })
71 | ```
72 | 我们的整个代码看上去像这样:
73 |
74 | ```html
75 |
76 |
77 | Hello
78 |
79 |
80 |
81 |
82 |
83 |
91 |
92 | ```
93 |
94 | 在前面的事例里, 我们只在组件中使用了 *template* 特性。 现在让我们来看看 *data* 和 *el* 特性应该在组件里如何配置。
95 |
96 | ## 在组件里控制 data 和 el 属性
97 | 正如前面提及的, 组件的语法和 Vue 实例的语法很想, 但是它必须扩展自 Vue 而非直接被调用。 有了这个前提, 貌似这样创建组件才对:
98 |
99 | ```
100 | var HelloComponent = Vue.extend({
101 | el: '#hello',
102 | data: {msg: 'hello'}
103 | })
104 | ```
105 |
106 | 但是这样会导致作用域泄漏, 每一个 *HelloComponent* 实例都会共享相同的 *data* 和 *el*。 这不是我们想要的。 这就是 Vue 为什么会明确地要求我们以函数的形式来声明这些属性。
107 |
108 | ```js
109 | var HelloComponent = Vue.component('hello-component', {
110 | el: function () {
111 | return '#hello';
112 | },
113 | data: function () {
114 | return {
115 | msg: 'Hello'
116 | }
117 | }
118 | });
119 | ```
120 |
121 | 甚至当你以对象的方式声明 *data* 和 *el* 时, Vue 会有善意的警告。
122 |
123 | 
124 |
125 | Vue 会在你用对象作为数据时发出警告
126 |
127 | ## 组件的作用域
128 |
129 | 正如已经提到的, 所有的组件拥有它们自己的作用域, 而且不会被其他组件访问到。 然而, 全局的方程式作用域可以被所有注册过的组件访问到哦。
130 | 你可以看到组件的作用域是本地的, 而方程式的作用域是全局的。 这是当然的。 但是, 在组件内不能使用父作用域。你不得不明确指出到底哪个组件的父级数据属性可以被访问,通过使用 *prop* 属性,然后再用 *v-bind* 语法把他们绑定到组件实例上。
131 |
132 | 我们可以先声明 HelloComponent 组件并包含数据及 msg 特性:
133 |
134 | ```js
135 | Vue.component('hello-component', {
136 | data: function () {
137 | return {
138 | msg: 'Hello'
139 | }
140 | }
141 | });
142 | ```
143 |
144 | 创建 Vue 实例并包含一些数据:
145 |
146 | ```js
147 | new Vue({
148 | el: '#app',
149 | data: {
150 | user: 'hero'
151 | }
152 | });
153 | ```
154 |
155 | 在我们的 **HTML** 里, 创建模板并以 ID 的形式应用到组件上:
156 |
157 | ```html
158 | //模板声明
159 |
160 | {{msg}} {{user}}
161 |
162 |
163 | //在组件中使用模板
164 | Vue.component('hello-component', {
165 | template: '#hello',
166 | data: function () {
167 | return {
168 | msg: 'Hello'
169 | }
170 | }
171 | });
172 | ```
173 | 为了在页面中看到组件, 我们应该在 **HTML** 中调用它:
174 |
175 | ```html
176 |
177 |
178 |
179 | ```
180 |
181 | 如果你在浏览器中打开页面, 你只能看到 **Hello**; *user* 数据属性未被绑定到组件中:
182 |
183 | 
184 |
185 | 父级的数据属性未被绑定到 Vue 组件中。
186 |
187 | 为了绑定父级的数据, 我们不得不做以下两件事:
188 |
189 | * 在 props 特性中指明这个属性
190 | * 把它绑定到 hello-component 调用
191 |
192 | ```html
193 | //在组件里调用父级数据特性
194 | Vue.component('hello-component', {
195 | template: '#hello',
196 | data: function () {
197 | return {
198 | msg: 'Hello'
199 | }
200 | },
201 | props: ['user']
202 | });
203 |
204 | //向组件绑定 user 数据属性
205 |
206 |
207 |
208 | ```
209 |
210 | 刷新页面, 你将看到如下消息:
211 |
212 | 
213 |
214 | 纠正向父级数据属性的绑定后, 一切按期执行了。
215 |
216 | ### Tip
217 | 实际上, *v-bind:user* 语法可以有如下的简写:
218 |
219 | ```
220 |
221 | ```
222 |
223 | ## 组件嵌套组件
224 |
225 | 组件的完美之处在于它们可以在其它组件里面重用这就像乐高中的积木一样! 我们来创建另一个组件; 一个叫 **greetings** 由两个二级组件(form asking 和 hello component )组成的组件。
226 |
227 | 我们先来声明模板:
228 |
229 | ```html
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 | //saying hello 模板
238 |
239 | {{msg}} {{user}}
240 |
241 | ```
242 |
243 | 现在,我们在这些模板的基础上来注册两个 Vue 组件:
244 |
245 | ```js
246 | //注册 form 组件
247 | Vue.component('form-component', {
248 | template: '#form',
249 | props: ['user']
250 | });
251 | //注册 hello 组件
252 | Vue.component('hello-component', {
253 | template: '#hello',
254 | data: function () {
255 | return {
256 | msg: 'Hello'
257 | }
258 | },
259 | props: ['user']
260 | });
261 | ```
262 |
263 | 最后, 我们将创建我们的 greetings 模板, 它使用了 *form 和 hello* 组件。 别忘了我们已经向组件绑定了 *user* 属性。
264 |
265 | ```
266 |
267 |
268 |
269 |
270 |
271 |
272 | ```
273 |
274 | 此时, 我们可以创建我们的 greetings 组件并在内使用 greetings 模板。
275 |
276 | 我们在这个组件内初始化带 user 名字的 data 函数:
277 |
278 | ```js
279 | //基于 greetings 模板创建 greetings 组件
280 | Vue.component('greetings-component', {
281 | template: '#greetings',
282 | data: function () {
283 | return {
284 | user: 'hero'
285 | }
286 | }
287 | });
288 | ```
289 |
290 | 在我们的中枢方程式中, 调用 greetings 组件:
291 |
292 | ```html
293 |
294 |
295 |
296 | ```
297 |
298 | 别忘了初始化 Vue 实例
299 |
300 | ```js
301 | new Vue({
302 | el: '#app'
303 | })
304 | ```
305 |
306 | 在浏览器中打开页面, 你可以看到如下输出:
307 |
308 | 
309 |
310 | 一个由不同组件构成的页面
311 |
312 | 尝试在 input 内改变 name 值。你会期望它改变因为我们已经绑定了它, 但是奇怪的是, 改变并未发生。 啊哦(⊙_⊙;).___, 就是这样。 默认情况下, 所有的属性遵守单向数据绑定。 这意味着在父级作用域内的变化将通知到所有子极组件反之却会失效。 这可以防止子极组件意外破坏父级状态。 就是这样, 但是, 也可以通过调用 *events* 强迫子极组件与他们的父级通信。 具体查看 Vue 文档 https://vuejs.org/guide/components.html#Custom-Events。
313 |
314 | <咚咚咚, ( •̀ ω •́ )✧ 大家快记重点。 组件间数据传递的方法>
315 |
316 | 在我们的例子中, 我们可以在每次输入变化时向 form input 组件绑定 user 模型, 然后分发 input 事件。 我们通过使用 *v-on:input* 修饰符来完成它, 就如在这里描述的一样 https://vuejs.org/guide/components.html#Form-Input-Components-using-Custom-Events 。
317 |
318 | 因此, 我们必须向 form-component 传入 *v-model="user"* :
319 |
320 | ```html
321 |
322 | ```
323 |
324 | 然后, form-component 应该接收 value 属性并分发 input 事件:
325 |
326 | ```js
327 | Vue.component('form-component', {
328 | template: '#form',
329 | props: ['value'],
330 | methods: {
331 | onInput: function (event) {
332 | this.$emit('input', event.target.value)
333 | }
334 | }
335 | });
336 | ```
337 |
338 | 在 form-component 模板内的输入框应该绑定 *v-on:input* 和用 *v-on:input* 修饰符的 onInput 方法:
339 |
340 | ```html
341 |
342 | ```
343 |
344 | ### Tip
345 |
346 | 事实上, 在先前的 Vue 2.0 中,这种在组件和父级间双向的同步是可以通过 **sync** 修饰符来交流属性的:
347 |
348 | ```html
349 |
350 | ```
351 |
352 | 刷新页面。 你现在就可以改变输入框内的值了, 它会迅速地传递给父级作用域, 从而也可通知给其它子组件。
353 |
354 | 
355 |
356 | 通过 .sync 修饰符可以在父级和子组件间形成双向绑定。
357 |
358 | 你可以在这里发现这个事例 https://jsfiddle.net/chudaol/1mzzo8yn/。
359 |
360 | ### Tip
361 |
362 | 在 Vue 2.0 版本前, 这里还有其它数据绑定修饰符, *.once* 。用这个修饰符, 数据将只绑定一次, 任何其它的变化不再影响到组件的状态。 比较下面几种方式:
363 |
364 | ```html
365 |
366 |
367 |
368 |
369 |
370 | ```
371 |
372 | ## 用一个简单组件来重写购物清单
373 |
374 | 既然我们已经深入了解了组件, 我们就来用组件重写购物清单吧。
375 |
376 | ### Tip
377 | 对于需要重写的方程式, 我们需要基于这个版本 https://jsfiddle.net/chudaol/vxfkxjzk/3/。
378 |
379 | 当我们开始讨论组件的时候, 我们就已经这样做过了。 不过在那时我们使用了模板字符串来设置组件。 现在我们用刚刚学习的组件配置来重写。 我们再来看看组件的界面和标识。
380 |
381 | 
382 |
383 | 我们的购物清单有四个组件
384 |
385 | 因此, 我建议应该包含这个四个组件:
386 | * AddItemComponent: 增加新的列表项
387 | * ItemComponent: 渲染后的购物列表
388 | * ItemsComponent: 渲染并操作列表
389 | * ChangeTitleComponent: 改变标题
390 |
391 | ## 为组件们定义模板
392 | 为我们的组件提供模板, 假设组件已经被定义注册。
393 |
394 | ### 注意
395 |
396 | **CamelCase VS kebab-case** 你可能已经注意到我们在声明组件名字时使用了驼峰式 *( var HelloComponent = Vue.extend({...}))* ,我们以短横线隔开式来命名它们: *Vue.component('hello-component', {...})* 。 我们这么做是因为 **HTML** 不区别大小写的特性。 因此呢, 我们的组件将会是这个样子地:
397 |
398 | ```
399 | add-item-component
400 |
401 | item-component
402 |
403 | items-component
404 |
405 | change-title-component
406 | ```
407 |
408 | 可以在这里看一下我们以前的例子 ( https://jsfiddle.net/chudaol/vxfkxjzk/3/ )。
409 |
410 | 我们来用模板和组件名重写它。 在这部分, 我们将只关心呈现层, 对于数据绑定的控制留到后面。 我们只复制粘贴方程式的部分 **HTML** 然后在我们的组件中重用。 我们的模板看起来像是下面这样:
411 |
412 | ```html
413 |
414 |
415 |
416 |
419 |
420 |
422 |
423 |
424 |
425 |
426 |
427 |
428 |
429 |
430 |
433 |
434 |
435 |
436 |
437 |
438 |
439 |
443 |
444 |
445 |
446 |
447 |
448 | Change the title of your shopping list here
449 |
450 |
451 |
452 | ```
453 |
454 | 我们的中枢组件将包含一些组件。
455 |
456 | ```html
457 |
458 |
{{ title }}
459 |
460 |
461 |
465 |
466 | ```
467 |
468 | 如你所见, 模板的主要部分都是复制粘贴了相应的 **HTML** 代码。
469 |
470 | 但是呢, 这里有很多重要的不同点。 在 list item 模板中, 做了轻微的改动。 你已经在前面学习了 *v-for* 指令。 在前面的例子中, 我们把它用在 li 这样的元素上。 现在你将看到我们同样把它应用在 Vue 自定义组件上。
471 |
472 | 你同样可能观察到了在标题模板上的小变化。 现在它有了一个绑定到它身上的值, 也使用了 *v-on:input* 修饰符进行分发 *onInput* 方法。 正如你在前面所学习的, 子组件不能在没有事件系统的情况下直接影响父级数据。
473 |
474 | ## 定义注册所有组件
475 |
476 | 先看一眼我们先前做的购物清单方程式: https://jsfiddle.net/chudaol/c8LjyenL/ 。 我们来加点创建组件的代码。 我们将使用模板的 ID 来定义组件的模板特性。 同时, 别忘了从父级传入的 *props* 特性。 因此, 我们的代码如下:
477 |
478 | ```js
479 | //add item component
480 | Vue.component('add-item-component', {
481 | template: '#add-item-template',
482 | data: function () {
483 | return {
484 | newItem: ''
485 | }
486 | }
487 | });
488 |
489 | //item component
490 | Vue.component('item-component', {
491 | template: '#item-template',
492 | props: ['item']
493 | });
494 |
495 | //items component
496 | Vue.component('items-component', {
497 | template: '#items-template',
498 | props: ['items']
499 | });
500 |
501 | //change title component
502 | Vue.component('change-title-component', {
503 | template: '#change-title-template',
504 | props: ['value'],
505 | methods: {
506 | onInput: function (event) {
507 | this.$emit('input', event.target.value)
508 | }
509 | }
510 | });
511 | ```
512 |
513 | 如你所见, 在每个组件的 *props* 特性中, 我们都传入了不同的数据特性。 我们同样在 *add-item-template* 组件里移入了 *newItem* 特性。 在 *change-title-template* 组件里增加了 *onInput* 方法用来分发输入事件, 所以用户的操作才会影响到父级组件。
514 |
515 | 在浏览器里打开 **HTML** 文件。 界面竟和以前的一模一样(⊙o⊙)?! 我们完成的代码可以在这里查看 https://jsfiddle.net/chudaol/xkhum2ck/1/ 。
516 |
517 | ## 练习
518 |
519 | 尽管我们的方程式看起来没什么变化, 它的功能却不在了。 不仅无法增加列表项, 而且会在控制台输出错误信息。
520 |
521 | 请使用事件系统为我们的组件添加功能。
522 |
523 | 在附录里的练习答案参考里有一个解决方案。
524 |
525 | ## 单文件组件
526 |
527 | 我们知道以前的最佳实践是分离 **HTML 、 CSS、 JavaScript**。 一些例如 React 的现代化的框架慢慢越过了这个规则。 当今在一个单文件里写结构, 样式, 逻辑代码已经很普遍了。 事实上, 对于一些小型组件, 我们完全可以转换成这种架构。 Vue 同样允许你在同一个文件里定义一切东东。 这种类型的组件被认为是单文件组件。
528 |
529 | ### 注意
530 |
531 | 单文件组件以 *.vue* 结束。 想这种类型的方程式可以使用 *webpack vue* 来配置。 生成这种类型的方程式, 最简单的方法就是使用 *vue-cli* (https://github.com/vuejs-templates/webpack) 。
532 |
533 | 一个 Vue 组件可以由三部分组成:
534 |
535 | * script
536 | * template
537 | * style
538 |
539 | 每个部分就如你所想的那样。 在 template 标签内放入 **HTML** 模板, 在 script 标签内放入 JavaScript 代码, 在 style 标签内放入 CSS 样式。
540 |
541 | 你还记得我们的 *hello-component* 组件吗? 从这里回顾一下 https://jsfiddle.net/chudaol/mf82ts9a/2/ 。
542 |
543 | 通过使用 *vue-cli 的 webpack-simple* 命令来生成脚手架。
544 |
545 | ```
546 | npm install -g vue-cli vue init webpack-simple
547 | ```
548 |
549 | 以 Vue 组件来重写它, 创建我们的 *HelloComponent.vue* 文件, 增加如下代码:
550 |
551 | ```html
552 |
553 | {{ msg }}
554 |
555 |
556 |
565 | ```
566 |
567 | 注意我们不需要为我们的组件增加特殊的模板标记。
568 | 作为一个单文件组件, 它已经隐式地说明了模板已经只作用这个文件了。 你可能注意到这里有些 ES6 的语法。 当然, 也别忘了数据特性应该是个函数而非对象。
569 |
570 | 在我们的中枢脚本中, 我们需要创建 Vue app 来通知脚本使用 *HelloComponent* 组件:
571 |
572 | ```js
573 | import Vue from 'vue'
574 | import HelloComponent from './HelloComponent.vue'
575 |
576 | new Vue({
577 | el: '#app',
578 | components: { HelloComponent }
579 | });
580 | ```
581 |
582 | 我们在 *index.html* 中的标记将不会改变。 它依然需要调用 *hello-component* :
583 |
584 | ```html
585 |
586 |
587 |
588 |
589 |
590 |
591 | ```
592 |
593 | 我们现在只需要安装 *npm* 依赖了(如果还没有的话), 构建方程式。
594 |
595 | ```
596 | npm install
597 | npm run dev
598 | ```
599 |
600 | 搞定, 你的浏览器将自动打开 *localhost:8080* 页面。
601 |
602 | 你可以在[chapter3/hello/](https://github.com/PacktPublishing/Learning-Vuejs-2/tree/master/chapter3/hello) 查看代码。
603 |
604 | 你也可以在 webpackbin 中修改, 测试。 查看 *hello* 组件 http://www.webpackbin.com/N1LbBIsLb。
605 |
606 | ### Tip
607 | Webpackbin 是一项可以运行测试以 Webpack 构建的方程式的很棒的服务。 尽管还是测试版依然是一款好工具。 当然也有一些小问题, 例如, 当你下载整个项目时, 它将不会运行。
608 |
609 | ## IDE 插件
610 | Vue 的创造者和贡献者也想着咱们开发者呢, 这里有一堆他们为我们开发的 IDE 插件。 你可以这里找到 https://github.com/vuejs/awesome-vue#syntaxhighlighting 。
611 | 如果喜欢使用 WebStorm IDE, 根据下面的说明来安装 Vue 插件。
612 |
613 | 1. 找到 Preferences | Plugins
614 | 2. 点击 Browse repositories
615 | 3. 于搜索框内输入 vue
616 | 4. 选择 Vue.js 点击安装
617 |
618 | 
619 |
620 | 为 webstorm 安装 Vue 插件
621 |
622 | ## 样式和作用域
623 | 很明显, 模板和脚本都只属于其附属的组件。 但是对于样式就不一样了。 试试在我们的 *hello* 组件中为 h1 增加一些 CSS 规则:
624 |
625 | ```html
626 |
631 | ```
632 |
633 | 现在, 刷新页面, **Hello!** 标题的颜色如期变成的红色。 然后在 *index.html* 文件中增加一个 h1 标签。 你可能会对这个标签同样变成了红色而感到吃惊:
634 |
635 | ```html
636 |
637 |
This is a single file component demo
638 |
639 |
640 | ```
641 |
642 | 
643 |
644 | 所有的 h1 标签都有了我们在组件内定义的样式
645 |
646 | 为了使样式只对作用域内的组件起效, 我们需要在 *
654 | ```
655 |
656 | 查看页面你将会发现只有 **Hello!** 文本是红色的, 另外的 *h1* 没有变化。
657 |
658 | ## 热重载
659 |
660 | 你可能注意到了我现在已经不再要你刷新页面去查看效果了。 这是因为在方程式被 *vue-cli* 生成脚手架后, 页面会在每次有变更时自动刷新。 这是由于 *vue-hot-reload* API 会检测方程式文件变化并告知浏览器自动刷新! 很棒吧!
661 |
662 | ## 预处理器
663 |
664 | 如何你知道预处理器, 你可能更喜欢在你的 *.vue* 组件中使用它们。 这多亏了 *vue-loader* 。
665 |
666 | ### 注意
667 |
668 | 你可以在这里发现更多关于预处理器和 *vue-loader* 的教程 http://vue-loader.vuejs.org/en/。
669 |
670 | ## HTML 预处理器
671 | 为了在单文件组件中使用预处理器, 你只需要在 template 标签内添加一个 *lang* 特性! 别忘了安装相应的模块:
672 |
673 | ```
674 | npm install jade --save-dev
675 | ```
676 |
677 | 对于使用 *jade*, 我们需要这样:
678 |
679 | ```html
680 |
681 | h1 {{ msg }}
682 |
683 | ```
684 |
685 | ## CSS 预处理器
686 | 对于 CSS 预处理器也是相同的逻辑。 例如 sass 预处理器:
687 |
688 | ```html
689 |
695 | ```
696 |
697 | ## Tip
698 |
699 | 就如前一个例子说的, 别忘了安装相应的模块加载器:
700 |
701 | ```
702 | npm install sass-loader node-sass --save-dev
703 | ```
704 |
705 | ## JavaScript 预处理器
706 |
707 | 你也可能使用 JavaScript 预处理器。 就像前面两个例子, 只是加上 *lang* 标识。 别忘了安装模块!
708 |
709 | ```html
710 | > npm install coffee-loader coffee-script --save-dev
711 |
712 |
716 | ```
717 |
718 | # 用单文件组件重写我们的购物清单方程式
719 |
720 | 既然我们已经了解了这么多书写组件的方法, 我们这就回到购物清单方程式, 用单文件组件重写它。 我们可以使用 Webpack配置的 *vue-cli*。 事实上, 我们已经在第二章这样做过了。 所以, 开始吧:
721 |
722 | ```
723 | # 没安装的先安装 vue-cli
724 | $ npm install vue-cli -g
725 | # 启动方程式
726 | $ vue init webpack shopping-list
727 | $ cd shopping-list
728 | $ npm install
729 | $ npm run dev
730 | ```
731 |
732 | 确保你的 *index.html* 文件是这样的:
733 |
734 | ```html
735 |
736 |
737 |
738 |
739 | shopping-list
740 |
743 |
744 |
745 |
746 |
747 |
748 | ```
749 |
750 | 你的 *main.js* 应该是这样地:
751 |
752 | ```js
753 | import Vue from 'vue'
754 | import App from './App'
755 | new Vue({
756 | el: 'app',
757 | components: { App }
758 | })
759 | ```
760 |
761 | 我们现在开始创建我们的组件。 当然你得知道我们的购物清单需要哪些必需的组件:
762 |
763 | * AddItemComponent: 增加新的列表项
764 | * ItemComponent: 渲染后的购物列表
765 | * ItemsComponent: 渲染并操作列表
766 | * ChangeTitleComponent: 改变标题
767 |
768 | 我们在 *components* 文件夹内创建它们。 我们先用三个空白的部分填充它们 (template, script, style) , 并在 App.vue 中正确调用。 可以在每个组件内写点东西, 这样我们好区分它们:
769 |
770 | 
771 |
772 | 四个组件内的代码
773 |
774 | 现在呢, 打开 *App.vue* 组件, 这是我们的中枢组件, 它可以集中我们所有的组件。
775 |
776 | 删除 template, script, style 标签。 开始构建我们的方程式。
777 |
778 | 首先, 我们需要导入在 *App.vue* 使用的组件。
779 |
780 | ### Tip
781 |
782 | 别忘了我们可以使用 ES2015 的 *import/export* 语法。
783 |
784 | 在 *
815 | ```
816 |
817 | 我们的模板可以是我们以前构建的那些。 我们现在只关心模型和数据绑定。 首先, 插入增加列表项的组件, 然后是所有列表项组件, 最后是改变标题组件。
818 |
819 | 我们的模板是这样地:
820 |
821 | ```html
822 |
823 |
824 |
{{ title }}
825 |
826 |
827 |
831 |
832 |
833 | ```
834 |
835 | 你还记得我们用驼峰式命名组件的名字吧? 在模板中, 我们应该使用短横线隔开式, 对吧? 好的, 我们看看浏览器里的效果:
836 |
837 | 
838 |
839 | ### Tip
840 | 我们将使用 Bootstrap 的 CSS 样式。 在全局 *index.html* 内引入文件:
841 |
842 | ```html
843 |
844 | ```
845 |
846 | ## AddItemComponent
847 |
848 | 打开 AddItemComponent.vue 。填充 template 。 看起来是这样地:
849 |
850 | ```html
851 |
852 |
853 |
854 |
856 |
857 |
858 |
859 |
860 |
861 |
862 | ```
863 |
864 | 如果你在浏览器查看页面, 你可以看到内容已经变化了。
865 |
866 | ## 配置 ItemComponent 和 ItemsComponent
867 |
868 | 我们打开 ItemComponent.vue 文件。 只是复制粘贴一些 **HTML**:
869 |
870 | ```html
871 | //ItemComponent.vue
872 |
873 |
874 |
875 |
878 |
879 |
880 |
881 | ```
882 |
883 | 同时增加 *scoped* 样式。 这个组件需要特指的 li, span 和 .remove 类。 我们复制粘贴:
884 |
885 | ```html
886 | //ItemComponent.vue
887 |
901 | ```
902 |
903 | 现在打开 *ItemsComponents* 。 正如你所记得的,它是一个 *ItemComponent* 列表。 即使你忘记了, 我想复数的字符名字也会使你记起来吧。 为了可以使用 *ItemComponent* ,你必须在组件内导入注册 *component* 属性。 所以我们先修改脚本:
904 |
905 | ```html
906 | //ItemsComponent.vue
907 |
916 | ```
917 |
918 | 现在你可以在 template 中使用 *item-component* 了! 你还记的怎么遍历元素吗? 你当然记得! 这就是我们现在打开 template 标签的原因:
919 |
920 | ```html
921 | //ItemsComponent.vue
922 |
923 |
924 |
925 |
926 |
927 |
928 | ```
929 |
930 | 如果你现在检查页面, 你会发现构建失败了! 你知道原因吗?
931 |
932 | 你还记得当自组件想改变父级数据必须要声明 "props" 吗? 我们确实忘记在 *ItemsComponent* 和 *ItemComponent* 内声明了。
933 |
934 | 首先, 在 *App.vue* 内, 绑定 *items-component* 调用。
935 |
936 | ```html
937 | //App.vue
938 |
939 | ```
940 |
941 | 然后为 *ItemsComponent* 增添 *props* 特性:
942 |
943 | ```html
944 | //ItemsComponent.vue
945 |
955 | ```
956 |
957 | 回到 *ItemComponent* 组件增加 *props* 属性:
958 |
959 | ```html
960 | //ItemComponent.vue
961 |
966 | ```
967 |
968 | 现在检查页面。 已经恢复正常了。 可以在这里查看完整的代码 [chapter3/shopping-list](https://github.com/PacktPublishing/Learning-Vue.js-2/tree/master/chapter3/shopping-list) 。
969 |
970 | ## 练习
971 | 完成购物清单让它拥有和以前一样的功能。
972 |
973 | 全讲完了, 我确信你能在半小时内完成练习。 参考答案在附录里。
974 |
975 |
976 | # 用单文件组件重写番茄钟方程式
977 |
978 | 我希望你已经可以使用的我们在第一章里开发的番茄钟方程式了。
979 |
980 | 我现在要回顾它并做和前一节里相同的练习 -- 用组件定义并重写。
981 |
982 | 我们来查看一下我们的番茄钟方程式。 我们加入了一张截图, 当重计时时才会更换的截图。
983 |
984 | 
985 |
986 | 重启状态的番茄钟
987 |
988 | 这里有一些易于标识的组件:
989 |
990 | * 控制器组件, ControlsComponent
991 | * 倒计时组件, CountdownComponent
992 | * 当前状态组件, StateTitleComponent
993 | * 猫咪渲染组件, KittensCompnent
994 |
995 | 现在, 别再盯着小猫啦, 我们来用单文件组件实现番茄钟吧! 步骤如下:
996 |
997 | 1. 打开先前的番茄钟文件夹或者基于 Webpack 模板创建一个新的
998 | 2. 在文件夹内运行 *npm install* 和 *npm run dev*
999 | 3. 确保你的 *index.html* 如下:
1000 |
1001 | ```html
1002 |
1003 |
1004 |
1005 |
1006 | pomodoro
1007 |
1008 |
1009 |
1010 |
1011 |
1012 | ```
1013 | 4. 确保你的 *main.js* 像下面这样:
1014 |
1015 | ```js
1016 | import Vue from 'vue'
1017 | import App from './App'
1018 |
1019 | new Vue({
1020 | el: 'app',
1021 | components: { App }
1022 | })
1023 | ```
1024 | 5. 在你的浏览器打开 *localhost:8080*。
1025 | 6. 然后就像前面的例子, 找到 *components* 文件夹并创建所有必要的 *.vue* 组件。
1026 | 7. 找到 *App.vue* , 导入注册好的组件。
1027 | 8. 在每个组件的 template 部分加上一些独特的标识从而让我们可以方便地查找。
1028 |
1029 | 你当然会遇到一些类似的结构, 看起来可能像这些:
1030 |
1031 | 
1032 |
1033 | 用单文件组件实现番茄钟的初始状态。
1034 |
1035 | 现在, 假设我们的组件已经就绪, 而且放置到了相应的位置。
1036 |
1037 | 我只是尽早提醒你一下我们的方程式标记应该是这样的:
1038 |
1039 | ```html
1040 |
1041 |
1042 | Pomodoro
1043 |
1044 | // ControlsComponent
1045 |
1048 |
1051 |
1054 |
1055 |
1056 | // StateTitleComponent
1057 |
{{ title }}
1058 |
1059 | // CountdownComponent
1060 |
1061 |
1062 | {{ min }}:{{ sec }}
1063 |
1064 |
1065 |
1066 | // KittensComponent
1067 |
1068 |
![]()
1069 |
1070 |
1071 | ```
1072 |
1073 | 你可能已经注意到我移除了一些类绑定和行为控制器。 别担心。 记住 Scarlett O'Hara 在 *Gone with the Wind?* 她经常说,
1074 | > "I can't think about that right now. I'll think about that tomorrow."
1075 |
1076 | ( http://goo.gl/InYm8e)。 Scarlett O'Hara 是个睿智的女人。 现在, 我们将只关注我们 *App.vue* 中的 template 标签。 剩下的事等等再做。 我们只是拷贝粘贴 **HTML** 片段来替换我们的标识内容, 用短横线隔开式的组件。 在 *App.vue* 中的模板将是这样:
1077 |
1078 | ```html
1079 | //App.vue
1080 |
1081 |
1082 |
1083 | Pomodoro
1084 |
1085 |
1086 |
1087 |
1088 |
1089 |
1090 |
1091 | ```
1092 |
1093 | 有点眼熟, 啊哈? 在你的浏览器里查看效果。 不是很棒, 但还不错。
1094 |
1095 | 
1096 |
1097 | 启动单文件组件的番茄钟方程式
1098 |
1099 | 我们现在该做什么呢? 复制粘贴相应的标记到组件内的 *template* 部分。 请独立完成, 我就当留个家庭作业吧。 但是如果你想对照一下文件的话, 可以看看这个 [chapter3/pomodoro](https://github.com/PacktPublishing/Learning-Vuejs-2/tree/master/chapter3/pomodoro) 。 所有的数据绑定和有趣的部分将放到下一章! 别合起书, 无论怎样, 请先暂停你的番茄钟。
1100 |
1101 | ## 响应式 CSS 变换绑定
1102 |
1103 | 在切换到讨论大量的不同类型的数据绑定的下一章前,我将介绍一点有趣的绑定。 我亲爱的读者, 我知道你在想这到底是什么意思。 你已经发现 transition 这个词出现了两次啦, 你或许已经猜到我们可以给数据变化绑定 CSS 变换了。
1104 |
1105 | 想象你有一个元素只有在 *show* 特性为真值的时候才显现。 这很简单, 对吗? 用你已经知道的 *v-if* 指令:
1106 |
1107 | ```html
1108 | hello
1109 | ```
1110 |
1111 | 因此, 当 *show* 特性改变时, 会相应地改变。 想象一下在 hideing/showing 上应用 CSS 变换。 在 Vue 中你可以使用特殊的 *transition* 包装在数据变化时提供特殊的变换效果。
1112 |
1113 | ```html
1114 |
1115 | hello
1116 |
1117 | ```
1118 |
1119 | 然后, 只需要在 *fade-enter, fade-leave, fade-enter-active, fade-leave-active* 类上定义 CSS 规则。 可以查看官方的 Vue 文档 https://vuejs.org/v2/guide/transitions.html#Transition-Classes 。
1120 |
1121 | 我们来看看我们的 *kittens* 组件上是怎么应用这些类的。 让我们为它加上 *v-if* 指令:
1122 |
1123 | ```html
1124 |
1125 | <...>
1126 |
1127 | <...>
1128 |
1129 | ```
1130 |
1131 | 当然, 我们应该在 *App.vue* 的 *
1147 | ```
1148 |
1149 | 查看浏览器: 没有变化, 打开开发者工具键入:
1150 |
1151 | ```js
1152 | data.kittens = false
1153 | ```
1154 |
1155 | 你将看到 *kittens* 组件在页面内消失了。 如果你键入下面的代码, 它又出现了:
1156 |
1157 | ```js
1158 | data.kittens = true
1159 | ```
1160 |
1161 | ### Tip
1162 | 我希望你没有忘记在 *index.html* 文件内引入 Boootstrap CSS 样式。 没有它, 你将看不到任何出现消失的变化。
1163 |
1164 | 现在我们将要讨论一些 CSS 变换而非简单的 hiding/showing 。我们为我们的 *kittens* 组件应用 CSS 的 fade 变换。 为组件变化增添一些 *fade* 特性的包装:
1165 |
1166 | ```html
1167 |
1168 | <...>
1169 |
1170 |
1171 |
1172 | <...>
1173 |
1174 | ```
1175 |
1176 | 现在如果我们已经为我们的类定义好了正确的规则, 将会看到一个很棒的 CSS 变换。 开动吧。 在 *
1187 | ```
1188 |
1189 | 再次查看页面。 打开控制台再次键入 data.kittens = false 和 data.kittens = true 。 你可以在每次数据改变时看到一个非常棒的 *fade* 变换。 在下一章, 我们将更多地讨论 Vue.js 变换并在我们的方程式中运用它。
1190 |
1191 | ## 总结
1192 |
1193 | 在本章节, 你已经学习了 Vue 组件如何去运用它们。 你看到了如何以一种经典的方式创建注册组件, 也知道了如何使用单文件组件:
1194 |
1195 | * 以驼峰式创建变量, 为了在模板内使用组件, 你必须应用相应的横断符隔开式形式, 例如, MyBeautifulComponent -> my-beautiful-component
1196 | * 在组件内的 *data* 和 *el* 特性必须是函数而非对象: {data : function () {}}
1197 | * 如果你想为组件增加样式而又不泄漏到全局作用域中, 为它添加 *scoped* 特性:
1198 |
1199 | 我们也用单文件组件重写方程式并尝试了数据绑定的 CSS 变换。
1200 |
1201 | 在下一章, 我们将深入数据绑定, 包括 CSS 和 JavaScript 变换。 我们将使用数据绑定激活我们的方程式。 最重要的是, 我们将看到更多猫咪(●'◡'●)!
1202 |
--------------------------------------------------------------------------------
/chapter-3/imgs/3-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-3/imgs/3-1.png
--------------------------------------------------------------------------------
/chapter-3/imgs/3-10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-3/imgs/3-10.png
--------------------------------------------------------------------------------
/chapter-3/imgs/3-11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-3/imgs/3-11.png
--------------------------------------------------------------------------------
/chapter-3/imgs/3-12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-3/imgs/3-12.png
--------------------------------------------------------------------------------
/chapter-3/imgs/3-13.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-3/imgs/3-13.png
--------------------------------------------------------------------------------
/chapter-3/imgs/3-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-3/imgs/3-2.png
--------------------------------------------------------------------------------
/chapter-3/imgs/3-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-3/imgs/3-3.png
--------------------------------------------------------------------------------
/chapter-3/imgs/3-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-3/imgs/3-4.png
--------------------------------------------------------------------------------
/chapter-3/imgs/3-5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-3/imgs/3-5.png
--------------------------------------------------------------------------------
/chapter-3/imgs/3-6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-3/imgs/3-6.png
--------------------------------------------------------------------------------
/chapter-3/imgs/3-7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-3/imgs/3-7.png
--------------------------------------------------------------------------------
/chapter-3/imgs/3-8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-3/imgs/3-8.png
--------------------------------------------------------------------------------
/chapter-3/imgs/3-9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-3/imgs/3-9.png
--------------------------------------------------------------------------------
/chapter-3/imgs/Thumbs.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-3/imgs/Thumbs.db
--------------------------------------------------------------------------------
/chapter-4/Reactivity.md:
--------------------------------------------------------------------------------
1 | # 响应式 -- 为你的方程式绑定数据
2 |
3 | 在前一章, 你学习了 Vue.js 最重要的一个概念: 组件。 知道了如何创建、 注册、 调用、 重用组件
4 | 。 你理解了单文件组件并在购物清单和番茄钟方程式中应用。
5 |
6 | 在本章, 我们将深入数据绑定。 我们在前面已经讨论过它了, 你应该对它不再陌生了。 在我们的组件中你将使用所有可能的方式来绑定数据。
7 |
8 | 总结起来就是:
9 | * 回顾数据绑定语法
10 | * 在方程式里应用数据绑定
11 | * 在相同的模板里用不同的数据来遍历数组并渲染单个元素
12 | * 在我们的方程式中回顾并使用数据绑定和事件绑定简写方式
13 |
14 | ## 回顾数据绑定
15 | 在第一章我们已经讨论了数据绑定和响应式。 现在你已经了解了数据绑定就是从数据到视图层的传播机制, 反之亦然。 在本章, 我们将在方程式中仔细复习所有的数据绑定方式。
16 |
17 | ## 启动数据
18 |
19 | 想象我们现在有以下 **HTML** 代码片段:
20 |
21 | ```html
22 |
23 | ```
24 |
25 | 同时还有如下 *JavaScript* 对象:
26 |
27 | ```js
28 | var data = {
29 | msg: 'Hello'
30 | };
31 | ```
32 |
33 | 我们怎样在页面渲染所有数据呢? 我们又是怎样在 **HTML** 内获取它们呢? 事实上, 我们已经在前两章就解决了这两个问题了。 当然我们可以再来一次。
34 |
35 | > "Repetitio est mater studiorum"
36 |
37 | 如果你已经对数据插值十分熟悉, 可以先跳过这部分直接到表达式和过滤器部分。
38 |
39 | 所以, 我们应该怎样在 div 标签内填充 *msg* 的值呢? 如果我们使用 jQuery, 老办法将是这样的:
40 |
41 | ```js
42 | $(".hello").text(data.msg);
43 | ```
44 |
45 | 但是, 如果你在方程式运行期间改变 *msg* 的值并期望改变传递给 DOM , 你必须手动更新。 简单改变 *data.msg* 的值, 什么都不会发生。
46 |
47 | ```js
48 | var data = {
49 | msg: 'Hello'
50 | };
51 | $("#hello").text(data.msg);
52 | data.msg = 'Bye';
53 | ```
54 |
55 | 在 *div* 中将毫无疑问出现 *Hello*, 在 JSFiddle 查看效果 https://jsfiddle.net/chudaol/uevnd0e4/。
56 |
57 | 通过 Vue, 最简单的实现方式是使用双大括号, 在我们的事例中, 我们这样写:
58 |
59 | ```html
60 |
%% msg %%
61 | ```
62 |
63 | *div* 的内容尽管被绑定到了 *msg* 数据上, 当 *msg* 变化时, *div* 的内容会自动更新。 在 JSFiddle 查看效果 https://jsfiddle.net/chudaol/xuvqotmq/1/ 。 *data.msg* 也是随着 Vue 实例而变化的。 页面中的值将是一个新值。
64 |
65 | 这依旧是单向的数据绑定。 如果我们在 DOM 中改变值, 数据不会发生改变。 因此, 当我们只需要数据的值出现在 DOM 中时这将是完美的解决方案。
66 |
67 | 现在, 我们非常清楚地知道: 如果我们只想在模板内使用数据对象的值, 我们应该使用双大括号。
68 |
69 | 我们来继续实现我们的番茄钟方程式。 请在[chapter4/pomodoro](https://github.com/PacktPublishing/Learning-Vue.js-2/tree/master/chapter4/pomodoro) 查看当前的解决方案。 如果你运行了 *npm run dev* 后,你的页面应该像这样:
70 |
71 | 
72 |
73 | 实现我们的番茄钟
74 |
75 | 简单看一眼页面, 我们可以看出这里缺少什么。
76 |
77 | 页面中缺少时间, 小猫, 还有番茄钟的状态。 小猫出现的逻辑依靠与番茄钟的状态。 我们来为番茄钟增加状态和时间。
78 |
79 | ## 为番茄钟增加状态标题
80 |
81 | 首先, 我们应该确定这个元素应该属于哪个组件。 看看我们的四个组件。 很显然它属于 *StateTitleComponent* 组件。 查看下面代码, 它实际上已经实现了标题绑定:
82 |
83 | ```html
84 | //StateTitleComponent.vue
85 |
86 | %% title %%
87 |
88 |
89 |
91 |
92 |
94 | ```
95 |
96 | 很好! 在前面的章节里, 我们已经做了大部分工作。 现在我们只需为此增加必须的数据绑定。 在这个组件的 script 标签内, 我们来增加包含 *title* 特性的数据对象。 现在, 我们给它硬编码一个可能的值然后再决定怎么去改变它。 你想怎样改变呢? *Work!* 还是 *Rest!* ? 我知道答案, 我们来添加如下代码:
97 |
98 | ```html
99 | //StateTitleComponent.vue
100 |
109 | ```
110 |
111 | 我们先就这样。 我们将在后面的 methods 和 event 控制中继续完善。
112 |
113 | ### 练习
114 |
115 | 用相同的方法为番茄钟增加事件, 可以先硬编码。
116 |
117 | ## 使用表达式和过滤器
118 |
119 | 在前面的事例中, 我们在双大括号中用了简单的属性键来插值。 事实上, Vue 在花括号里还有更多语法。 我们来看看。
120 |
121 | ## 表达式
122 |
123 | 超出意料, 但 Vue 完全支持在花括号写表达式! 我们来用任意一个番茄钟组件试试这些表达式。 你可以在这里查看一些实践 [chapter4/pomodoro2](https://github.com/PacktPublishing/Learning-Vuejs-2/tree/master/chapter4/pomodoro2) 。
124 |
125 | 随便在一个组件内尝试下, 例如 *StateTitleComponent.vue* 文件。 我们来添加一些 JavaScript 表达式:
126 |
127 | ```html
128 |
129 | ```
130 |
131 | 事实上, 你需要取消下面这些注释:
132 |
133 | ```html
134 | //StateTitleComponent.vue
135 |
136 |
137 |
138 | ```
139 |
140 | 你将会在页面中看到 *25*。 好的。 我们用表达式来替换番茄钟内的一些简单数据绑定。 例如, 在 *CountdownComponent* 组件模板中, 两个指令, *min* 和 *sec* ,就可以被替换成一个表达式, 现在它是这样的:
141 |
142 | ```html
143 | //CountdownComponent.vue
144 |
145 |
146 |
147 | %% min %%:%% sec %%
148 |
149 |
150 |
151 | ```
152 |
153 | 我们可以用下面的代码进行替换:
154 |
155 | ```html
156 | //CountdownComponent.vue
157 |
158 |
159 |
160 | %% min + ':' + sec %%
161 |
162 |
163 |
164 | ```
165 |
166 | 我们还可以在哪里加点表达式呢? 我们再看看 *StateTitleComponent* 。现在, 我们需要硬编码标题。 我们知道, 但是, 标题依赖于番茄钟的状态。 如果是在 *working* 状态, 它将显示 **work!**, 否则应该显示 **Rest!**。 我们来创建一个叫 *isworking* 的特性, 把它分配在 App.vue 组件内, 因为它看起来更像是个全局方程式状态。 我们将重用 *StateTitleComponent* 组件的 *props* 特性。 因此, 打开 App.vue 为 *isworking* 添加一个布尔值, 设为真:
167 |
168 | ```js
169 | //App.vue
170 | <...>
171 | window.data = {
172 | kittens: true,
173 | isworking: true
174 | };
175 | export default {
176 | <...>
177 | data () {
178 | return window.data
179 | }
180 | }
181 | ```
182 |
183 | 我们在 *StateTitleComponent* 中重用属性, 为每个可能的值增加两个字符串属性, 最后, 在模板内增加判断状态来渲染标题的表达式。 因此, 组件的脚本看起来是这样的:
184 |
185 | ```html
186 | //StateTitleComponent.vue
187 |
198 | ```
199 |
200 | 我们用 *isworking* 属性来有条件地渲染标题。 因此, *StateTitleComponent* 的模板是这样的:
201 |
202 | ```jade
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 | ```
212 |
213 | 刷新页面。 奇怪的是, 竟然标题显示的是 *Rest!* , 可我们明明设置 *isworking* 值为真的。 那是因为我们忘记在 *App.vue* 中调用这个属性值! 打开 *App.vue* 组件增加下面的代码:
214 |
215 | ```html
216 |
217 | ```
218 |
219 | 现在, 如果你再查看页面, 当前的标题就显示为 *Work!* 了! 如果你打开控制台并输入 *data.isworking = false*, 你将看到标题改变。
220 |
221 | 如果 *isworking* 特性为假, 标题就是 *Rest!*, 截图在下面:
222 |
223 | 
224 |
225 | 如果 *isworking* 特性为真, 标题就是 *Work!*, 截图在下面:
226 |
227 | 
228 |
229 | ## 过滤器
230 |
231 | 除了在模板中使用表达式, 我们还可以使用过滤器来转换表达式的值。 过滤器就是写函数。 我们创建它们并用管道符 **l** 应用它。 如果你创建了一个让字符大写的过滤器并命名为 uppercase ,为了使用它, 仅仅是在管道符后调用:
232 |
233 | ```html
234 | %%title l lowercase %%
235 | ```
236 |
237 | 你可以链接很多过滤器, 例如, 如果你有了 A、 B 、C 三个过滤器, 你可以这样做 *%% key l A l B l C %%*。 过滤器通过 *Vue.filter* 语法创建。 我们来创建我们的 *lowercase* 过滤器:
238 |
239 | ```js
240 | //main.js
241 | Vue.filter('lowercase', (key) => {
242 | return key.toLowerCase()
243 | })
244 | ```
245 |
246 | 我们在我们的番茄钟的 *App.vue* 组件中应用。 为了使用过滤器, 我们应该网模板插值传入 “Pomodoro” 字符串。 我们应该传入 JavaScript 字符串表达式, 并使用管道符来应用:
247 |
248 | ```html
249 |
250 | <...>
251 |
252 | %% 'Pomodoro' l lowercase %%
253 |
254 |
255 | <...>
256 |
257 | ```
258 |
259 | 查看页面; **Pomodoro** 标题将以小写语法出现。
260 |
261 | 我们来回顾 *CountdownTimer* 组件。 现在, 这里只有一个硬编码的值。 但是但当方程式充满功能, 值应该由方程式计算而来。 值的范围为 0 到 60, 值可以是 **20:40** ,但不应该比 10 小。 例如, 现在只有 1 分钟 5 秒, 它会显示 1: 5, 这样不统一。 我们期望它是这样的 *01: 05* 。 所以, 我们需要一个左边填充的过滤器! 我们来创建一个。
262 |
263 | 在 *main.js* 文件中添加一个左边填充过滤器:
264 |
265 | ```js
266 | //main.js
267 | Vue.filter('leftpad', (value) => {
268 | if (value >= 10) {
269 | return value
270 | }
271 | return '0' + value
272 | })
273 | ```
274 |
275 | 打开 *CountdownComponent* 组件, 来把 *min* 和 *sec* 分割成不同的插值, 并为它们都添加一个过滤器:
276 |
277 | ```html
278 | //CountdownComponent.vue
279 |
280 |
281 |
282 | %% min l leftpad %%:%% sec l leftpad %%
283 |
284 |
285 |
286 | ```
287 |
288 | 用 1 和 5 来替代 *min* 和 *sec*, 分别查看。 数字被 “0” 填充好。
289 |
290 | ## 练习
291 |
292 | 创建两个过滤器, *uppercase* 和 *addspace*, 在番茄钟里应用:
293 |
294 | * *uppercase* 过滤器必须确切地自说明
295 | * *addspace* 过滤器必须为给定的字符串右边增加空间
296 |
297 | 不要忘记 **Pomodoro** 不是关键, 所以在插值括号里它不应被认为是一个字符串! 标题应该像下面截图所示:
298 |
299 | 
300 |
301 | 番茄钟标题在应用过滤器前后的变化在[ chapter4/pomodoro3 ](https://github.com/PacktPublishing/Learning-Vue.js-2/tree/master/chapter4/pomodoro3)。
302 |
303 | ## 回顾并应用指令
304 |
305 | 在前面的部分, 我们知道了方程式中插值并绑定到可见视图层。 尽管过滤器语法非常棒并提供了数据更改的能力, 它依然有很多限制。 例如, 用双大括号实现下面的效果:
306 |
307 | * 在输入框内使用插值属性, 并对相应的对应值应用变化
308 | * 为 data 绑定特殊的元素特性
309 | * 依条件来渲染元素
310 | * 遍历数组并渲染一些数组组件
311 | * 为元素创建事件监听器
312 |
313 | 我们来试试第一个。 例如打开购物清单方程式( [chapter4/shopping-list ](https://github.com/PacktPublishing/Learning-Vue.js-2/tree/master/chapter4/shopping-list) )。 在 *App.vue* 模板中创建输入元素并设置 *%% title %%*:
314 |
315 | ```html
316 |
317 |
318 |
%% title %%
319 |
320 |
321 | <...>
322 |
323 |
324 | ```
325 |
326 | 啊偶! 错误, 满地都是(T_T)。 **在特性中插值已经被移除了**。 在 Vue 2.0 中你可以简单地在特性中使用插值了吗? 是的, 也不是。 在特性中插值将不会出现错误但会使标题中的值清零。 在 Vue 2.0 中, 也就是先行版, 实现这种行为, 我们必须使用指令。
327 |
328 | ### 注意
329 |
330 | **指令** 是拥有 *v-* 前缀的特殊特性元素。 为什么是 *v-* 呢?因为 Vue 指令提供了简单的语法, 它有一套丰富的文本插值。 它们在数据每次变化时强有力地响应可见视图的特殊行为。
331 |
332 | ## 用 v-model 指令启动双向数据绑定
333 |
334 | 双向绑定会将数据变化传递给 DOM 层, 同时在 DOM 中的绑定数据也会传递回数据。 用这样的方法来向 DOM 绑定数据, 我们可以使用 *v-model* 指令。
335 |
336 | 我们确信你依然记得第一章的 *v-model* 指令吧:
337 |
338 | ```html
339 |
340 | ```
341 |
342 | 以这种方式, 标题的值将出现在 input 中, 如果你在 input 中输入一些值, 相应的变化将立即应用给数据并在页面中所有的插值得到相应。
343 |
344 | 仅仅是用 *v-model* 来替换 handlebars 标记。
345 |
346 | 试着在输入框中输入一些值。 你可以看到标题值得变化!
347 |
348 | 记住, 这个指令只能应用给下面的元素:
349 |
350 | * input
351 | * select
352 | * textarea
353 |
354 | 试着删除所有代码。 我们的主要目标是用标题组件改变标题。
355 |
356 | ## 组件间的双向绑定
357 |
358 | 记得在前面的章节里, 组件间双向的绑定不可以简单地通过 *v-model* 来实现。 由于架构原因, Vue 阻止了子类简单改变父级的作用域。
359 |
360 | 我们在子极组件中使用事件系统来改变购物清单标题。
361 |
362 | 我们将在这一章再做一次。 等过几段之后我们将到 *v-on* 指令再谈。
363 |
364 | ## 用 v-bind 指令绑定特性
365 |
366 | *v-bind* 指令允许我们用表达式绑定一个元素的特性或组件属性。 为了给具体的特性应用, 我们使用冒号分隔符:
367 |
368 | ```html
369 | v-bind:attribute
370 | ```
371 |
372 | 例如:
373 |
374 | * v-bind:src = "src"
375 | * v-bind:class = "className"
376 |
377 | 表达式可在 “” 中重写。 data 也可以被使用, 就像在前面的例子一样。 我们在 *KittenCompnent* 中添加小猫图片。 打开我们的番茄钟方程式 [chapter4/pomodoro3](https://github.com/PacktPublishing/Learning-Vuejs-2/tree/master/chapter4/pomodoro3) 。
378 |
379 | 打开 *KittenCompnent* , 添加 *catimgsrc* 为组件数据, 用 *v-bind* 语法把 *src* 绑定到图片模板:
380 |
381 | ```html
382 |
383 |
384 |
![]()
385 |
386 |
387 |
389 |
398 | ```
399 |
400 | 打开页面, 燥起来吧(●'◡'●)!
401 |
402 | 
403 |
404 | 运用数据特性后了 KittenCompnent 组件
405 |
406 | ## 用 v-if 和 v-show 指令来条件渲染
407 |
408 | 如果你关注了前面的部分, 如果我让你条件渲染一些东东, 你可能在插值括号里使用 JavaScript 表达式。
409 |
410 | 但是, 试试在一整个组件内来条件渲染一些元素。 它不像在括号里的表达式那样简单。
411 |
412 | *v-if* 指令允许条件式地渲染整个元素, 它可以是一个依赖一些条件的组件元素。 条件可以是任何表达式并使用数据属性。 例如, 我们可以这样做:
413 |
414 | ```html
415 | hello
416 | ```
417 |
418 | 或者:
419 |
420 | ```html
421 | hello
422 | ```
423 | 或者:
424 |
425 | ```html
426 | Beer Time!
427 | ```
428 |
429 | 或者使用组件的数据:
430 |
431 | ```html
432 |
433 |
434 |
Beer Time!
435 |
436 |
437 |
446 | ```
447 |
448 | *v-show* 特性也是一样的。 唯一的不同点在于 *v-if* 将通过相应的条件来选择是否渲染元素, 而 *v-show* 特性将一直渲染元素, 只是在值为 *false* 时应用 *display: none* 。 我们来看看差别。 在 [ chapter4/beer-time](https://github.com/PacktPublishing/Learning-Vue.js-2/tree/master/chapter4/beer-time) 内打开 *beer - time* 项目, 运行 *npm run dev* 。 打开 *App.vue* 组件用 *true/false* 值来试验, 试着用 *v-show* 来替代 *v-if*。 打开开发者工具, 检查 **element** 选项卡。
449 |
450 | 我们先来看看用 *v-if* 指令的效果。
451 |
452 | 当条件值为真时, 效果如期; 元素被渲染出现在页面内:
453 |
454 | 
455 |
456 | 当条件值为真时, 效果如期。
457 |
458 | 当条件值为假时,元素不被渲染:
459 |
460 | 
461 |
462 | 当条件值为假时,元素不被渲染。
463 |
464 | 注意当条件不被满足时, 相应的元素一点也不会被渲染。
465 |
466 | 我们我看看 *v-show* 指令的效果。当条件为真时, 和使用 *v-if* 一样:
467 |
468 | 
469 |
470 | 使用 *v-show* 来条件渲染。 条件为真。
471 |
472 | 现在我们来看看当条件为假时, *v-show* 将发生什么:
473 |
474 | 
475 |
476 | 使用 *v-show* 来条件渲染。 条件为假。
477 |
478 | 在这个事例中, 条件为真时, 效果一致, 但当条件为假时, 元素也被渲染出来但加上了 *display: none* 属性。
479 |
480 | 你如何去决定使用哪一个指令呢? 在第一次渲染时, 如果条件为假, *v-if* 指令一点也不会渲染元素, 因此减少了初始的计算消耗。 但是, 如果属性在运行期间频繁改变, 渲染元素的成本将会比设置 *display:none* 更昂贵。 因此在频繁改变属性时使用 *v-show* , 其它情况使用 *v-if*。
481 |
482 | 我们回到番茄钟方程式。 *KittensCompnent* 组件将会依赖于发你番茄钟的状态而被条件式地渲染。 所以, 在 [chapter4/pomodoro4](https://github.com/PacktPublishing/Learning-Vue.js-2/tree/master/chapter4/pomodoro4) 中打开你的番茄钟方程式。
483 |
484 | 你觉得应该使用哪个指令? *v-if* 还是 *v-show* ? 我们来分析一下。 站在客观的角度, 这个元素应该在初始化时是可视的吗? 答案是否定的, 因为在初始渲染时, 用户才开始打开番茄钟。 可能使用 *v-if* 更好点。 但是我们来分析另一种情况 -- 频繁地切换状态来使 kittens 组件可见/不可见。 这将发生在番茄钟的倒计时中, 对吗? 在 15-20 分钟后或在 5 分钟后重置计时, 实际上这也不是很频繁, 渲染带来的代价也不高
485 | 。 对于这个例子, 我的意见是, 用那个都行。 我们使用 *v-show* 。 打开 *App.vue* 文件向 *kittens-component* 应用 *v-show* 指令:
486 |
487 | ```html
488 |
489 |
490 | <...>
491 |
492 |
493 |
494 |
495 |
496 | ```
497 | 打开页面, 试着在开发者工具内切换 *data.isworking* 的值, 观察效果。
498 |
499 | ## 用 v-for 指令来遍历数组
500 |
501 | 你可能记得我们用 *v-for* 指令来遍历数组:
502 |
503 | ```html
504 |
505 | item
506 |
507 | ```
508 |
509 | 或者在组件内:
510 |
511 | ```html
512 |
513 | ```
514 |
515 | 对于数组内的每一个项, 这将渲染一个组件并绑定组件中项的值。 当然, 你应该记得 “” 的绑定语法可以随意使用。
516 |
517 | ### Tip
518 |
519 | 别忘了在我们使用的绑定语法应该在组件 *data* 内呈现!
520 |
521 | 顺便看一个例子, 我们的购物清单方程式 [chapter4/shopping-list](https://github.com/PacktPublishing/Learning-Vuejs-2/tree/master/chapter4/shopping-list)。 它已经在 *ItemsComponent* 内使用了 *v-for* 语法:
522 |
523 | ```html
524 |
525 |
528 |
529 | ```
530 |
531 | *ItemComponent*, 也需要声明 *props* :
532 |
533 | ```html
534 |
539 | ```
540 |
541 | 我们来做点有趣的事。 直到现在我们依然只是在处理单个的购物清单。 想象一下你想要为不同的目的建立不同的购物清单。 例如, 你可能要在平常杂货采购中需要一份平常的购物清单。 在节日时你可能需要不同的购物清单。 你可能会在买房子时要一份不同的购物清单。 我们这就用 Vue 中强力的可重用组件来实现我们的购物清单方程式! 我们将使用 *Bootstrap* 的选项卡面板来展示; 更多信息,请查阅 http://getbootstrap.com/javascript/#tabs 。
542 |
543 | 在你的 IDE 内打开[购物清单方程式](https://github.com/PacktPublishing/Learning-Vue.js-2/tree/master/chapter4/shopping-list) 。
544 |
545 | 首先, 我们增加 Bootstrap 的 JavaScript 文件和 jQuery , 因为 bootstrap 依赖它们。 在 *index.html* 内手动添加:
546 |
547 | ```html
548 |
549 | <...>
550 |
551 |
553 | <...>
554 |
555 | ```
556 |
557 | 现在我们为我们将做的工作建立一个步骤总览:
558 | 1. 首先, 我们需要创建组件。 叫它 *ShoppingListComponent* , 把我们的当前 *App.vue* 内容放到这里面。
559 | 2. 我们新的 *ShoppingListComponent* 应该包含 title 和 items 的 *props* 特性。
560 | 3. ItemComponent 应该从 *props* 特性内接收 *items* , 而非把它硬编码。
561 | 4. 在 *App* 组件数据中, 我们先硬编码一个购物清单的数组, 每一项都应有标题, 数组项, ID。
562 | 5. *App.vue* 应该导入 *ShoppingListComponent*, 在模板内, 遍历 *shoppinglists* 数组, 建立一个 *html/jade* 的选项卡结构。
563 |
564 | 好了, 我们开始吧!
565 |
566 | ## 创建 ShoppingListComponent 修改 ItemsComponent
567 |
568 | 在组件文件夹内, 创建一个新的 *ShoppingListComponent.vue* 。复制拷贝 *App.vue* 文件内容到新文件内。 别忘了在 *items-component* 上声明包含 *title* 和 *items* 的 *props* 。 你的代码应该看起来像这样:
569 |
570 | ```html
571 | //ShoppingListComponent.vue
572 |
573 |
574 |
%% title %%
575 |
576 |
577 |
581 |
582 |
583 |
584 |
598 |
604 | ```
605 |
606 | 注意我们移除了 container 的类。 这部分应该在 *App.vue* 内, 因为它定义了方程式全局的 container 样式。 别忘了 *props* 特性, 绑定 *props* 特性到 *items-component*!
607 |
608 | 打开 *ItemsComponent.vue* 并确保有包含 *items* 的 *props* 特性:
609 |
610 | ```html
611 |
618 | ```
619 |
620 | ## 修改 App.vue
621 |
622 | 现在我们打开 *App.vue* 。移除在 script 标签和 template 标签内所有代码。 在 script 标签内导入 ShoppingListComponent, 并在 *component* 里调用它:
623 |
624 | ```html
625 | //App.vue
626 |
635 | ```
636 |
637 | 增加 *data* 特性并创建 *shoppinglists* 数组。 为数组添加任意的数据。 每一个数组对象都有 *id, title, items* 特性。在 *items* 特性内应该包含 *checked* 和 *text* 属性。 例如, 你的数据属性可能看起来是这样的:
638 |
639 | ```html
640 | //App.vue
641 |
668 | ```
669 |
670 | 随便再添加点内容: 添加更多的 lists , items。
671 |
672 | 我们现在来创建通过遍历清单数组而编译到 bootstrap 选项卡面板的结构! 我们先来定义一个基本的结构。 我们添加必要的类和模板结构假装我们现在只有一个元素。 我们用大写标识所有我们需要在 shopping list 数组内重用的项:
673 |
674 | ```html
675 | //App.vue
676 |
677 |
678 |
679 | -
680 | TITLE
681 |
682 |
683 |
684 |
685 | SHOPPING LIST COMPONENT
686 |
687 |
688 |
689 |
690 | ```
691 |
692 | 在这里我们需要遍历 shopping lists 数组内的两个元素 -- 包含着 a 特性的 li 标签和 *tab-pane* 块。 对于第一个元素, 我们必须为每一个购物清单中的 *href* 和 *aria-controls* 特性绑定一个 ID 并插入标题。 在第二个元素内, 我们需要为 *id* 特性绑定 ID, 并在上面绑定 *items* 和 *title* 渲染购物清单列表项! 我们开始吧。 开始添加 *v-for* 指令给每个元素。
693 |
694 | ```html
695 | //App.vue
696 |
697 |
698 |
699 | -
700 | TITLE
701 |
702 |
703 |
704 |
705 | SHOPPING LIST COMPONENT
706 |
707 |
708 |
709 |
710 | ```
711 | 现在用合适的绑定来替换大写标识的部分。 记住对于绑定特性, 我们使用 *v-bind: <相应特性>* = "表达式" 的语法。
712 |
713 | 对于每个锚元素的 *href* 特性, 我们必须为它定义附加到 ID 选择器的表达式: *v-bind:href = "'#' + list.id"*。 *aria-controls* 特性应该被绑定到 ID 值上。 *title* 可以使用简单的双大括号插值标记。
714 |
715 | 对于 *shopping-list-component* 组件, 我们必须绑定 *title* 和 *items* 给对应的 list item。 你还记得我们在 *ShoppingListComponent* 的 *props* 内定义的 *title* 和 *items* 属性吗? 绑定应该像是这样 *v-bind:title = list.title* 和 *v-bind:items= list.items*。
716 |
717 | 有了合适的绑定特性, 模板看起来应该像这样:
718 |
719 | ```html
720 | //App.vue
721 |
722 |
734 |
735 | ```
736 |
737 | 我们基本完工了! 如果你现在打开页面, 你可以发现两个选项卡出现在页面中:
738 |
739 | 
740 |
741 | 修改后出现选项卡标题
742 |
743 | 如果你点击选项卡标题, 响应的面板将会打开。 但这不是我们希望看到的。 我们希望看到第一个面板是被默认打开的。 为了实现这个效果, 我们应该为第一个选项卡增加一个 *active* 类。 但是我们应该怎样在所有由遍历数组产生的面板上操作呢?
744 |
745 | 幸运的是, Vue 不仅仅在 *v-for* 提供了对每一个遍历项的操作, 还提供多每项索引的遍历。 在模板内的表达式上重用索引变量。 因此, 我们恰当地用 “0” 这个索引来添加 *active* 类。 在 *v-for* 循环内使用索引变量很简单:
746 |
747 | ```js
748 | v-for= "(list, index) in shoppinglists"
749 | ```
750 |
751 | 类绑定的语法也和其它绑定一样:
752 |
753 | ```js
754 | v-bind:class = "active"
755 | ```
756 |
757 | 你还记得我们可以在表达式内随便写 JavaScript 语法吗? 在这个例子内, 我们想写一个条件来解析当前值得索引, 如果索引为 “0” , 值的类是 *active*。
758 |
759 | ```js
760 | v-bind:class = "index === 0? 'active' : ''"
761 | ```
762 |
763 | 为 *v-for* 修饰符添加 *index* 变量, 为 li 和 tabpanel 元素绑定 *class* 类, 所以最终的模板代码看起来是这样的:
764 |
765 | ```html
766 |
778 |
779 | ```
780 |
781 | 查看页面。 你应该可以看到一个完好的默认选项卡:
782 |
783 | 
784 |
785 | 修改绑定正确类后的 shopping list 方程式
786 |
787 | 最终的 shopping list 方程式代码在[chapter4/shopping-list2](https://github.com/PacktPublishing/Learning-Vuejs-2/tree/master/chapter4/shopping-list2)。
788 |
789 | ### 使用 v-on 指令来监听事件
790 |
791 | 用 Vue.js 非常容易去为事件添加监听器。 事件监听也需要特殊修饰符的不同指令。 *v-on* 指令后面添加冒号:
792 |
793 | ```js
794 | v-on:click = "MyMethod"
795 | ```
796 |
797 | 是的, 那我们在哪声明方法呢? 你可以不按我说的做, 但大多数组件方法应该在 *methods* 属性内声明! 所以我们来声明一个叫 *myMethod* 的方法, 就像下面这样:
798 |
799 | ```html
800 |
809 | ```
810 |
811 | 所有的 *data* 和 *props* 特性在 methods 内都是可以用 *this* 关键字获取的。
812 |
813 | 我们为 *items* 数组增加一个增加新项数的方法。 我们实际上已经在前面的章节这样做过了, 在我们学习怎样用事件分发系统在父子组件间传递数据时。 我们将在这回顾这一部分。
814 |
815 | 为了在 *AddItemComponent* 内增加新的项给清单列表, 我们需要以下步骤:
816 |
817 | * 确保 *AddItemComponent* 拥有一个叫 *newItem* 的 *data* 属性。
818 | * 在 *AddItemComponent* 内创建 *addItem* 方法, 它用来添加新项并分发添加事件。
819 | * 给 **Add!** 按钮用 *v-on:click* 指令运用事件监听器。 这个事件监听器应该调用默认的 *addItem* 方法。
820 | * 在 *ShoppingListComponent* 内创建一个 *addItem* 方法, 它将接收文本值并把它作为参数添加到 *items* 数组内。
821 | * 为在 *ShoppingListComponent* 内的 *add-item-component* 用自定义 *v-on* 指令来绑定一个 *add* 方法。
822 |
823 | 我们这就开始! 使用[ chapter4/shopping-list2](https://github.com/PacktPublishing/Learning-Vuejs-2/tree/master/chapter4/shopping-list2) 文件夹。
824 |
825 | 打开 *AddItemComponent* 并为 **Add!** 按钮上添加 *v-on* 指令的 *addItem* 方法:
826 |
827 | ```html
828 | //AddItemComponent.vue
829 |
830 |
831 |
832 |
833 |
834 |
835 |
836 |
837 |
838 |
857 | ```
858 | 切换到 *ShoppingListComponent* 绑定 *v-on:add* 指令在 template 内的 *add-item-component* 调用:
859 |
860 | ```html
861 | //ShoppingListComponent.vue
862 |
863 |
864 |
%% title %%
865 |
866 |
867 |
871 |
872 |
873 | ```
874 |
875 | 现在在 *ShoppingListComponent* 内创建 *addItem* 方法。 接收文本值并把它添加到 *this.items* 数组:
876 |
877 | ```html
878 | //ShoppingListComponent.vue
879 |
900 | ```
901 |
902 | 打开页面尝试为列表添加在输入框内的值, 点击前面的按钮。 成功啦!
903 |
904 | 现在, 我想让你把自己从开发者模式切换用户模式。 在输入框内输入新值。 当列表项被添加后有那些明显行为? 你会在键盘上点击回车键吗? 我猜你会的! 但在我们的方程式里, 我们还未实现这个功能。 别担心, 我的朋友, 我们只需要为输入框再添加点事件监听器就行啦就像我们在 **Add!** 按钮上做的那样。
905 |
906 | 听起来很简单, 是不是? 在我们点击回车键后该触发哪些事件呢? 对的, 就是键盘点击事件。 所以呢, 我们只需要在添加一个 *v-on:key* 指令就行啦。 问题是任何一个键被按下都该触发这个事件吗。 这不是我们想要的。 当然我们可以在 *addItem* 方法内增加条件判断并检查 *event.code* 特性, 当它为 13 (代表回车键)时, 我们调用方法。 幸运的是, Vue 给方法提供了一种按键修饰符的机制, 它允许我们只在正确的按键被点击时才触发。 它可以使用点修饰符来触发:
907 |
908 | ```js
909 | v-on:keyup.enter
910 | ```
911 |
912 | 我们来为我们的输入框添加这个机制。 找到 *AddItemComponent* 为输入框添加 *v-on:keyup.enter* 指令:
913 |
914 | ```html
915 |
916 |
917 |
918 |
919 |
920 |
921 |
922 |
923 | ```
924 |
925 | 打开页面尝试点击回车键为 shopping list 添加项。 成功啦!
926 |
927 | 我们对改变标题做同样的事。 唯一的区别在于增加条目, 我们过去使用自定义的 *add* 事件, 在这我们将使用原生的输入事件。 我们已经这么做过啦。 现在只是按下面步骤执行一遍:
928 |
929 | 1. 在 *change-title-component* 中使用 *v-model* 来绑定标题模型。
930 | 2. 在 *ChangeTitleComponent* 中导出在 *props* 内的值。
931 | 3. 在 *ChangeTitleComponent* 创建一个 *onInput* 方法用来分发目标对象的原生输入方法。
932 | 4. 在 *ChangeTitleComponent* 组件的模板内和 *onInput* 修饰符上绑定输入框的值。
933 |
934 | 因此, 在 *ShoppingListComponent* 模板内的 *change-title-component* 调用看起来是这样的:
935 |
936 | ```html
937 | //ShoppingListComponent.vue
938 |
939 |
940 | ChangeTitleComponent will look like the following:
941 | //ChangeTitleComponent.vue
942 |
943 |
944 | Change the title of your shopping list here
945 |
946 |
947 |
948 |
958 | ```
959 |
960 | 这部分最终版的代码在[ chapter4/shopping-list3](https://github.com/PacktPublishing/Learning-Vuejs-2/tree/master/chapter4/shopping-list3) 。
961 |
962 | ## 简写
963 | 当然, 每次去写 *v-bind* 和 *v-on* 指令也不费多少事。 开发者们更倾向于缩减大量的代码, 是的。 Vue.js 允许我们这样做! 记住 *v-bind* 的简写形式是 : 符号, *v-on* 的简写形式是 @ 符号。 这意味对于下面这些代码我们可以这样写:
964 |
965 | ```js
966 | v-bind:items="items"
967 | :items="items"
968 |
969 | v-bind:class=' $index === 0 ? "active" : ""'
970 | :class=' $index===0 ? "active" : ""'
971 |
972 | v-on:keyup.enter="addItem"
973 | @keyup.enter="addItem"
974 | ```
975 |
976 | ## 练习
977 | 用我们刚学过的简写方式重写在购物清单内的所有 *v-bind* 和 *v-on* 指令。
978 |
979 | 可以在这里查看[ chapter4/shopping-list4](https://github.com/PacktPublishing/Learning-Vuejs-2/tree/master/chapter4/shopping-list4) 。
980 |
981 | ## 小猫
982 |
983 | 在本章, 我们没在番茄钟上实现小猫切图效果。 我向你保证在下一章我们将把它当成重点项目。 与此同时, 我希望下面这只小猫会让你感到开心:
984 |
985 | 
986 |
987 | 小猫问 “接下来是啥呢?”
988 |
989 |
990 | ## 总结
991 |
992 | 在本章, 我们扩展了所有绑定数据的方式。 你学习了如何使用双花括号来简单地插入数据。 你也学习了怎样使用 JavaScript 表达式来实现过滤器。 你学习运用了诸如 *v-bind, v-model, v-for, v-if, v-show* 这类指令。
993 |
994 | 我们使用了更丰富有效的数据绑定语法来改善我们的方程式。
995 |
996 | 在下一章, 我们将用简洁的概念谈谈 Vuex, 一个受 Flux 和 Redux 启发而成的状态管理架构。
997 |
998 | 我们将创建为两个方程式提供全局的方程式状态管理的仓库并深入它的工作原理。
999 |
--------------------------------------------------------------------------------
/chapter-4/imgs/4-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-4/imgs/4-1.png
--------------------------------------------------------------------------------
/chapter-4/imgs/4-10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-4/imgs/4-10.png
--------------------------------------------------------------------------------
/chapter-4/imgs/4-11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-4/imgs/4-11.png
--------------------------------------------------------------------------------
/chapter-4/imgs/4-12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-4/imgs/4-12.png
--------------------------------------------------------------------------------
/chapter-4/imgs/4-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-4/imgs/4-2.png
--------------------------------------------------------------------------------
/chapter-4/imgs/4-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-4/imgs/4-3.png
--------------------------------------------------------------------------------
/chapter-4/imgs/4-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-4/imgs/4-4.png
--------------------------------------------------------------------------------
/chapter-4/imgs/4-5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-4/imgs/4-5.png
--------------------------------------------------------------------------------
/chapter-4/imgs/4-6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-4/imgs/4-6.png
--------------------------------------------------------------------------------
/chapter-4/imgs/4-7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-4/imgs/4-7.png
--------------------------------------------------------------------------------
/chapter-4/imgs/4-8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-4/imgs/4-8.png
--------------------------------------------------------------------------------
/chapter-4/imgs/4-9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-4/imgs/4-9.png
--------------------------------------------------------------------------------
/chapter-4/imgs/Thumbs.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-4/imgs/Thumbs.db
--------------------------------------------------------------------------------
/chapter-5/imgs/5-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-5/imgs/5-1.png
--------------------------------------------------------------------------------
/chapter-5/imgs/5-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-5/imgs/5-2.png
--------------------------------------------------------------------------------
/chapter-5/imgs/5-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-5/imgs/5-3.png
--------------------------------------------------------------------------------
/chapter-5/imgs/5-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-5/imgs/5-4.png
--------------------------------------------------------------------------------
/chapter-5/imgs/5-5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-5/imgs/5-5.png
--------------------------------------------------------------------------------
/chapter-5/imgs/5-6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-5/imgs/5-6.png
--------------------------------------------------------------------------------
/chapter-5/imgs/5-7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-5/imgs/5-7.png
--------------------------------------------------------------------------------
/chapter-5/imgs/5-8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-5/imgs/5-8.png
--------------------------------------------------------------------------------
/chapter-5/imgs/5-9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-5/imgs/5-9.png
--------------------------------------------------------------------------------
/chapter-5/imgs/Thumbs.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-5/imgs/Thumbs.db
--------------------------------------------------------------------------------
/chapter-6/Plugins.md:
--------------------------------------------------------------------------------
1 | # Plugins – Building Your House with Your Own Bricks
2 |
3 | 在前面的章节里, 你已经学习了如何用 Vuex 架构来管理全局方程式仓库。 你学习了一堆新概念并应用了它们。 你也学习了如何去创建一个仓库, 如何定义它的状态和 mutations, 如何使用 actions 和 getters。 我们们学会的知识再次激活了我们的购物清单和番茄钟方程式。
4 |
5 | 在本章, 我们将回顾 Vue 插件, 看看它们是如何工作的, 为什么必须要创建插件。 我们将使用一些已有的插件并自己再创建一个。
6 |
7 | 总结起来呢, 就是以下几点:
8 |
9 | * 理解 Vue 插件的本质
10 | * 在购物清单方程式里使用 resource 插件
11 | * 创建一个生成白, 粉, 棕声音的插件并在番茄钟内应用
12 |
13 | ## Vue 插件的本质
14 |
15 | Vue 中的插件完全和用在任何作用域的插件一样: 添加功能, 也基于插件的本质, 去完成系统中一些核心不能完成的功能。 Vue 的插件可以提供不同的功能, 像定义一些 Vue 全局的方法甚至是实例方法, 提供新的指令, 过滤器,变换。
16 |
17 | 为了使用那些已有在插件, 首先你必须安装它:
18 |
19 | **npm install some-plugin --save-dev**
20 |
21 | 然后, 告诉 Vue 你要在方程式内使用它:
22 |
23 | ```js
24 | var Vue = require('vue')
25 | var SomePlugin = require('some-plugin')
26 | ```
27 |
28 | **Vue.use(SomePlugin)**
29 |
30 | 我们也可以创建我们自己的插件。 很简单的。 你的插件必须提供一个 *install* 方法来定义一些或全局或实例的方法, 或者是一些自定义指令:
31 |
32 | ```js
33 | MyPlugin.install = function (Vue, options) {
34 | // 1. add global method or property
35 | Vue.myGlobalMethod = ...
36 | // 2. add a global asset
37 | Vue.directive('my-directive', {})
38 | // 3. add an instance method
39 | Vue.prototype.$myMethod = ...
40 | }
41 | ```
42 |
43 | 然后就可以像使用其它插件一样使用了。 在本章, 我们将使用已有的 *resource* 插件并创建我们自己的插件。
44 |
45 | ## 在我们的购物清单方程式内使用 vue-resource 插件
46 |
47 | 打开我们的[购物清单方程式](https://github.com/PacktPublishing/Learning-Vuejs-2/tree/master/chapter6/shopping-list) 并运行 *npm install npm run dev*。 很棒, 但它依旧使用硬编码来建立购物清单。 如果我们想增删改查一些条目时使用 *resource* 插件会很方便, 这个插件允许我们创建简单的 REST 资源并调用 REST 方法。 在开始前, 我们来总结一下我们将需要做的工作:
48 |
49 | * 首先, 我们需要一个简单的服务器来储存我们的购物清单。 后端提供所有需要的功能。
50 | * 在创建完成服务及所有后端工作后, 我应该安装 *vue-resource* 插件来创建资源和行为来供后端调用。
51 | * 为了保证数据的完整性, 我们应该在每次服务状态更新时执行一系列行为。
52 | * 在方程式开始时, 我们应该从后端取得所有的购物清单数据并分配给我们的仓库。
53 | * 我们应该提供一个机制来创建新的购物清单或删除已有的购物清单
54 |
55 | 听起来不难吧? 开动起来!
56 |
57 | ## 创建一个简单的服务
58 |
59 | 为了足够简单, 我们将使用非常基本易用的 HTTP 服务, 用 JSON 文件来储存数据。 这个服务是 *json-server* 在https://github.com/typicode/json-server 。 把它安装在我们的方程式目录里:
60 |
61 | **cd shopping-list**
62 | **npm install --save-dev json-server**
63 |
64 |
65 | 创建一个叫 *db.json* 的服务器文件夹, 内容如下:
66 |
67 | ```json
68 | //shopping-list/server/db.json
69 |
70 | {
71 | "shoppinglists": [
72 | ]
73 | }
74 | ```
75 |
76 | 这将是我们的数据库。 我们在我们的 *package.json* 内添加一些脚本, 从而更容易地开始我们的服务:
77 |
78 | ```json
79 | "scripts": {
80 | "dev": "node build/dev-server.js ",
81 | "server": "node_modules/json-server/bin/index.js --watch
82 | server/db.json",
83 | <...>
84 | },
85 | ```
86 |
87 | 现在, 启动服务, 按如下运行:
88 |
89 | **cd shopping-list**
90 | **npm run server**
91 |
92 | 在 http://localhost:3000/shoppinglists 打开浏览器。 你将看到一个空数组。 这是因为我们的数据库依然是空的。 我们试着用 curl 来添加一些数据:
93 |
94 | **curl -H "Content-Type:application/json" -d '{"title":"new","items":[]}'**
95 | **http://localhost:3000/shoppinglists**
96 |
97 | 如果你现在刷新页面, 你将看到新添加的值。
98 |
99 | 既然我们有了自己的 REST 服务。 那我们就在 *vue-resource* 的帮助下使用它吧!
100 |
101 |
102 | ## 安装 vue-resource, 创建 resource 和它的方法
103 |
104 | 在深入学习 *vue-reource* 之前, 查看它的文档 https://github.com/vuejs/vue-resource/blob/master/docs/resource.md。 文档提供了基于 URL 来简单创建 resources 的方法。 在 resource 被创建后, 我们可以对他进行 *get, delete, post update*。
105 |
106 | 在项目的文件夹内安装:
107 |
108 | **cd shopping-list**
109 | **npm install vue-resource --save-dev**
110 |
111 | 现在我们来为我们的端口创建 API 。 在 *src* 文件夹内, 创建一个叫 *api* 的二级文件夹。 在里面创建 *index.js* 文件, 导入 *vue-resource* 插件并告诉 *Vue* 来使用它:
112 |
113 | ```js
114 | //api/index.js
115 | import Vue from 'vue'
116 | import VueResource from 'vue-resource'
117 |
118 | Vue.use(VueResource)
119 | ```
120 |
121 | 好的!现在我们来创建 *ShoppingListResource* 并在里面添加一些方法。 用 *vue-resource* 插件创建 resource , 我们只需要在 *Vue* 上调用 *resource* 方法并传入 URL 即可。
122 |
123 | ```
124 | const ShoppingListsResource = Vue.resource('http://localhost:3000/' + 'shoppinglists{/id}')
125 | ```
126 |
127 | *ShoppingListsResource* 常量现在暴露了我们所有需要的方法(CRUD)。 通过简单地暴露 resource 我们就可以完成。 但是我们需要为每个操作导出相应的方法:
128 |
129 | ```js
130 | export default {
131 | fetchShoppingLists: () => {
132 | return ShoppingListsResource.get()
133 | },
134 | addNewShoppingList: (data) => {
135 | return ShoppingListsResource.save(data)
136 | },
137 | updateShoppingList: (data) => {
138 | return ShoppingListsResource.update({ id: data.id }, data)
139 | },
140 | deleteShoppingList: (id) => {
141 | return ShoppingListsResource.remove({ id: id })
142 | }
143 | }
144 | ```
145 |
146 | *api/index.js* 完整的代码在 https://gist.github.com/chudaol/d5176b88ba2c5799c0b7b0dd33ac0426 。
147 |
148 | 就是这样! 我们的 API 已经准备好了接收 Vue 的数据了!
149 |
150 | ## 从获取所有购物清单的数据开始
151 |
152 | 从创建一个获取 *shoppinglists* 状态的行为开始。 在它创建后, 我们在 *App.vue* 上调用。
153 |
154 | 在 *mutations_types.js* 内定义一个常量:
155 |
156 | ```js
157 | //mutation_types.js
158 | export const POPULATE_SHOPPING_LISTS = 'POPULATE_SHOPPING_LISTS'
159 | ```
160 |
161 | 现在创建一个 mutation 。 这个 mutation 将只接收购物清单数组并分配给 *shoppinglists* 状态:
162 |
163 | ```js
164 | //mutations.js
165 | export default {
166 | [types.CHANGE_TITLE] (state, data) {
167 | findById(state, data.id).title = data.title
168 | },
169 | [types.POPULATE_SHOPPING_LISTS] (state, lists) {
170 | state.shoppinglists = lists
171 | }
172 | }
173 | ```
174 |
175 | 好的! 现在我们只需要一个使用 API 方法来分发 mutation 的 action 了。 在 *actions.js* 文件内导入 API并创建相应的 action 方法:
176 |
177 | ```js
178 | import { CHANGE_TITLE, POPULATE_SHOPPING_LISTS } from './mutation_types'
179 | import api from '../api'
180 |
181 | export default {
182 | changeTitle: ({ commit }, data) => {
183 | commit(CHANGE_TITLE, data)
184 | },
185 | populateShoppingLists: ({ commit }) => {
186 | api.fetchShoppingLists().then(response => {
187 | commit(POPULATE_SHOPPING_LISTS, response.data)
188 | })
189 | }
190 | }
191 | ```
192 |
193 | 在前面的代码里, 我们完成了一个简单的任务 -- 我们调用了 *fetchShoppingLists* API 方法, 反过来, 调用了 resource 的 *get* 方法。 这个方法产生一个 *http GET* 请求并当数据返回时完成一个 promise 。
194 |
195 | 数据将被用于分发填充 mutation 。 这个方法将为仓库状态属性 *shoppinglists* 分配数据。 这个属性是响应式的; 你还记得吗?
196 | 这意味着所有依赖 *shoppinglists* 属性的视图将被更新。 我们现在在 *App.vue* 组件内的 mounted 状态上使用 action。 更多关于 mounted 状态的钩子信息 https://vuejs.org/v2/api/#mounted 。
197 |
198 | 打开 *App.vue* 组件, 导入 *mapActions* 对象, 在组件的 *methods* 属性上映射 *populateShoppingLists* action, 在 *mounted* 控制器上调用。 所以我们的 *App.vue* 将会是这样的:
199 |
200 | ```html
201 |
223 | ```
224 |
225 | 如果你现在打开页面, 会发现这里只有一个我们用 *curl* 创建的购物清单, 就像下图一样:
226 |
227 | 
228 |
229 | 这里展示了由简单服务创建的购物清单!
230 |
231 | 试着用 *curl* 来插入更多的数据!
232 |
233 | ## 在变化上更新服务器状态
234 |
235 | 非常棒, 我们现在有了 REST API 服务, 一切正常运行。 试着添加一些购物清单或改变标题。 刷新页面。 哇哦, 列表空了! 没错, 我们有一个 API 方法来更新给定的购物清单, 但是数据没有同步, 所以我们的服务器没有意识到变化。
236 |
237 | 我们来定义一个组件来让服务器接收更改。 下面的三件事将是我们要做的:
238 |
239 | * 列表的标题可以在 *changeTitleComponent* 内改变
240 | * 新的列表项可以在 *AddItemComponent* 内添加
241 | * 购物清单的列表项可以在 *ItemComponent* 内切换
242 |
243 | 我们必须创建一个行为来触发所有的更改。 在这个行为内, 我们应该调用更新 API 的方法。 在 *api/index.js* 内查看 更新方法; 它必须接收整个购物清单对象为参数:
244 |
245 | ```js
246 | //api/index.js
247 | updateShoppingList: (data) => {
248 | return ShoppingListsResource.update({ id: data.id }, data)
249 | }
250 | ```
251 |
252 | 我们来创建一个行为来接收参数为 id ,通过 ID 来取回购物列表, 调用 API 方法。 在这么做之前,在 *getters.js* 内创建一个 *getListById* 方法并在 actions 内导入它:
253 |
254 | ```js
255 | //getters.js
256 | import _ from 'underscore'
257 | export default {
258 | getLists: state => state.shoppinglists,
259 | getListById: (state, id) => {
260 | return _.findWhere(state.shoppinglists, { id: id })
261 | }
262 | }
263 |
264 | //actions.js
265 | import getters from './getters'
266 | ```
267 |
268 | 现在, 我们来调用更新购物清单的行为:
269 |
270 | ```js
271 | //actions.js
272 | <...>
273 |
274 | export default {
275 | <...>
276 | updateList: (store, id) => {
277 | let shoppingList = getters.getListById(store.state, id)
278 | api.updateShoppingList(shoppingList)
279 | }
280 | }
281 | ```
282 |
283 | 事实上, 我们可以在 *mutations.js* 文件内删除 *findById* 方法了, 在 *getters.js* 内重用这个方法:
284 |
285 | ```js
286 | //mutations.js
287 | import * as types from './mutation_types'
288 | import getters from './getters'
289 |
290 | export default {
291 | [types.CHANGE_TITLE] (state, data) {
292 | getters.getListById(state, data.id).title = data.title
293 | },
294 | [types.POPULATE_SHOPPING_LISTS] (state, lists) {
295 | state.shoppinglists = lists
296 | }
297 | }
298 | ```
299 |
300 | 好的, 现在我们已经为 API 定义了调用 *updateList* 方法的行为了。 现在我们只需在组件内的每次变化时调用这个行为就可以了。
301 |
302 | 我们先修改 *AddItemComponent*。 我们必须在这里组件内用 *this.$store.dispatch* 方法分发 *updateList* 行为。 但是这里有个小问题 -- 我们必须把列表的 ID 传给 *updateList* 方法, 而且我们不能在这里组件里有它的引用。 但是呢, 这其实很好解决。 仅仅在组件的 *props* 添加 ID 并绑定给在组件上的调用行为。 我们的 *AddItemComponent* 组件看起来像这样:
303 |
304 | ```html
305 | //AddItemComponent.vue
306 |
326 | ```
327 |
328 | 在 *ShoppingListComponent* 内, 在 *add-item-component* 调用上绑定 ID:
329 |
330 | ```html
331 | //ShoppingListComponent.vue
332 |
333 | <...>
334 |
335 | <...>
336 |
337 | ```
338 |
339 | 现在, 如果你试着添加列表项并刷新页面会发现添加的列表项还在!
340 |
341 | 现在, 我们应该给 *ChangeTitleComponent* 做相同的事。 打开 *ChangeTitleComponent.vue* 文件, 查看代码。 在 *changeTitle* action 上调用:
342 |
343 | ```html
344 | //ChangeTitleComponent.vue
345 |
346 |
347 | Change the title of your shopping list here
348 |
350 |
351 |
352 |
353 |
363 | ```
364 |
365 | 我们当然可以导入 *updateList* action 并在调用 *changeTitle* action 后调用。 但是, 在 action 内部操作会更简单。 你可能记得为了分发仓库的 action , 我们应该在仓库上以 action 的名字为参数调用 *dispatch* 方法。 所以我们在 *changeTitle* action 这么修改:
366 |
367 | ```js
368 | //actions.js
369 | export default {
370 | changeTitle: (store, data) => {
371 | store.commit(CHANGE_TITLE, data)
372 | store.dispatch('updateList', data.id)
373 | },
374 | <...>
375 | }
376 | ```
377 |
378 | 完成了, 打开页面, 修改标题, 刷新页面, 标题应该不变!
379 |
380 | 我们最后需要修改的是列表项的切换属性。 我们来看看 *ItemComponent* 并决定在哪里调用 *updateList* action。
381 |
382 | 我们从为 *props* 特性添加 ID 开始, 就像我们在 *AddItemComponent* 内做的一样:
383 |
384 | ```html
385 | //ItemComponent.vue
386 |
391 | ```
392 |
393 | 我们必须给组件的调用绑定 id 属性, 在 *ItemsComponent* 内完成:
394 |
395 | ```html
396 | //ItemsComponent.vue
397 |
398 |
402 |
403 |
404 |
414 | ```
415 |
416 | 这意味着我们必须为 *item-component* 绑定 id 属性:
417 |
418 | ```html
419 | //ShoppingListComponent.vue
420 |
421 | <...>
422 |
423 | <...>
424 |
425 | ```
426 |
427 | 我们应该在 *ItemComponent* 导入 *mapActions* 对象并在 *methods* 属性上导出 *updateList* 方法:
428 |
429 | ```html
430 | //ItemComponent.vue
431 |
438 | ```
439 |
440 | 很好, 万事俱备; 我们能仅需要在 *ItemComponent* 内找到调用 *updateList* 行为的地方就可以了。
441 |
442 | 这不是一个简单的任务, 因为不像在其它组件内我们有事件控制器来处理变更并调用相应的函数。 在这, 我们只有类和模型绑定给复选框元素。 幸运的是, Vue 提供了一个 *watch* 选项来为我们附加监视器到任意的组件数据上, 而且可以给它们绑定处理器。 在我们的例子中, 我们想监听 *item.checked* 属性并调用行为。 所以, 仅仅在组件上添加一个 *watch* 特性:
443 |
444 | ```html
445 | //ItemComponent.vue
446 |
458 | ```
459 |
460 | 我们完成了! 试着添加清单列表, 切换, 刷新页面, 什么都不变!
461 |
462 | ## 创建一个新的购物清单
463 |
464 | 好的, 我们已经从服务器获取了购物清单; 我们也应用了数据变更。 但是, 如果我们在用户界面内直接创建购物清单那样的话会更棒。 当然, 我们能做到!
465 |
466 | 我们来增加几个行为来调用相应的 API 方法, 如下:
467 |
468 | ```js
469 | //actions.js
470 | export default {
471 | <...>
472 | createShoppingList: ({ commit }, shoppinglist) => {
473 | api.addNewShoppingList(shoppinglist)
474 | }
475 | }
476 | ```
477 |
478 | 现在我们提供了一种可视化操作。 我们在选项卡列表中通过 + 操作来创建额外的选项卡, 我们用点击事件来实现。 我们将在 *App.vue* 组件中实现它。 我们已经导入了 *mapActions* 对象。 我们在 *methods* 属性上创建一个 *createShoppingList* 方法。
479 |
480 | ```html
481 | //App.vue
482 |
505 | ```
506 |
507 | 现在我们的 *App.vue* 组件已经可以访问 *createShoppingList* action, 还能再事件控制器上得到调用。 问题是哪个是数据? *createShoppingList* 方法一直在等待接收一个发往服务器的数据。 我们来创建一个能生成新列表的方法, 在这个方法内调用 action。 但是这个方法该放在哪里呢? 组件里的 *methods* 属性已经被 *mapActions* 辅助函数占用了。 好的, *mapActions* 方法返回方法的映射。 我们可以用本地方法来简单地扩展这个映射:
508 |
509 | ```js
510 | //App.vue
511 | methods: _.extend({},
512 | mapActions(['populateShoppingLists', 'createShoppingList']),
513 | {
514 | addShoppingList () {
515 | let list = {
516 | title: 'New Shopping List',
517 | items: []
518 | }
519 | this.createShoppingList(list)
520 | }
521 | }),
522 | ```
523 |
524 | 现在我们仅仅需要添加一个按钮并绑定 *addShoppingList* 方法给它的点击事件。 你可以在页面的任意地方创建你自己的按钮。 我的按钮代码是这样的:
525 |
526 | ```html
527 | App.vue
528 |
529 |
550 |
551 | ```
552 |
553 | 查看页面; 在最后一个选项卡上你有了一个加号按钮, 它清晰地指引你来创建一个新的列表:
554 |
555 | 
556 |
557 | 现在我们通过加号按钮来添加新的购物列表
558 |
559 | 试着点击按钮, 哇哦, 什么都没发生! 但是如果你查看网络面板, 你可以看到刚刚产生的请求:
560 |
561 | 
562 |
563 | 创建请求已经被成功执行了; 但是在这里却什么都没有发生。
564 |
565 | 实际上, 这已经成功了。 我们已经更新了服务器的信息, 但是客户端并没有意识到这个变化。 如果我们能在创建购物清单后填充清单就更好了。 当然啦, 我们可以。 回到 *actions.js* 并调用 *populateShoppingLists* action :
566 |
567 | ```js
568 | //actions.js
569 | createShoppingList: (store, shoppinglist) => {
570 | api.addNewShoppingList(shoppinglist).then(() => {
571 | store.dispatch('populateShoppingLists')
572 | })
573 | }
574 | ```
575 |
576 | 现在, 如果你点击加号按钮, 你将立即看到刚创建的清单出现在选项卡面板里, 就像下图一样:
577 |
578 | 
579 |
580 | 在填充列表后的新列表
581 |
582 | 你可以点击新列表, 改变标题, 添加列表项, 切换。 当你刷新页面后, 所有数据都将不变。 干得好!
583 |
584 | ## 删除已有的购物清单
585 |
586 | 我们已经能在创建更新我们的购物清单了。 现在我们需要删除它们。 在我们学习完本章的内容后, 你会认为这其实是最简单的一部分了。 我们应该在我们的 API 上添加一个 *deleteShoppingList* 方法, 为每个购物清单添加删除按钮, 并在上面调用 行为。
587 |
588 | 我们从添加这个行为开始。 很简单, 正如我们创建列表一样, 我们将在删除购物清单后调用 *populate* 方法, 我们的行为就像这样:
589 |
590 | ```js
591 | //action.js
592 | deleteShoppingList: (store, id) => {
593 | api.deleteShoppingList(id).then(() => {
594 | store.dispatch('populateShoppingLists')
595 | })
596 | }
597 | ```
598 |
599 | 现在让我们想想在哪加个删除按钮呢。 我更喜欢在选项卡的头部加上它。 这个组件叫 *ShoppingListTitleComponent* 。 打开它并导入 *mapActions* 辅助函数就像下面这样:
600 |
601 | ```html
602 | //ShoppingListTitleComponent.vue
603 |
616 | ```
617 |
618 | 现在我们来添加删除按钮并绑定 *deleteShoppingList* 方法给它的点击事件监听器。 我们将把 ID 传入这个方法中。 我们可以直接在模板内这样做:
619 |
620 | ```html
621 | //ShoppingListTitleComponent.vue
622 |
623 |
624 | {{ title }}
625 |
627 |
628 |
629 | ```
630 |
631 | 我也添加了一点点标题的样式让它看起来更棒:
632 |
633 | ```html
634 |
641 | ```
642 |
643 | 就是这样! 打开页面你在购物列表标题旁边看到一个 X 按钮。 试着点击它你将立即看到变化, 正如下图:
644 |
645 | 
646 |
647 | 有了删除按钮的购物清单列表
648 |
649 | 恭喜你哦! 我们现在已经有了功能完备的方程式, 我们可以创建, 删除购物清单并改变列表项! 干得好! 最终代码在 [ chapter6/shopping-list2](https://github.com/PacktPublishing/Learning-Vuejs-2/tree/master/chapter6/shopping-list2) 。
650 |
651 | ### 练习
652 |
653 | 我们的每个购物清单都挺像的。 你能加点样式来区分它们吗。
654 |
655 | ## 在番茄钟方程式内创建使用插件
656 |
657 | 现在我们已经知道如何使用已有的插件了, 我们为什么不自己制作一个呢? 我们番茄钟方程式已经有了一些动画, 页面也将在番茄钟切换状态时发生变化。 但是如果我们不查看选项卡, 我们分不清它到底是不是在运行。 我们来为我们的番茄钟方程式加点声音!
658 |
659 | 在考虑为一款时间管理方程式加点声音时, 我更想想想对工作有益的声音。 我们每个人都有自己最爱的工作列表播放录。 当然, 这取决于每个人的音乐爱好。 这就是我为什么决定要给我们的方程式添加一些自然的音响的原因。 研究指出不同的声音与高强度的工作很配。 可以在维基 https://en.wikipedia.org/wiki/Sound_masking 了解。 Quora 也有些讨论 http://bit.ly/2cmRVW2 。
660 |
661 | 在这部分, 我们将使用 Web Audio API ( https://developer.mozilla.org/enUS/docs/Web/API/Web_Audio_API ) 来创建 Vue 的插件生成 白, 粉, 棕的声音。 我们将提供一种声音的事例。 我们也提供全局的 Vue 方法来开始暂停这些声音。 然后, 我们将使用这个插件在休息时切换到沉默状态, 工作时切换为声音状态。 听起来有趣又有挑战吧? 我希望如此! 我们开始吧!
662 |
663 | ## 创建一个声音生成器插件
664 |
665 | 我们的插件可以存储到单个 JavaScript 文件内。 它将包含三个方法, 一个用于生成每种声音并提供 *Vue.install* 方法来安装。 我们使用 [ chapter6/pomodoro ](https://github.com/PacktPublishing/Learning-Vue.js-2/tree/master/chapter6/pomodoro ) 文件夹作为起点。 在 *src* 文件夹下创建一个插件二级文件夹。 添加 *VueNoiseGeneratorPlugin.js* 文件。 现在创建下面三个方法:
666 |
667 | * generateWhiteNoise
668 | * generatePinkNoise
669 | * generateBrownNoise
670 |
671 | 我不再造轮子了我将复制粘贴我在网上找到的代码。 当然我对这些代码非常感谢 http://noisehack.com/generate-noise-web-audio-api/ 。 正如所说的, 我们的插件就像下面这样组织:
672 |
673 | ```js
674 | // plugins/VueNoiseGenerator.js
675 | import _ from 'underscore'
676 | // Thanks to this great tutorial:
677 | //http://noisehack.com/generate-noise-web-audio-api/
678 |
679 | var audioContext, bufferSize, noise
680 | audioContext = new (window.AudioContext || window.webkitAudioContext)()
681 |
682 | function generateWhiteNoise () {
683 | var noiseBuffer, output
684 | bufferSize = 2 * audioContext.sampleRate
685 | noiseBuffer = audioContext.createBuffer(1, bufferSize, audioContext.sampleRate)
686 |
687 | output = noiseBuffer.getChannelData(0)
688 |
689 | _.times(bufferSize, i => {
690 | output[i] = Math.random() * 2 - 1
691 | })
692 | noise = audioContext.createBufferSource()
693 | noise.buffer = noiseBuffer
694 | noise.loop = true
695 | noise.start(0)
696 | return noise
697 | }
698 |
699 | function generatePinkNoise () {
700 | bufferSize = 4096
701 | noise = (function () {
702 | var b0, b1, b2, b3, b4, b5, b6, node
703 | b0 = b1 = b2 = b3 = b4 = b5 = b6 = 0.0
704 | node = audioContext.createScriptProcessor(bufferSize, 1, 1)
705 | node.onaudioprocess = function (e) {
706 | var outputoutput = e.outputBuffer.getChannelData(0)
707 | _.times(bufferSize, i => {
708 | var white = Math.random() * 2 - 1
709 | b0 = 0.99886 * b0 + white * 0.0555179
710 | b1 = 0.99332 * b1 + white * 0.0750759
711 | b2 = 0.96900 * b2 + white * 0.1538520
712 | b3 = 0.86650 * b3 + white * 0.3104856
713 | b4 = 0.55000 * b4 + white * 0.5329522
714 | b5 = -0.7616 * b5 - white * 0.0168980
715 | output[i] = b0 + b1 + b2 + b3 + b4 + b5 + b6 + white * 0.5362
716 | output[i] *= 0.11 // (roughly) compensate for gain
717 | b6 = white * 0.115926
718 | })
719 | }
720 | return node
721 | })()
722 | return noise
723 | }
724 |
725 | function generateBrownNoise () {
726 | bufferSize = 4096
727 | noise = (function () {
728 | var lastOut, node
729 | lastOut = 0.0
730 | node = audioContext.createScriptProcessor(bufferSize, 1, 1)
731 | node.onaudioprocess = function (e) {
732 | var output = e.outputBuffer.getChannelData(0)
733 | _.times(bufferSize, i => {
734 | var white = Math.random() * 2 - 1
735 | output[i] = (lastOut + (0.02 * white)) / 1.02
736 | lastOut = output[i]
737 | output[i] *= 3.5
738 | // (roughly) compensate for gain
739 | })
740 | }
741 | return node
742 | })()
743 | return noise
744 | }
745 | ```
746 |
747 | 你可以在 JSFiddle https://jsfiddle.net/chudaol/7tuewm5z/ 听到所有的声音。
748 |
749 | 现在, 我们实现了这三种声音了。 我们必须导出 *install* 方法。 这个方法接收 Vue 实例, 然后我们可以在上面创建指令和方法。 我们创建一个 *noise* 指令。 这个指令可以有三个值, *white, pink, brown* 。 通过相应声音的创建方法来调用。 我们的 *install* 方法如下:
750 |
751 | ```js
752 | // plugins/VueNoiseGeneratorPlugin.js
753 | export default {
754 | install: function (Vue) {
755 | Vue.directive('noise', (value) => {
756 | var noise
757 | switch (value) {
758 | case 'white':
759 | noise = generateWhiteNoise()
760 | break
761 | case 'pink':
762 | noise = generatePinkNoise()
763 | break
764 | case 'brown':
765 | noise = generateBrownNoise()
766 | break
767 | default:
768 | noise = generateWhiteNoise()
769 | }
770 | noise.connect(audioContext.destination)
771 | audioContext.suspend()
772 | })
773 | }
774 | }
775 | ```
776 |
777 | 安装后, 我们实例化 *audioContext* 并挂起它, 因为我们可不想一开始就产生这些声音。 我们需要在一些事件上实例化它们(例如,开始按钮, 暂停按钮之类的)。 我们提供方法来开始, 暂停, 停止我们的 *audioContext* 。 我们将把这三个方法包装成一个叫 *noise* 的全局 Vue 属性。 我们把这些方法命名为 *start, pause, stop*。 在 *start* 方法内, 我们要开始 *audioContext* , 在 *pause* 和 *stop* 方法上挂起它。 所以, 我们的方法看起来就像下面这样:
778 |
779 | ```js
780 | // plugins/VueNoiseGeneratorPlugin.js
781 |
782 | export default {
783 | install: function (Vue) {
784 | Vue.directive('noise', (value) => {
785 | <...>
786 | })
787 | Vue.noise = {
788 | start () {
789 | audioContext.resume()
790 | },
791 | pause () {
792 | audioContext.suspend()
793 | },
794 | stop () {
795 | audioContext.suspend()
796 | }
797 | }
798 | }
799 | }
800 | ```
801 |
802 | 就是这样! 我们的插件完全可用了, 它不是很完美, 当然, 因为我们只有一个 *audioContext* , 它只被一次实例化并只有一种声音, 这意味着我们将不能多次使用 *noise* 指令。 但是呢, 这只是个原型, 你完全可以在后续完善它!
803 |
804 | ## 在番茄钟内使用插件
805 |
806 | 首先, 我们有一个声音生成器插件, 就差使用了! 你已经知道如何来使用它额。 打开 *main.js* 文件, 导入 *VueNoiseGeneratorPlugin* , 并告诉 Vue 来使用它:
807 |
808 | ```js
809 | import VueNoiseGeneratorPlugin from
810 | './plugins/VueNoiseGeneratorPlugin'
811 |
812 | Vue.use(VueNoiseGeneratorPlugin)
813 | ```
814 |
815 | 然后, 我们可以添加 *noise* 指令并在番茄钟内任意地使用 *Vue.noise* 方法。 我们绑定给 *App.vue* 组件内的主模板:
816 |
817 | ```html
818 | //App.vue
819 |
820 |
821 | <...>
822 |
823 |
824 | ```
825 |
826 | 注意我们使用了 *v-noise* 名字的指令, 而非 *noise*。 我们已经在自定义指令时讨论过它了。 在使用指令时, 给它的名字加上 *v-* 前缀。 也请注意, 我们在单引号包围的 *brown* 字符串后又用了双引号。 如果不这样做, Vue 将搜索 *brown* 数据属性, 这就是 Vue 的工作。 我们可以在指令内写任何 JavaScript 语句绑定任务, 我们必须在双引号内传入。 你可以创建操作 *noise* 数据属性并分配给你想要分配的值, 在指令绑定语法内重用。
827 |
828 | 然后, 我们在 *start* mutation 内调用 *Vue.noise.start* 方法:
829 |
830 | ```js
831 | //mutations.js
832 | import Vue from 'vue'
833 |
834 | <...>
835 | export default {
836 | [types.START] (state) {
837 | <...>
838 | if (state.isWorking) {
839 | Vue.noise.start()
840 | }
841 | },
842 | }
843 | ```
844 |
845 | 查看页面并点击 start 按钮。 你将听到 brown 声音。 小心千万别吵醒你的同事也别吓到你的家人。 试着改变声音的值。
846 |
847 | 剩下就是你还没有做的。 我们创建了声音开始但无法停止的机制。 我们在 *pause* 和 *stop* mutations 上调用 *Vue.noise.pause* 和 *Vue.noise.stop* 方法:
848 |
849 | ```js
850 | //mutations.js
851 | export default {
852 | <...>
853 | [types.PAUSE] (state) {
854 | <...>
855 | Vue.noise.pause()
856 | },
857 | [types.STOP] (state) {
858 | <...>
859 | Vue.noise.stop()
860 | }
861 | }
862 | ```
863 |
864 | 查看页面。 点击暂停或停止按钮, 声音被挂起了! 我们还什么都没做呢。 记得我们的目的是在工作时有声音而不是在休息时。 所以, 我们看看再 *mutations.js* 文件内的 *togglePomodoro* 方法并添加一个通过番茄钟当前状态来开始暂停声音的机制:
865 |
866 | ```js
867 | //mutations.js
868 | function togglePomodoro (state, toggle) {
869 | if (_.isBoolean(toggle) === false) {
870 | toggle = !state.isWorking
871 | }
872 |
873 | state.isWorking = toggle
874 | if (state.isWorking) {
875 | Vue.noise.start()
876 | } else {
877 | Vue.noise.pause()
878 | }
879 | state.counter = state.isWorking ? WORKING_TIME : RESTING_TIME
880 | }
881 | ```
882 |
883 | 番茄钟的代码可以在这里查看[chapter6/pomodoro2](https://github.com/PacktPublishing/Learning-Vue.js-2/tree/master/chapter6/pomodoro2) 文件夹。
884 |
885 | ## 创建一个按钮切换声音
886 |
887 | 为声音绑定番茄钟的状态很棒。 当暂停方程式时声音也停止很棒。 但是, 在方程式运行时停止声音也很有用。 想到这些应用场景, 甚至是在方程式运行时拨打 Skype 电话。 在这些情况下, 在这类场景下, 有个声音背景可一点也不好。 我们添加一个按钮来切换声音。 以声明一个 *soundEnabled* 仓库属性并初始为 *true* 为起始点。 为这个属性创建 *getter* 。 所以看起来是这样的:
888 |
889 | ```js
890 | //store.js
891 | <...>
892 | const state = {
893 | <...>
894 | soundEnabled: true
895 | }
896 |
897 | //getters.js
898 |
899 | export default {
900 | <...>
901 | isSoundEnabled: state => state.soundEnabled
902 | }
903 | ```
904 |
905 | 现在我们必须提供一种机制来切换声音。 我们来为它创建 mutation 方法并添加分发这个 mutation 的 action。 以声明 TOGGLE_SOUND 为 mutation 类型开始:
906 |
907 | ```js
908 | //mutation_types.js
909 | <...>
910 | export const TOGGLE_SOUND = 'TOGGLE_SOUND'
911 | ```
912 |
913 | 现在打开 *mutations.js* 添加 mutation 方法切换 *soundEnabled* 仓库属性:
914 |
915 | ```js
916 | //mutations.js
917 | [types.TOGGLE_SOUND] (state) {
918 | state.soundEnabled = !state.soundEnabled
919 | if (state.soundEnabled) {
920 | Vue.noise.start()
921 | } else {
922 | Vue.noise.pause()
923 | }
924 | }
925 | ```
926 |
927 | 现在我们添加分发这个 mutation 的 action:
928 |
929 | ```js
930 | //actions.js
931 | export default {
932 | <...>
933 | toggleSound: ({ commit }) => {
934 | commit(types.TOGGLE_SOUND)
935 | }
936 | }
937 | ```
938 |
939 | 现在, 我们创建了所有需要的按钮! 我们在 *ControlsComponent* 完成它。 给 methods 映射添加一个 *getter* 和 *action* :
940 |
941 | ```html
942 | //ControlsComponent.vue
943 |
951 | ```
952 |
953 | 现在我们能给模板添加一个按钮。 我建议以一个 *glyphicon* 类的标致替代。
954 |
955 | 我们只在方程式开始并未暂停时显示这个标志, 只有番茄钟的状态是 *working* 时显示, 这样我们就不会弄乱切换状态了。 这意味着我们需要 *v-show* 指令:
956 |
957 | ```js
958 | v-show="isStarted && !isPaused && isWorking"
959 | ```
960 |
961 | 注意我们在这里使用 *isWorking* 属性, 它还没被导入呢。 把它加入到方法映射里:
962 |
963 | ```html
964 | //ControlsComponents.vue
965 |
973 | ```
974 |
975 | 在这个元素上我们也使用 *glyphicon-volume-off* 和 *glyphicon-volume-on* 类。 它们将指明声音切换动作状态。 *glyphicon-volume-off* 类将在有声音时, *glyphicon-volume-on* 类将在么声音时。 代码里这样写:
976 |
977 | ```js
978 | :class="{ 'glyphicon-volume-off': isSoundEnabled, 'glyphicon-volume-up': !isSoundEnabled }"
979 | ```
980 |
981 | 还有一点, 我们应该在按钮被点击时调用 *toggleSound* 。 就是说我们应该给这个元素绑定点击事件:
982 |
983 | ```js
984 | @click='toggleSound'
985 | ```
986 |
987 | 所以呢, 整个代码看起来像这样:
988 |
989 | ```html
990 | //ControlsComponent.vue
991 |
992 |
993 | <...>
994 |
996 |
997 |
998 | ```
999 |
1000 | 我们为这个按钮再加一点样式:
1001 |
1002 | ```html
1003 |
1010 | ```
1011 |
1012 |
1013 | 打开页面点击开启番茄钟。 你可以看到右上角的按钮, 你可以点击它来切换声音:
1014 |
1015 | 
1016 |
1017 | 现在我们可以在番茄钟运行时关闭声音了!
1018 |
1019 | 如果你点击这个按钮, 它将变成另一个按钮:
1020 |
1021 | 
1022 |
1023 | 我们可以切换它
1024 |
1025 | 现在想象有下面的场景: 我们启动方程式, 关闭声音, 暂停方程式, 重启方程式。 我们的当前逻辑是声音在每次方程式启动时开启。 我们将处于一种不一样的状态中 -- 方程式已经启动, 声音开始, 但是声音切换键显示要开启声音, 好像不对吧? 但是这很好解决 -- 仅仅添加一个条件来开启 mutation 而不是检查 *isworking* 状态:
1026 |
1027 | ```js
1028 | //mutations.js
1029 | [types.START](state) {
1030 | <...>
1031 | if (state.isWorking && state.soundEnabled) {
1032 | Vue.noise.start()
1033 | }
1034 | },
1035 | ```
1036 |
1037 | 完整版的代码在[chapter6/pomodoro3](https://github.com/PacktPublishing/Learning-Vue.js-2/tree/master/chapter6/pomodoro3) 文件夹。
1038 |
1039 | ## 练习
1040 |
1041 | 如果在番茄钟休息时也能有点音乐就好了, 创建一个在番茄钟休息时播放的音乐的插件。
1042 |
1043 | ## 总结
1044 |
1045 | 我在写最后一点代码时就被这张图片迷住了:
1046 |
1047 | 
1048 |
1049 | 一大群猫咪好像在问我: 这章什么时候结束?
1050 |
1051 | 在本章, 你学习了插件系统如何工作。 我们使用了已有的 *resource* 插件来给我们的购物清单添加服务端行为。 现在我们能创建, 删除, 更新我们的购物清单了。
1052 |
1053 | 我们也创建了我们自己的插件! 我们的插件可以产生声音来帮助我们在工作时集中精神。 我们不仅创建了它, 而且还在我们的番茄钟方程式内应用了它! 现在我们可以在番茄钟工作时更加集中精神了!
1054 |
1055 | 现在我们手边有两个真正的方程式了。 你知道什么才是更好的方程式吗?
1056 |
1057 | 让方程式变得更好的方法是测试我们方程式!
1058 |
1059 | 记住, 我们要开始测试我们的方程式了。 下一章, 我们将深入一些测试技术。 我们将用 Karms 写一些单元测试, 用 Jasmine 为断言库。 我们将用 Nightwatch 写端对端测试。 我喜欢测试方程式也希望你喜欢。 出发!
1060 |
--------------------------------------------------------------------------------
/chapter-6/imgs/6-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-6/imgs/6-1.png
--------------------------------------------------------------------------------
/chapter-6/imgs/6-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-6/imgs/6-2.png
--------------------------------------------------------------------------------
/chapter-6/imgs/6-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-6/imgs/6-3.png
--------------------------------------------------------------------------------
/chapter-6/imgs/6-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-6/imgs/6-4.png
--------------------------------------------------------------------------------
/chapter-6/imgs/6-5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-6/imgs/6-5.png
--------------------------------------------------------------------------------
/chapter-6/imgs/6-6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-6/imgs/6-6.png
--------------------------------------------------------------------------------
/chapter-6/imgs/6-7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-6/imgs/6-7.png
--------------------------------------------------------------------------------
/chapter-6/imgs/6-8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-6/imgs/6-8.png
--------------------------------------------------------------------------------
/chapter-6/imgs/Thumbs.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-6/imgs/Thumbs.db
--------------------------------------------------------------------------------
/chapter-7/imgs/7-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-7/imgs/7-1.png
--------------------------------------------------------------------------------
/chapter-7/imgs/7-10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-7/imgs/7-10.png
--------------------------------------------------------------------------------
/chapter-7/imgs/7-11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-7/imgs/7-11.png
--------------------------------------------------------------------------------
/chapter-7/imgs/7-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-7/imgs/7-2.png
--------------------------------------------------------------------------------
/chapter-7/imgs/7-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-7/imgs/7-3.png
--------------------------------------------------------------------------------
/chapter-7/imgs/7-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-7/imgs/7-4.png
--------------------------------------------------------------------------------
/chapter-7/imgs/7-5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-7/imgs/7-5.png
--------------------------------------------------------------------------------
/chapter-7/imgs/7-6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-7/imgs/7-6.png
--------------------------------------------------------------------------------
/chapter-7/imgs/7-7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-7/imgs/7-7.png
--------------------------------------------------------------------------------
/chapter-7/imgs/7-8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-7/imgs/7-8.png
--------------------------------------------------------------------------------
/chapter-7/imgs/7-9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-7/imgs/7-9.png
--------------------------------------------------------------------------------
/chapter-7/imgs/Thumbs.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-7/imgs/Thumbs.db
--------------------------------------------------------------------------------
/chapter-8/Deploying.md:
--------------------------------------------------------------------------------
1 | # Deploying – Time to Go Live!
2 |
3 | 在前面的章节里, 你已经学习了如何测试 Vue 方程式。 我们用不同的测试技术来测试它们。 一开始, 我们为 Vue 组件和 Vuex-related 模块运行了经典的单元测试, 像是 actions, mutations, getters。 然后我们学习了如何使用 Nightwatch 来运行端对端测试。
4 |
5 | 在本章, 我们将通过把我们的方程式部署到服务器上来让全世界都可以访问到。 我们将保证持续集成开发我们的方程式, 它们将自动被测试部署。
6 |
7 | 主要来讲, 在本章我们要做以下几件事:
8 | * 用 Travis 持续集成
9 | * 用 Heroku 持续部署
10 |
11 |
12 | ## 软件开发
13 |
14 | 在开始部署我们的方程式前, 我们先来定义什么是软件开发:
15 |
16 | > "Software deployment is all of the activities that make a software system available for use." –
17 |
18 | > Wikipedia: https://en.wikipedia.org/wiki/Software_deployment
19 |
20 | 这个定义说, 在我们执行完所有必要的操作后, 我们的软件将提供给公众。 在我们的事例中, 当我们开发 web 方程式时, 这意味着需要一个公开的 URL, 任何人都可以通过这个 URL 来访问我们的方程式。 这是怎么办到的呢? 最简单的方式是提供你自己的 IP 地址给你的小伙伴们。 因此, 人们将在你的个人网络中访问你的方程式。 所以呢, 运行你的番茄钟:
21 |
22 | **> cd **
23 | **> npm run dev**
24 |
25 |
26 | 查看你的 IP:
27 |
28 | **ifconfig**
29 |
30 | 
31 |
32 | 用 ifconfig 命令行查看 IP 地址
33 |
34 | 把这个地址分享给和你在统一局域网内的小伙伴们。 在我这就是 *http://192.168.1.6:8080* 。
35 |
36 | 但是, 只有那些和你在统一局域网的小伙伴才能访问到方程式, 其他人访问不到。
37 |
38 | 当然你可以使用一些把你的地址转换为公共地址的软件, 例如, **ngrok** ( https://ngrok.com/)。 运行程序后输入如下命令行:
39 |
40 | **ngrok http 8080**
41 |
42 | 这将创建一个在任何地方都可以访问到的地址, 就像普通的网站一样:
43 |
44 | 
45 |
46 | 使用 ngrok 为你的本地主机提供通道
47 |
48 | 我的地址是 *http://5dcb8d46.ngrok.io* 。 我可以把这个地址分享给任何人, 它们都可以通过这个地址来访问到番茄钟方程式了! 但是我可以让我的笔记本运行一晚上, 但我不能让它运行一辈子。 一旦我关掉网络连接, 我的方程式将不能再被访问。 所以呢, 即使我们可以一直开机, 但我不太喜欢这个网站地址。 这个地址由一些字符和数字组成, 而我希望它们是一些有意义的东东。
49 |
50 | 这里有很多好的方式实现它。 我可以购买, 例如在 **AWS(AmazonWeb Services)** 购买一个虚拟实例服务, 复制我的方程式到这个实例上,在类似 *GoDaddy* 上买个域名, 关联这个域名到购买的实例地址上, 我们就能访问啦。 很神奇吧, 但是很贵。 在我们的方程式达到一定规模时再考虑这个方案吧。
51 |
52 | 现在, 在本章, 我们将采用一种更便宜, 简单, 健壮的部署方案。 这就是我们为何要在 Heroku 上部署方程式的原因。 Heroku 是一个云应用平台。 首先我们需要在 GitHub 上建立我们的方程式。 你还记得部署的意思是指让我的方程式可以被使用吗? 我想在部署前让这个方程式测试, 并通过测试。 这就是我们在部署前使用 Travis 来保证我们的方程式质量的原因。 所以, 按照下面的步骤来:
53 |
54 | 1. 创建 GitHub 项目, 并移入方程式。
55 | 2. 用 Travis 持续集成。
56 | 3. 链接项目到 Heroku, 并配置。
57 |
58 | 在下面的三小章, 我将稍微介绍一下 GitHUb, Travis, Heroku。
59 |
60 |
61 | ## 啥是 GitHub?
62 | GitHub 是基于 Git-based 项目的主机提供商。
63 |
64 | 它可以用在小型私人规模的个人和公共项目中。 它可以用于那些涉及开发大型协同项目中。
65 |
66 | 每个了解开源软件的人都知道 GitHub。 如果你在读这本关于 Vue 的书, 我相信你一定会跳过这部分的。
67 |
68 | ## 啥是 Travis?
69 |
70 | Travis 是由 GitHub 出品的允许我们连接 GitHub 项目并确保项目质量的工具。 它会在你的项目内运行测试并告诉你构建是否已通过。 在这里 https://travis-ci.org/ 查看更多信息。
71 |
72 |
73 | ## 啥是 Heroku?
74 |
75 | Heroku 是一个部署你的方程式的云平台。 它非常易用。 你只需要创建你的方程, 并起一个有意义的名字, 连接 GitHub 上的项目, 就行啦! 每次你推送给指定的分支, Heroku 将运行你在入口文件内提供的脚本并重新部署。
76 |
77 | 它是高可配置性的, 也提供了一个命令行界面, 所以你可以完全是用命令行而不必去 Heroku 网站指示板上查看。 我们来自己动手吧!
78 |
79 |
80 | ## 迁移方程式到 GitHub 仓库
81 |
82 | 我们以为我们的方程式创建一个 GitHub 仓库为起点。
83 |
84 | 请使用 [ chapter8/pomodoro ](https://github.com/PacktPublishing/Learning-Vue.js-2/tree/master/chapter8/pomodoro) 和 [ chapter8/shopping-list ](https://github.com/PacktPublishing/Learning-Vuejs-2/tree/master/chapter8/shopping-list) 文件。
85 |
86 | 如果你还没注册 GitHub, 注册一个。 登录帐号并创建两个仓库 *Pomodoro, ShoppingList* :
87 |
88 | 
89 |
90 | 在 GitHub 上创建仓库
91 |
92 | 一旦你点击了 **Create respository** 按钮,一个带有不同指令的页面将出现。 我们对第二段很感兴趣, **...or create a new
93 | repository on the command line**。 在番茄钟方程式内的命令行按提示复制, 粘贴。移除第一行:
94 |
95 | **git init**
96 | **git add**
97 | **git commit -m "first commit"**
98 | **git remote add origion https://github.com/chudaol/Pomodoro.git**
99 | **git push -u origin master**
100 |
101 | 刷新你的 GitHub 项目页, 你将看到代码出现在这了!
102 |
103 | ## 用 Travis 持续集成
104 |
105 | 为了用 Travis 持续集成, 首先你需要用你的 GitHub 帐号连接 Travis 帐号。 打开 https://travis-ci.org/ 点击 **Sign in with GitHub** 按钮:
106 |
107 | 
108 |
109 | 点击 Sign in with GitHub 按钮
110 |
111 | 现在你可以添加被 Travis 跟踪的仓库了。 点击加号:
112 |
113 | 
114 |
115 | 点击加号添加你的 GitHub 项目
116 |
117 | 在你点击加号后, 整个 GitHub 项目都出现了, 选择你要跟踪的那个:
118 |
119 | 
120 |
121 | 选择你要跟踪的项目
122 |
123 | 现在我们已经把项目连接到了 Travs 构建系统了, 所有 commit 和推送到 msater 分支的东东都将被跟踪, 我们需要告诉它在追踪到变化时该做些什么。 Travis 所有的配置都在 *.travis.yml* 文件内。 为两个项目添加 *.travis.yml* 文件。 我们至少需要告诉它 node 使用的版本。 检查你系统内 Node 的版本:
124 |
125 | **node --version**
126 |
127 | 我的是 *v5.11.0* 。 所以:
128 |
129 | ```js
130 | //.travis.yml
131 | language: node_js
132 | node_js:
133 | - "5.11.0"
134 | ```
135 |
136 | 如果你现在 commit , push, 你将看到 Travis 自动运行了测试。 默认的, 它为项目运行 *npm test* 命令。 等几分钟后查看结果。 不幸的是, 在执行端对端测试时将失败。 为啥会这样呢?
137 |
138 | 默认情况下, 虚拟映像的 Travis 构建和测试环境没有安装浏览器。 但我们的 Selenium 测试尝试运行浏览器。 幸运的是, Travis 提供了在构建时执行命令行的机制。 可以在 *before_script* 部分设置。 我们调用必要的安装 Chrome 浏览器命令并导出 CHROME_BIN 变量。 为你的 *.travis.yml* 文件添加如下内容:
139 |
140 | ```js
141 | before_script:
142 | - export CHROME_BIN=/usr/bin/google-chrome
143 | - sudo apt-get update
144 | - sudo apt-get install -y libappindicator1 fonts-liberation
145 | - wget https://dl.google.com/linux/direct/google-chromestable_current_amd64.deb
146 | - sudo dpkg -i google-chrome*.deb
147 | ```
148 |
149 | 正如你看到的, 为了执行安装及升级系统, 我们必须以 *sudo* 调用命令行。 Travis 在默认情况下不允许你使用 *sudo* 命令以防出现非信任的行为。 但是你可以明确告知 Travis 使用 *sudo* 。 加入下面内容:
150 |
151 | ```js
152 | sudo: required
153 | dist: trusty
154 | ```
155 |
156 | 现在整个 *.travis.yml* 文件是这样的:
157 |
158 | ```yml
159 | //.travis.yml
160 | language: node_js
161 | sudo: required
162 | dist: trusty
163 | node_js:
164 | - "5.11.0"
165 |
166 |
167 | before_script:
168 | - export CHROME_BIN=/usr/bin/google-chrome
169 | - sudo apt-get update
170 | - sudo apt-get install -y libappindicator1 fonts-liberation
171 | - wget https://dl.google.com/linux/direct/google-chromestable_current_amd64.deb
172 | - sudo dpkg -i google-chrome*.deb
173 | ```
174 |
175 | 试着 commit 并查看 Travis 仪表板。
176 |
177 | 哇哦! 再次失败了:
178 |
179 | 
180 |
181 | 在安装 Chrome 后, 由于超时测试失败了
182 |
183 | 为啥会这样呢? 我们回忆下运行端对端测试时会发生什么。 每个测试都将打开浏览器执行 UI 操作。 关键是在 UI 内的最后一句。 如果我们需要测试 UI, 我们需要 **图形用户界面 (GUI)** 。 Travis 虚拟映像没有图形显示的。 因此, 没办法打开浏览器显示我们的 UI。 幸运的是, 这有个好东西叫 *Xvfb - X virtual framebuffer* 。
184 |
185 | Xvfb 是实现物理显示协议的展示服务器。 所有需要的图形操作都在内存中执行; 因此, 我们不需要物理显示了。 我们通过运行 Vvfb 服务为测试添加虚拟图形环境。 如果你看了 Travis 文档, 你将在这里发现这已被写入建议中: https://docs.travis-ci.com/user/gui-and-headless-browsers/#Using-xvfb-to-Run-Tests-ThatRequire-a-GUI 。 打开 *.travis.yml* 文件添加:
186 |
187 | ```js
188 | - export DISPLAY=:99.0
189 | - sh -e /etc/init.d/xvfb start
190 | ```
191 |
192 | 完整 YML 文件如下:
193 |
194 | ```yml
195 | //.travis.yml
196 | language: node_js
197 | sudo: required
198 | dist: trusty
199 | node_js:
200 | - "5.11.0"
201 | before_script:
202 | - export CHROME_BIN=/usr/bin/google-chrome
203 | - sudo apt-get update
204 | - sudo apt-get install -y libappindicator1 fonts-liberation
205 | - wget https://dl.google.com/linux/direct/google-chromestable_current_amd64.deb
206 | - sudo dpkg -i google-chrome*.deb
207 | - export DISPLAY=:99.0
208 | - sh -e /etc/init.d/xvfb start
209 | ```
210 |
211 | commit 并查看 Travis 仪表板。 番茄钟方程式构建成功了!
212 |
213 | 番茄钟方程式成功构建
214 |
215 | 但是购物清单构建失败了。 注意 Travis 甚至变化了每个构建任务选项卡的颜色:
216 |
217 | 
218 |
219 | 通过构建状态 Travis 改变了选项卡标志的颜色
220 |
221 |
222 | 在构建购物清单时发生了什么? 在端对端测试里有一步来检查物理显示的标题是否呈现在页面上。 这个标题来自后端服务器, 需启用 *npm run server* 命令。 你还记得我们在第六章中使用的 *vue-resource* 插件吗? 就是说, 在构建我们的方程式前我们需要告诉 Travis 先运行服务器。 在 *.travis.yml* 文件内添加:
223 |
224 | ```
225 | - nohup npm run server &
226 | ```
227 |
228 | Commit 改变并检查 Travis 仪表盘。构建通过了! 现在我们的构建过程通过了, 我们可以在 README.md 文件内加一个 Travis 按钮了。
229 |
230 | 在你的方程式的 Travis 页面点击 **build passing** 按钮, 选择 **Markdown** 选项, 拷贝生成的文本到 README.md 文件。
231 |
232 | 
233 |
234 | 然后你的 README 文件会是这样:
235 |
236 | 
237 |
238 |
239 | 现在, 我们的方程式将会在每次 commit 后自动测试。
240 |
241 | 在部署方程式前请查看 Heroku 文档并创建帐号 https://signup.heroku.com/dc , 安装 Heroku Toolbelt (https://devcenter.heroku.com/articles/getting-started-with-nodejs#set-up) 。
242 |
243 | 现在来部署我们的项目。
244 |
245 | ## 部署番茄钟
246 |
247 | 往 Heroku 帐户上添加一个项目。 点击 **Create New App** 按钮。 你可以创建方程式自己的名字:
248 |
249 | 
250 |
251 | 点击 *Create App* 按钮, 选择 GitHub 方式, 选择你要部署的项目:
252 |
253 | 
254 |
255 |
256 | 然后点击 **Connect** 按钮, 选择 **Automatic deploys from master are enabled, Wait for CI to pass before
257 | deploy** 。
258 |
259 | 
260 |
261 | 万事俱备, 点击 **Deploy Branch** 按钮, Heroku 将执行构建, 然后你就可以在浏览器内看到方程式了。
262 |
263 | ## 查看日志
264 |
265 | 希望你已经安装了 Heroku CLI, 你可以运行 heroku 命令行了, 检查日志, 运行 *heroku logs* :
266 |
267 | **heroku logs --app catodoro --tail**
268 |
269 | 你将看到持续运行的日志在 Heroku 试着执行构建时。 错误为 *npm ERR! missing script: start* 。 在 *package.json* 内没有启动脚本。
270 |
271 | 确实, 我们要建立一个启动脚本, 首先得知道如何构建并运行一个 Vue 方程式。 README 文件告诉我们需要运行 *npm run build* 命令行。 本地运行它:
272 |
273 | 
274 |
275 | 我们知道构建的结果是放在 *dist* 文件夹的。 我们也知道我们需要 *index.html* 文件来启动 HTTP 服务。 我们知道必须来创建一个启动脚本, 然后 Heroku 就知道怎么启动我们的方程式了。
276 |
277 | ### 准备在 Heroku 上运行方程式
278 |
279 | 我们可以通过日志文件来集中信息。 在部署前需要以下几个步骤:
280 |
281 | * 运行 *npm install* 脚本来加载依赖
282 | * 运行 *npm start* 脚本开启服务
283 |
284 | 所以我们需要这样做:
285 | * 告诉 Heroku 安装所有的依赖; 我们需要移除 *devDependencies* 部分的项目依赖, 然后 Heroku 才能安装
286 | * 告诉 Heroku 在 *npm install* 后运行构建脚本; 为此我们需要创建 *postinstall* 脚本调用 *npm run*
287 | * 创建 *server.js* 来启动 *index.html* 文件
288 | * 提供一个 Heroku 运行 *server.js* 的服务, 我们需要创建 *start* 脚本来运行 *server.js* 脚本
289 |
290 | 移除所有的依赖:
291 |
292 | ```json
293 | "dependencies": {
294 | "autoprefixer": "^6.4.0",
295 | "babel-core": "^6.0.0",
296 | "babel-eslint": "^7.0.0",
297 | "babel-loader": "^6.0.0",
298 | "babel-plugin-transform-runtime": "^6.0.0",
299 | "babel-polyfill": "^6.16.0",
300 | "babel-preset-es2015": "^6.0.0",
301 | "babel-preset-stage-2": "^6.0.0",
302 | "babel-register": "^6.0.0",
303 | "chalk": "^1.1.3",
304 | "connect-history-api-fallback": "^1.1.0",
305 | "cross-spawn": "^4.0.2",
306 | "css-loader": "^0.25.0",
307 | "es6-promise": "^4.0.5",
308 | "eslint": "^3.7.1",
309 | "eslint-config-standard": "^6.1.0",
310 | "eslint-friendly-formatter": "^2.0.5",
311 | "eslint-loader": "^1.5.0",
312 | "eslint-plugin-html": "^1.3.0",
313 | "eslint-plugin-promise": "^2.0.1","eslint-plugin-standard": "^2.0.1",
314 | "eventsource-polyfill": "^0.9.6",
315 | "express": "^4.13.3",
316 | "extract-text-webpack-plugin": "^1.0.1",
317 | "file-loader": "^0.9.0",
318 | "function-bind": "^1.0.2",
319 | "html-webpack-plugin": "^2.8.1",
320 | "http-proxy-middleware": "^0.17.2",
321 | "inject-loader": "^2.0.1",
322 | "isparta-loader": "^2.0.0",
323 | "json-loader": "^0.5.4",
324 | "lolex": "^1.4.0",
325 | "opn": "^4.0.2",
326 | "ora": "^0.3.0",
327 | "semver": "^5.3.0",
328 | "shelljs": "^0.7.4",
329 | "url-loader": "^0.5.7",
330 | "vue": "^2.0.1",
331 | "vuex": "^2.0.0",
332 | "vue-loader": "^9.4.0",
333 | "vue-style-loader": "^1.0.0",
334 | "webpack": "^1.13.2",
335 | "webpack-dev-middleware": "^1.8.3",
336 | "webpack-hot-middleware": "^2.12.2",
337 | "webpack-merge": "^0.14.1"
338 | },
339 | "devDependencies": {
340 | "chai": "^3.5.0",
341 | "chromedriver": "^2.21.2",
342 | "karma": "^1.3.0",
343 | "karma-coverage": "^1.1.1",
344 | "karma-mocha": "^1.2.0",
345 | "karma-phantomjs-launcher": "^1.0.0",
346 | "karma-sinon-chai": "^1.2.0",
347 | "karma-sourcemap-loader": "^0.3.7",
348 | "karma-spec-reporter": "0.0.26",
349 | "karma-webpack": "^1.7.0",
350 | "mocha": "^3.1.0",
351 | "nightwatch": "^0.9.8",
352 | "phantomjs-prebuilt": "^2.1.3",
353 | "selenium-server": "2.53.1",
354 | "sinon": "^1.17.3",
355 | "sinon-chai": "^2.8.0"
356 | }
357 | ```
358 |
359 | 现在创建 *postinstall* 脚本来运行 *npm run build*:
360 |
361 | ```json
362 | "scripts": {
363 | <...>
364 | "postinstall": "npm run build"
365 | },
366 | ```
367 |
368 | 创建一个 *server.js* 文件来启动 *index.html* :
369 |
370 | ```js
371 | // server.js
372 | var express = require('express');
373 | var serveStatic = require('serve-static');
374 | var app = express();
375 | app.use(serveStatic(__dirname + '/dist'));
376 | var port = process.env.PORT || 5000;
377 | app.listen(port);
378 | console.log('server started '+ port);
379 | ```
380 |
381 | 好的, 现在我们只需要创建 *start* 脚本了:
382 |
383 | ```json
384 | "scripts": {
385 | <...>
386 | "postinstall": "npm run build",
387 | "start": "node server.js"
388 | },
389 | ```
390 |
391 | 提交更改, 点击 **Deploy Branch** 按钮, 别忘了查看运行日志!
392 |
393 | 太棒了, 在成功构建后, 点击 **View** 按钮; 你就能看到自己的方程式了:
394 |
395 | 
396 |
397 | 成功部署到 Heroku 上的番茄钟方程式
398 |
399 | ## 部署购物清单方程式
400 |
401 | 为了部署我们的购物清单方程式我们需要做在番茄钟内做的准备工作。
402 |
403 | 在 Heroku 上创建新的方程式并连接到 GitHub。 然后拷贝 *server.js* 文件, 处理依赖,创建 *postinstall* 和 *start* 脚本。
404 |
405 | 但是, 我们依然还差一步, 别忘了我们有 REST API 服务, 我们需要启动它。
406 |
407 | 我们只需要合并两个服务, 在 *server.js* 内直接开启了全部服务:
408 |
409 | ```js
410 | //server.js
411 | var express = require('express');
412 | var jsonServer = require('json-server');
413 | var serveStatic = require('serve-static');
414 | var app = express();
415 |
416 | app.use(serveStatic(__dirname + '/dist'));
417 | app.use('/api', jsonServer.router('server/db.json'));
418 | var port = process.env.PORT || 5000;
419 | app.listen(port);
420 | console.log('server started '+ port);
421 | ```
422 |
423 | 我们告诉 express app 使用 *jsonServer* 并在 */api/* 上启动 *db.json* 文件。
424 |
425 | 我们也得改变 Vue resource 上的端口。 在 API 文件夹内打开 *index.js* 用下面的 api 替代 *localhost:3000*:
426 |
427 | ```
428 | const ShoppingListsResource = Vue.resource('api/' + 'shoppinglists{/id}')
429 | ```
430 |
431 | 我们也应该添加 JSON 服务给 *dev-server.js*; 否则, 我们将不能在开发模式内运行方程式。 所以打开 *build/dev-server.js* 文件, 导入 *jsonServer*:
432 |
433 | ```js
434 | //dev-server.js
435 | var path = require('path')
436 | var express = require('express')
437 | var jsonServer = require('json-server')
438 | <...>
439 |
440 | // compilation error display
441 | app.use(hotMiddleware)
442 | // use json server
443 | app.use('/api', jsonServer.router('server/db.json'));
444 | <...>
445 | ```
446 |
447 | 试着在开发模式下运行方程式 *npm run dev*。 运行成功。
448 |
449 | 现在我们可以在 *travis.yml* 文件内移除服务器运行指令 (- nohup npm run server &)。 你也可以在 *package.json* 文件内移除服务器脚本。
450 |
451 | 在本地运行测试检查错误。
452 |
453 | 基本大功告成了。 我们在本地运行下方程式。
454 |
455 | ## 在本地运行 Heroku
456 |
457 | 有时我们需要不停地更改文件, 部署, 所以在本地试着运行方程式会更加节省时间。 幸运的是, Heroku CLI 提供了一种在本地运行方程式的方法, 你只需要运行 *heroku local web* 命令就行了:
458 |
459 | **npm run build**
460 | **heroku local web**
461 |
462 | 在浏览器中打开 http://localhost:5000。 成功了!
463 |
464 | 
465 |
466 | 我们来提供更改并推送。
467 |
468 | 现在你可以等待 Travis 成功构建并自动部署在 Heroku 上或者打开你的 Heroku 仪表盘点击 **Deploy Branch** 按钮。 这儿有我们今天部署的两个方程式:
469 |
470 | * 番茄钟: https://catodoro.herokuapp.com/
471 | * 购物清单: https://shopping-list-vue.herokuapp.com/
472 |
473 | 在 GitHub 上找到相应的项目 https://github.com/chudaol/Pomodoro, https://github.com/chudaol/ShoppingList 。
474 |
475 | 克隆, 部署。此时, 你学会了所有需要的工具, 可以把你的方程式展示给全世界了。 谢谢你一直陪着我走完这段激情之旅!
476 |
--------------------------------------------------------------------------------
/chapter-8/imgs/8-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-8/imgs/8-1.png
--------------------------------------------------------------------------------
/chapter-8/imgs/8-10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-8/imgs/8-10.png
--------------------------------------------------------------------------------
/chapter-8/imgs/8-11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-8/imgs/8-11.png
--------------------------------------------------------------------------------
/chapter-8/imgs/8-12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-8/imgs/8-12.png
--------------------------------------------------------------------------------
/chapter-8/imgs/8-13.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-8/imgs/8-13.png
--------------------------------------------------------------------------------
/chapter-8/imgs/8-14.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-8/imgs/8-14.png
--------------------------------------------------------------------------------
/chapter-8/imgs/8-15.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-8/imgs/8-15.png
--------------------------------------------------------------------------------
/chapter-8/imgs/8-16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-8/imgs/8-16.png
--------------------------------------------------------------------------------
/chapter-8/imgs/8-17.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-8/imgs/8-17.png
--------------------------------------------------------------------------------
/chapter-8/imgs/8-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-8/imgs/8-2.png
--------------------------------------------------------------------------------
/chapter-8/imgs/8-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-8/imgs/8-3.png
--------------------------------------------------------------------------------
/chapter-8/imgs/8-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-8/imgs/8-4.png
--------------------------------------------------------------------------------
/chapter-8/imgs/8-5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-8/imgs/8-5.png
--------------------------------------------------------------------------------
/chapter-8/imgs/8-6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-8/imgs/8-6.png
--------------------------------------------------------------------------------
/chapter-8/imgs/8-7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-8/imgs/8-7.png
--------------------------------------------------------------------------------
/chapter-8/imgs/8-8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-8/imgs/8-8.png
--------------------------------------------------------------------------------
/chapter-8/imgs/8-9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-8/imgs/8-9.png
--------------------------------------------------------------------------------
/chapter-8/imgs/9-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-8/imgs/9-1.png
--------------------------------------------------------------------------------
/chapter-8/imgs/Thumbs.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-8/imgs/Thumbs.db
--------------------------------------------------------------------------------
/chapter-9/Next.md:
--------------------------------------------------------------------------------
1 | # What Is Next?
2 |
3 | 在前一章我们部署了方程式并让全世界的人都可以访问。 我们持续集成和持续部署我们的方程式。 这意味着我们每次的更改方程式将自动测试并部署。
4 |
5 | 看起来我们在本书的旅程要结束了。 但是, 好戏才刚刚开始。 在学习了所有知识后, 依旧有很多需要学习! 在本章, 我们将总结我们所学的一切。 有如下几件:
6 |
7 | * 总结我们所学的一切
8 | * 制作后续事项清单
9 |
10 | # 至今的旅程
11 |
12 | 我们已经走了很长一段, 是时候总结一下啦。
13 |
14 | 在第一章, 和 *Vue.js 去采购*, 是我们和 Vue.js 的第一面。 我们讨论了 Vue.js 是什么, 能做什么, 它是怎么被创建来的, 附加一些基本事例。
15 |
16 | 在第二章, 基础 -- 安装使用, 深入 Vue.js。 我们学习了 MVVM 架构模式, 我们看到了 Vue.js 是怎么运行的, 了解 Vue.js 不同的方面如 *components, directives, plugins* 和方程式状态。 我们学习了不同的安装方法, 开始使用简单的编译脚本。 尝试使用 CDN, NPM 并进入到 Vue.js 的源码内。 我们学习了如何调试, 如何用 *Vue-cli* 来生成 Vue.js 方程式脚手架。 我们还用 CSP-compliant 版本的 Vue 创建了一个真正的 Chrome 方程式。
17 |
18 | 在第三章, 组件 -- 理解与使用, 我们深入了组件系统。 我们学习了如何来定义 Vue 组件, 组件的作用域如何运行, 组件间是如何关联的。 我们开始使用单文件组件。
19 |
20 | 在[第四章](), 响应式 -- 为你的方程式绑定数据, 我们深入 Vue.js 的数据绑定和响应式原理。 学习如何使用指令, 表达式, 过滤器。 我们为开发中的方程式带来数据绑定, 让它们变得可交互, 感谢 Vue.js 带来响应式。
21 |
22 | 在[第五章](), Vuex -- 在方程式内管理状态, 我们学习了用 Vuex 仓库系统在 Vue 方程式内管理全局状态。 我们学习了如何使用 states, actions, getters, mutations 来创建模块化的良好结构的组件。 我们从此便一直在方程式内运用这些新知识。
23 |
24 | 在[第六章](), 插件 -- 自己动手,丰衣足食, 我们学习了 Vue 方程式如何操作插件。 我们使用了已有的插件 *vue-resource* , 它帮助我们在浏览器刷新时保存方程式的状态。 我们也为自己的方程式创建了一个产生白, 棕, 粉声音的插件。 此时, 我们已经有了功能基本完备的方程式了。
25 |
26 | 在[第七章](), 测试 -- 测试我们所做的一切, 我们学习了如何来测试我们的方程式。 我们学习如何书写单元测试, 如何用 Selenium 驱动进行端对端测试。 我们学习了什么是代码覆盖率, 在单元测试内怎么去模拟服务器响应。 我们通过了百分百的覆盖率并用 Selenium 驱动实践了端对端测试。
27 |
28 | 在[第八章](), 部署 -- 上线!, 方程式最终上线了。 我们把它们部署到 Heroku 云系统, 现在可以在任何地方访问这些方程式了。 而且, 我们让方程式部署完全自动化。 每次推送变化到主分支, 方程式会自动测试部署。
29 |
30 | 因此, 在本书内, 我们不仅学习了一个新框架。 我们还运用所学开发了两个简单的方程式。 我们运用了 Vue 最重要的概念让我们的方程式变得响应式, 迅速, 可测试。 但是, 还没完呢。 在本书写就时, Vue 2.0 已经发布。 它带来了一些新东东来学习使用。
31 |
32 | ## Vue 2.0
33 |
34 | Vue 2.0 在 2016 年 9 月 30 日发布了。 查看 Evan You 的文章 https://medium.com/the-vue-point/vue-2-0-is-here-ef1f26acf4b8#.ifpgtjlek 。
35 |
36 | 在本书中, 我们使用了最新的版本; 但是我参考了很多 Vue.js 1.0 的内容。 实际上, API 基本不变, 只有轻微的改变, 一些弃用的特性, 最后提供的接口基本不会改变。
37 |
38 | 然而, Vue 基本上是重写过了! 当然, 这里有一些部分的代码基本没变, 但是总的来说, 主要的重构和一些概念完全变化了。 例如, 渲染层完全被重写。 性能提升了:
39 |
40 | 
41 |
42 |
43 | ## 回顾我们的方程式
44 |
45 | 我们来看看迄今为止我们做了哪些。 我们已经用 Vue.js 开发了两个方程式。 现在就回顾一下。
46 |
47 | ## 购物清单方程式
48 |
49 | 我们开发的购物清单方程式允许:
50 |
51 | * 创建不同的清单列表
52 | * 添加新的清单项兵切换状态
53 | * 重命名购物清单并删除
54 |
55 | 我们的购物清单方程式在 Heroku 可以找到: https://shopping-list-vue.herokuapp.com/ 。
56 |
57 | 它的代码托管在 GitHub:https://github.com/chudaol/ShoppingList 。
58 |
59 | 用 Travis 持续集成: https://travis-ci.org/chudaol/ShoppingList 。
60 |
61 | 界面易于理解使用:
62 |
63 | 
64 |
65 | 距离一个真正的购物清单方程式还远, 对吗?
66 |
67 |
68 | ## 番茄钟方程式
69 |
70 | 番茄钟方程式完成了番茄钟计时功能和声音选择, 还能显示小猫的。 有如下几个功能:
71 |
72 | * 开始, 暂停, 停止方程式
73 | * 在工作时听白色的声音, 声音来帮助我们集中精神
74 | * 切换声音
75 | * 在业余时间盯着小猫
76 |
77 | 我们的番茄钟方程式也在 Heroku : https://catodoro.herokuapp.com/。
78 |
79 | 代码托管在 GitHub: https://github.com/chudaol/Pomodoro。
80 |
81 | 同样以 Travis 构建测试: https://travis-ci.org/chudaol/Pomodoro。
82 |
83 | 用户界面清晰简单:
84 |
85 | 
86 |
87 | 五分钟休息时会出现一只小猫:
88 |
89 | 
90 |
91 | 很可爱吧,但是远不够完美。
92 |
93 | ## 为啥才是刚刚开始?
94 |
95 | 在前面的部分, 我们总结了我们在本书发开的方程式。 我们也同意这些方程式远不够完美。 进击更完美的状态就是我们现在的挑战和目的。 这里还有很多工作需要完成。 我们的方程式很棒, 但是缺少 功能, 样式, 标识, 用户体验模式, 接入平台等等。 我们看看还需哪些工作。
96 |
97 | ## 为我们的方程式添加功能
98 |
99 | 我们的方程式已经有很多很棒的功能了, 但还可以拥有更多。 它们应该是可以配置的。 更灵活。 也可以对用户更加友好。 我们来看看每个方程式可以添加哪些功能。 这留作你的家庭作业。
100 |
101 | ### 购物清单方程式
102 |
103 | 在浏览器内打开方程式。 你可以添加你的列表和列表项。 你可以删除选项和列表。 但是每个人打开方程式都是一样的列表。 这意味着我们必须为每个人提供各自的购物清单方程式, 这需要认证机制。
104 |
105 | 这里也有一些 UX 问题。 为什么我们需要在输入框内改变标题而不是在标题本身上改变呢? 实际上, 这个功能是我们最先完成的, 这在当时很有意义, 但现在我们需要改进它。
106 |
107 | 另一件事就是删除数据。 这儿没有一种方法来全部删除它们。 如果我们有一个长列表, 当我们删除单个选项时, 需要删除整个购物清单。 我们应该通过点击来删除。
108 |
109 | 还有一个就是样式问题了。 不同的列表应该有不同的背景色, 不同的字体色, 甚至是不同的字体。 这有一份改进清单:
110 |
111 | * 完成认证机制
112 | * 完成行内名称编辑
113 | * 完成清除已选选项
114 | * 完成配置不同清单的样式, 如背景色, 文字色, 字体等
115 |
116 | 你也可以为每个选项类型提供不同的标志。 你可以看看 Splitwise 方程式照照灵感 https://www.splitwise.com/ 。
117 |
118 | 
119 |
120 | ### 番茄钟方程式
121 |
122 | 在浏览器内打开番茄钟方程式。 它简单易用很好。 但是, 添加一些额外的配置将更好。 例如, 改变工作时间, 可能是 15 分钟, 也可能是 30 分钟, 这应该是可以配置的。
123 |
124 | 我们在维基上看看一个番茄钟到底该是怎样的: https://en.wikipedia.org/wiki/Pomodoro_Technique 。
125 |
126 | > "After four pomodoros, take a longer break (15-30 minutes), reset your checkmark count to
127 | zero, then go to step 1."
128 | --https://en.wikipedia.org/wiki/Pomodoro_Technique
129 |
130 |
131 | 哎呀! 我们应该在四个番茄中后休息的时间长一点。
132 |
133 | 还有一件重要的事。 对于任何人, 在努力工作后, 我们希望看到自己的工作进度。 你的番茄钟可以显示一些统计就更好了。
134 |
135 | 如果在一段时间后这些统计变得可视化就更好的。 为每个人提供一个番茄钟, 所以我们需要认证机制。
136 |
137 | 而且我们的声音也是被硬编码在方程式:
138 |
139 | ```html
140 |
141 |
142 |
143 |
144 | ```
145 |
146 | 我们不应该让用户自己切换自己喜欢的声音吗? 有足够多的点需要改进了:
147 |
148 | * 完成认证机制
149 | * 完成储存机制 -- 储存工作统计
150 | * 完成统计展示机制 —— 统计并以适当的形式展示
151 | * 添加配置机制。 配置如下:
152 | * 配置工作时间
153 | * 配置休息时间
154 | * 配置在四个番茄钟后更长的休息时间
155 | * 配置自定义的声音
156 |
157 | 正如你看到的, 你依旧有很多工作要做。
158 |
159 | ## 美化我们的方程式
160 |
161 | 当前我们两个方程式都是灰色的。 来点设计会更棒的。 我们来让它们更特别。
162 |
163 | ### 标识
164 |
165 | 以设计标识开始。 通过一个好的标识让你的产品脱颖而出。 我可以帮助你设计番茄钟的标志, 至少有个想法。 类似于这样:
166 |
167 | 
168 |
169 | 为购物清单想一个好标志。 会是什么样的呢? 一个杂货篮? 一个复选框? 还是只是一个缩写? 没有限制!
170 |
171 | ### 标识和设计
172 |
173 | 我们的方程式标识需要独特的设计。 通过一些 UX 技术来为方程式开发良好的标识指南。 想想颜色, 字体, 元素怎样出现在页面上。
174 |
175 | ### 动画和变换
176 |
177 | 动画和变换是带给方程式灵性强有力的方式。 但是, 绝不能滥用。 把他们用在合适的地方才有意义。 例如, 鼠标悬浮在列表标题上会高亮标题, 购物清单选项在被切换时可以有一些弹跳。 番茄钟方程式的每个状态都有不同的背景色。 可以通过背景的不同来体现时间。 机会是无限的。 运用你的想象力和 Vue 的力量来实现你的主意。
178 |
179 | ## 扩展我们的方程式到其它设备
180 |
181 | 我们两个方程式都是 web 方程式。 但我们的购物清单方程式不应只作为一款 web 方程式。 当然你可以在家里写好购物清单然后再手机浏览器上浏览, 但这不是最佳的方式。 使用 Weex(https://github.com/alibaba/weex) 来把我们的 web 方程式带到手机上。 我们两个方程式都可以扩展成 Google Chrome 方程式, 就像我们在第二章里学到的一样。 尽力扩展你的方程式。 期待你的作品。
182 |
183 | ## 总结
184 |
185 | 这是本书的最后一章。 说实话, 我对此有点伤感。 我们一起度过了愉快的时光。 我知道我不认识你, 但我感觉已经认识了你。 我们就像在一起对话一样。 对于所有开发的东东, 我不能说这全部是由我一个人开发的; 但我感觉我们就像在一起工作一样。
186 |
187 | 这种感觉很搞笑, 实际上, 因为当你阅读此书时已经是将来的事了。 而你读到这儿时是和过去的我在交流。 我喜欢书和科技这种不仅在人与人间而且是在时空与时空间建立的联系的方式。
188 |
189 | 我真的希望你也能成为像我一样 Vue.js 的粉丝。
190 |
191 | 我真的希望你至少完善一个我们开发至今方程式并给我看看。 我将很乐意帮助你。 欢迎给我留言 *chudaol@gmail.com*。
192 |
193 | 感谢你和我一路走来, 希望我们在下一本书相见。
194 |
--------------------------------------------------------------------------------
/chapter-9/imgs/9-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-9/imgs/9-1.png
--------------------------------------------------------------------------------
/chapter-9/imgs/9-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-9/imgs/9-2.png
--------------------------------------------------------------------------------
/chapter-9/imgs/9-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-9/imgs/9-3.png
--------------------------------------------------------------------------------
/chapter-9/imgs/9-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-9/imgs/9-4.png
--------------------------------------------------------------------------------
/chapter-9/imgs/9-5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-9/imgs/9-5.png
--------------------------------------------------------------------------------
/chapter-9/imgs/9-6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-9/imgs/9-6.png
--------------------------------------------------------------------------------
/chapter-9/imgs/Thumbs.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/chapter-9/imgs/Thumbs.db
--------------------------------------------------------------------------------
/last.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hiscc/Learning-Vuejs-2-zh_CN/cbf94ed98d5ac0b75d999774e234c8cccb482a4b/last.md
--------------------------------------------------------------------------------