This is component-a
27 | `
28 | })
29 | ```
30 |
31 | 这样我们就注册了一个名为`component-a`的**全局组件**。注意是全局组件,也就是说,在任何地方我们都可以使用这个组件,而且有一点需要注意,就是组件**名字不能重复**。所以,除了注册一个全局组件外,还可以注册有一个局部组件,那么这个局部组件只能在当前的文件中使用。
32 |
33 | ```js
34 | var componentA = {
35 | // ...
36 | }
37 | ```
38 |
39 | 这样,就完成了一个局部组件的注册。
40 |
41 | 有一点我需要声明一下,本文主要是向大家讲解一下如何学习我们工作中最常使用的方法。所以,以上方式我在工作中并没有经常使用,我也不准备深入讲解以上方法,更具体的信息可以查阅官方文档中关于[组件基础](https://cn.vuejs.org/v2/guide/components.html)/[深入了解组件](https://cn.vuejs.org/v2/guide/components-registration.html)一节。
42 |
43 | 所以,我接下来要讲的是我工作中最常使用的注册组件和使用注册的方式,也就是如何将一个`.vue`文件注册为一个组件和**怎么使用它**。更多关于此方法优点的描述请查看[官方文档](https://cn.vuejs.org/v2/guide/single-file-components.html)
44 |
45 | ::: warning 注意
46 | 在此之前,你需要学会使用`vue-cli`构建一个完整的 Vue 项目。接下来都是对于此方式进行讲解。同时,你还需要知道点`es6 import、export`的知识。
47 | :::
48 |
49 | ### 深入注册组件
50 |
51 | 在讲怎么使用之前,我想举一个例子,就是赵本上老师的小品,把大象放冰箱需要几步。
52 |
53 | - 首先,我们得有一个“大象”吧,那第一步意味着我们需要得到一个大象。
54 | - 其次,就是打开冰箱门
55 | - 再次,把“大象”放入冰箱
56 | - 最后,关上冰箱门
57 |
58 | 那么,在我们 Vue 中,跟上面几步其实差不多。
59 |
60 | - 注册组件
61 | - 引入组件
62 | - 放入`components`中
63 | - 使用组件
64 |
65 | 下面我们来分解一下以上步骤
66 |
67 | #### 注册组件
68 |
69 | 根据项目规范,我们所有的页面文件都放在`src`目录下,其中创建一个`views`文件夹存放我们的页面文件,`components`文件夹用来存放我们的组件文件。一个`.vue`文件就是一个组件,所以我们可以在`components`目录下创建一个`Button.vue`,新建一个按钮的组件。那么我们可以在`Button.vue`里面放入如下代码结构:
70 |
71 | ```html
72 |
73 |
74 |
75 |
76 |
77 |
78 |
95 |
96 |
97 | ```
98 |
99 | 以上就创建了一个`Button.vue`的组件。
100 |
101 | #### 引入组件
102 |
103 | 引入组件这就涉及到了`es6`的内容,不过 如果你还没有学习,不用担心,你只需要一条指令就可以完成组件的引入,以后你再补`es6`中关于`import/export`的知识即可。
104 |
105 | `import xxx from 'xxx'`
106 |
107 | 上面这条指令就是引入组件的命令,其中,第一个 xxx 是文件名,第二个 xxx 是这个文件对应的路径。
108 |
109 | - 文件名比较简单,如上例的话,就是`Button`。那么,全写为`import Button from 'xxx`
110 | - 文件路径可以有两种方式,一种是**相对路径**,另一种是基于`webpack`配置的`@(src)`的路径。
111 | - 相对路径,是基于当前文件的路径。假如你在`views`目录下创建了一个`index.vue`的文件,那么基于此文件的路径就是`../components/Button.vue`。全写为`import Button from '../components/Button.vue`
112 | - `@`或者`src`, 是通过`webpack`配置的路径,使用`@`或者`src`就代表从`src`目录下开始查找文件。全写为`import Button from '@/components/Button.vue`
113 |
114 | 以上完成了在一个“页面”中引入组件。
115 |
116 | #### 放入 components
117 |
118 | 这一步相当于“把大象放入冰箱的步骤”,引入在一个`.vue`文件里面,只通过`import`引入组件是使用不了的,还需要在`components`里面声明一下。
119 |
120 | `components`相当于一个容器,里面放着的是你引入的组件。其中,`components`跟`methods、computed、data、watch`是同级别的。
121 |
122 | 我们引入了`Button`组件后,需要在`components`里面声明一下:
123 |
124 | ```html
125 |
138 | ```
139 |
140 | 通过`Button: Button`就完成了一个组件的声明,代表着你可以在本页中使用`Button`组件了。
141 |
142 | 如果你知道`es6`对象值的简写的话,上面还可以写成:
143 |
144 | ```js
145 | components: {
146 | Button
147 | }
148 | ```
149 |
150 | 效果相同,看着简洁多了。
151 |
152 | ::: tip 提示
153 | 我记得我刚开始学习的时候,这一步经常落下,所以各位小伙伴们,千万不要把这一步落下!!!
154 | :::
155 |
156 | ::: warning 注意
157 |
158 | 是`components`不是`component`
159 |
160 | 是`components`不是`component`
161 |
162 | 是`components`不是`component`
163 |
164 | :::
165 |
166 | #### 使用组件
167 |
168 | 最后一步是最轻松的了,我们只需要在需要的位置写上组件就可以了。
169 |
170 | ```js
171 |
172 |
173 | // 或者
174 |
175 |
176 | ```
177 |
178 | 以上就是一个组件从注册到引入到声明到使用的步骤,看起来挺复杂,实际上,等你熟悉了之后,使用组件会非常方便。而且你注册了一次组件后,就不需要在注册组件了,只需要重复 2.3.4 步就可以了。
179 |
--------------------------------------------------------------------------------
/docs/basics/class-and-style.md:
--------------------------------------------------------------------------------
1 | # Class 与 Style 绑定
2 |
3 | [[toc]]
4 |
5 | ## 介绍
6 |
7 | 在日常工作中,避免不了需要操作 style 样式和 class,我们可以使用`v-bind`来操作他们。通过`v-bind` 指令,我们可以使用**表达式、对象和数组**来对他们进行控制。
8 |
9 | > 因为本节比较好理解,所以基本参考[官方文档](https://cn.vuejs.org/v2/guide/class-and-style.html)。
10 |
11 | ## class
12 |
13 | 我们可以根据变量来动态的切换 class,最终结果只会渲染出计算后的 class。
14 |
15 | ```html
16 |
17 |
18 | data: { isActive: true }
19 | ```
20 |
21 | 结果为:
22 |
23 | ```html
24 |
25 | ```
26 |
27 | 其中,左侧为 class,右侧为变量。只有变量的值为`truthy`时,才会渲染对应的 class。
28 |
29 | 除了可以使用`v-bind`来绑定 class 外,还可以与原生 class 共存:
30 |
31 | ```html
32 |
33 | data: { isActive: true }
34 | ```
35 |
36 | 结果为:
37 |
38 | ```html
39 |
40 | ```
41 |
42 | 如果样式多了,写在 html 里面比较麻烦,也可以在`data`里面定义一个对象:
43 |
44 | ```html
45 |
46 | data: { classObject: { active: true, test: false } }
47 | ```
48 |
49 | 除了可以使用`对象{}`的方式,还可以通过`数组[]`进行绑定:
50 |
51 | ```html
52 |
53 | data: { activeClass: 'active', testClass: 'test' }
54 | ```
55 |
56 | 结果为:
57 |
58 | ```html
59 |
60 | ```
61 |
62 | 但是这样写的话,写在上面的所有 class 都会渲染出来,那么想要条件渲染可以在数组里面使用对象的语法:
63 |
64 | ```html
65 |
66 | data: { isActiveClass: true, activeClass: 'active', testClass: 'test' }
67 | ```
68 |
69 | ## style
70 |
71 | 同样,style 与 class 一样,都可以通过**对象**和**数组**的方式进行绑定。
72 |
73 | ```html
74 |
75 |
76 | data: { Color: '#000', Height: 100 }
77 |
78 |
79 |
80 | data: { Color: '#000', Height: '100px' }
81 | ```
82 |
83 | 更完整的信息请参阅[官方文档](https://cn.vuejs.org/v2/guide/class-and-style.html)
84 |
85 | ## 小例子
86 |
87 | ### 动态的切换导航页高亮
88 |
89 | 一个常见的需求,就是顶部导航栏或底部导航栏的点击高亮,那么我们通过`v-bind:class`就可以实现这个功能。不过,这个小例子需要搭配`v-for`一起使用。
90 |
91 | 首先我们初始化一个模板:
92 |
93 | ```html
94 |
95 |
96 |
97 |
98 |
106 |
107 |
108 | ```
109 |
110 | 然后简单的添加一个导航栏,在添点样式(不能一直这样丑下去啊:kissing_heart:):
111 |
112 | ```html
113 |
114 |
115 | 标签{{index+1}}
116 |
117 |
118 |
119 |
127 | ```
128 |
129 | 我们首先给导航栏加一个鼠标滑过改变字体颜色的效果吧,那么我们可以使用`v-bind:style`来动态的绑定一个 color 颜色,首先你应该思考一下怎么做,然后在看我的思路:
130 |
131 | ```html
132 |
133 |
134 | 标签{{index+1}}
142 |
143 |
144 |
145 |
155 |
156 |
164 | ```
165 |
166 |
167 |
168 | 通过预览效果能够看到,我们鼠标放上去,颜色字体改变了。这就是通过动态的添加 style 的 color 来改变的,下面我们来分析一下具体的操作:
169 |
170 | - 在我们使用`v-for`遍历时,有一个 index ,代表当前的索引项,分别为 0、1、2...,那么对于我们的例子来说,就是`0、1、2、3 和 4`。
171 | - `@mouseenter="hoverIndex = index"` 这一步是最为重要,这里使用了我们前面学习的`v-on`的语法,监听了`mouseenter`事件,当鼠标放在**当前的 span 标签时**,设置`hoverIndex`变量等于**index**。通过这一步,我们就知道了**当前鼠标放在的是哪一个 span 标签**。
172 | - `:style="{color: hoverIndex === index ? '#3498db' : ''}"` 使用三元表达式,对 color 的字体颜色进行求值,表示:如果 hoverIndex 等于 当前的**索引项**的话,就添加*蓝色*,否则*什么都不做*。所以,我们知道了鼠标滑过的是哪一个 span 标签后,在设置值就可以了。
173 | - `@mouseleave="hoverIndex = -1"` 这一步,是清除动作,鼠标离开后,把值初始化为-1。为-1 不等于任何 index(0-4)。所以鼠标离开后,不会有任何标签高亮。
174 |
175 | 在经过以上解释后,相信大家已经有了初步的了解,那么我们在设置点击标签后,添加高亮理解起来也就比较方便了。
176 |
177 | 跟动态绑定`style`一样,添加高亮我们使用`v-bind:class`,那么我们就设置一个 class:
178 |
179 | ```css
180 | .active {
181 | border-bottom: 2px solid #e74c3c;
182 | }
183 | ```
184 |
185 | 上面是鼠标滑过,添加颜色效果。这次是点击标签,添加 class。那么就需要使用 `v-on` 来监听 `click` 事件。先自己思考一下,然后继续看如下代码:
186 |
187 | - 首先添加一个变量,用来控制当前**点击的是哪个标签**。我们知道,导航肯定得有一个是高亮状态,不可能没有高亮的标签,所以我们初始化为 0,这样第一个标签自动变成高亮状态。
188 |
189 | ```js
190 | activeIndex: 0
191 | ```
192 |
193 | - 添加`v-on:click`事件。跟上面一样,点击的时候,存储点击的 index。这样通过`activeIndex`变量,我们就知道当前点击的是哪个标签了。如果为 0,说明是第一个;为 1,说明是第二个,以此类推。
194 |
195 | ```js
196 | @click="activeIndex = index"
197 | ```
198 |
199 | - 接下来就是进行判断了,如果当前点击的项和`index`相同,那么就认为这个 span 标签需要添加高亮 class
200 |
201 | ```js
202 | :class="{active: activeIndex === index}"
203 | ```
204 |
205 | 那么通过以上设置后,效果如下:
206 |
207 |
208 |
209 | 总结一下,就是通过一个**变量来储存当前的点击状态**,然后通过**变量与 index 进行对比** 来添加 class。
210 |
211 | 如果你觉得以上你内容比较麻烦,那么还有一个简单的方法。如果你使用了`vue-router`的话,可以通过`
`自带的高亮 class 来添加对应的样式,引用[官方的话](https://router.vuejs.org/zh/guide/#javascript)就是:
212 |
213 | > 要注意,当 `` 对应的路由匹配成功,将自动设置 class 属性值 `.router-link-active`。查看 API 文档 学习更多相关内容。
214 |
215 | 那么只要我们给`.router-link-active`这个 class 设置对应的高亮样式就可以了。
216 |
--------------------------------------------------------------------------------
/docs/basics/instructs/conditional.md:
--------------------------------------------------------------------------------
1 | # 条件渲染
2 |
3 | [[toc]]
4 |
5 | ## 介绍
6 |
7 | 这一节讲的是条件渲染,顾名思义,就是动态的让一个元素显示或者隐藏。主要由两个指令构成,分别是`v-if`和`v-show`。下面我们分别对着两个指令进行详细分析。
8 |
9 | ## v-if
10 |
11 | [v-if](https://cn.vuejs.org/v2/guide/conditional.html)指令可以控制一个元素是否渲染。只有`v-if`的值为 truthy 后,才被渲染。关于 truthy 的解释可以查看[这篇文档](https://developer.mozilla.org/zh-CN/docs/Glossary/Truthy)。其实简单的说就是为“真值”的时候才渲染。
12 |
13 | 还记得最开始的那个例子吗,让我们把它改变一下:
14 |
15 | ```html
16 |
17 |
18 | {{message}}
19 |
20 |
21 |
22 |
31 |
32 |
33 | ```
34 |
35 |
36 | Hello Vue!
37 |
38 |
39 | 上面代码标记出了改变的部分,我们使用`v-if`指令来控制``标签的显示或隐藏。`v-if="message"`表示,当`message`为 truthy 时,`
`标签显示,否则隐藏。
40 |
41 | 所以,变量`message`的值为: `hello Vue!`,转换后为**真值**,所以,这个`
`标签能够显示出来。
42 |
43 | 如果我们给值取个反呢?来看看,我们使用`!`进行取反操作:
44 |
45 | ```html
46 |
47 |
48 | {{message}}
49 |
50 |
51 | ```
52 |
53 |
54 |
55 |
56 |
57 | 为了简化篇幅,没有改动的部分不做展示。 我们进行了取反操作后,果然,页面上原本的`hello Vue!`不见了。
58 |
59 | ---
60 |
61 | vue 还提供了`v-else`。当`v-if`为“假值”时,显示此元素。就跟 JavaScript 的`if/else`一样。
62 |
63 | ```html
64 |
65 |
66 |
{{message}}
67 | Message 被隐藏了
68 |
69 | ```
70 |
71 |
72 | Message 被隐藏了
73 |
74 |
75 | 通过上面例子可以看出,设置`v-else`的元素作为补充元素被显示出来。同时需要注意,`v-else`不需要提供任何值,同时只能作为`v-if`的补充。
76 |
77 | > v-else 元素必须紧跟在带 v-if 或者 v-else-if 的元素的后面,否则它将不会被识别。
78 |
79 | 如果你想要在添加另外一个条件呢? vue 又提供了`v-else-if`,用来充当`v-if`的下一个判断条件:
80 |
81 | ```html
82 |
83 |
84 |
count 小于0
85 | count 等于0
86 | count 大于0
87 |
88 |
89 |
90 |
91 |
92 |
101 | ```
102 |
103 |
106 |
107 |
108 |
109 | 这里为了更直观的展示`v-else-if`的效果,更换了一下例子中的值,把变量`message`更换为`count`,同时,分别把 count 与 0 进行了三次比较,显示比较后为 truthy 的元素。
110 |
111 | 为了展示 vue 中的**双向绑定**,在下方添加了一个`
`,并设置另外一个指令`v-model`。现在你不需要搞懂`v-model`的意思,只需感受一下效果即可,试着修改一下 `input` 中的值。
112 |
113 | ::: warning 注意
114 |
115 | 在上例中`
count 等于0
` 进行比较时使用了`==`。相信大家应该知道`==`和`===`的区别。为了保证一致性推荐大家使用`===`。本例使用`==`是因为`
`输出的值为**字符串**,所以为了更直观的展示而使用`==`进行比较。
116 |
117 | :::
118 |
119 | ## v-show
120 |
121 | `v-show`指令跟`v-if`指令一样,都是用来条件渲染元素。不过`v-show`没有`v-else`,只能单一的判断条件。而且`v-show`和`v-if`的渲染方式也有些区别,我们把上例进行如下修改:
122 |
123 | ```html
124 |
count 小于0
125 |
count 等于0
126 |
count 大于0
127 | ```
128 |
129 | 首先我们先把每个判断条件都改为`v-show`,然后把最后一个判断条件改为`count > 0`,因为`v-show`不支持`v-else`,只有`v-if`才能写`v-else`!
130 |
131 |
132 |
133 | 进行了如上修改后,大家可能发现在效果上这两个例子看起来没有什么区别。但是实际上有很大的区别,大家可以打开 F12 控制台,然后选择`Elements`选项,观察`
`标签的显示和隐藏。
134 |
135 | ```html
136 |
137 |
138 |
count1 小于0
139 | count1 等于0
140 | count1 大于0
141 |
142 |
143 |
144 | count2 小于0
145 | count2 等于0
146 | count2 大于0
147 |
148 |
149 |
150 |
151 |
152 |
162 |
163 |
164 | ```
165 |
166 |
167 |
168 | 对应的**控制台 Elements 选项卡页面显示应该如下**:
169 |
170 | ```html
171 |
172 |
count1 等于0
173 |
174 | count2 小于0
175 | count2 等于0
176 | count2 大于0
177 |
178 |
179 | ```
180 |
181 | 从上面可以看出,`v-if`指令控制的选项,为“假值”的元素并没有在页面上显示出来。而`v-show`为“假值”的元素是通过`css`属性`display: none`进行隐藏的。
182 |
183 | > 不同的是带有 v-show 的元素始终会被渲染并保留在 DOM 中。v-show 只是简单地切换元素的 CSS 属性 display。
184 |
185 | 所以总的来说就是,`v-if`如果为“真值”的情况下,**才会被渲染在页面上**,而`v-show`则是不管什么情况下,都会渲染,只不过会**通过 css 来进行隐藏**。
186 |
187 | 那么引申出来的就是性能问题。现在大家思考一下上面的例子,是使用`v-if`比较好呢,还是使用`v-show`比较好呢?答案是:使用`v-show`比较好。
188 |
189 | 因为我们在一个表单里面可以随意的输入任意数值,而只要输入的数值等于 0 或小于 0,对应的``标签就需要从新计算,`v-show`只需要简单的切换一下 css 属性就可以切换了。而如果使用`v-if`的话,就需要重新渲染元素了。
190 |
191 | > 一般来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。
192 |
193 | ## 小例子
194 |
195 | ### 根据用户信息来给用户展示不同的界面
196 |
197 | 一般来说,在一个前后端分离的项目中,前端负责接收后端传过来的数据,然后展示页面。在 Vue 中,一般都是使用`axios`来发送 http 请求。我们接收到数据后,进行渲染。(在最后会提及`axios`的使用)
198 |
199 | 假设有一个**会员中心**页面,只有当用户为会员时,才展示对应的用户信息,否则展示申请会员界面。
200 |
201 | ```html
202 |
203 |
204 |
205 |
206 |
姓名: {{userInfo.name}}
207 |
年龄: {{userInfo.age}}
208 |
性别: {{userInfo.sex === 1 ? '男' : '女'}}
209 |
210 |
211 |
212 |
213 |
你现在不是会员哦!
214 |
215 |
216 |
217 |
218 |
219 |
233 |
234 |
235 | ```
236 |
237 | 我们进行如上初始化,`data`里面的`userInfo`就是我们请求到的数据,一般我们都会使用`1或2`等 数字来表示`是或否`,比如说,`性别sex`,我们不会接收为`sex: '男'`,只会接收到`sex: 1`这样的形式。
238 |
239 | 当我们请求到数据并写好页面后,就轮到判断是否是会员了。大家想一下,在页面初始化的时候,我们获取数据,接着进行判断,最后页面进行相对应的渲染。渲染一次之后,页面不会再切换了,只会展示一次,要么你是会员,就显示会员信息,要么你不是会员,则显示申请会员。 这么分析后,大家应该知道我们是使用`v-if`还是`v-show`了吧,我们把剩下的补上。
240 |
241 | ```html
242 |
243 |
244 |
245 |
姓名: {{userInfo.name}}
246 |
年龄: {{userInfo.age}}
247 |
性别: {{userInfo.sex === 1 ? '男' : '女'}}
248 |
249 |
250 |
你现在不是会员哦!
251 |
252 |
253 |
254 |
255 | ```
256 |
257 | 我们选择使用`v-if`进行判断,因为不在需要频繁的切换状态。这里有一点需要说明一下,`v-if="userInfo.isMember"`进行了简写,实际上为`v-if="userInfo.isMember === 1"`。
258 | ::: tip 提示
259 | 在 JavaScript 中,truthy(真值)指的是在布尔值上下文中,转换后的值为真的值。所有值都是真值,除非它们被定义为 假值(即除 false、0、""、null、undefined 和 NaN 以外皆为真值)。
260 | :::
261 |
262 | 那我们换个思路来想,`if(1)`返回`true`,`if(0)`返回`false`。所以`v-if="userInfo.isMember"`会自动判断,如果为`1`就显示,为`0`就隐藏,最终表达式返回`0` ,则显示会员申请页面。
263 |
264 | 这里还有一点没有提及,就是**模板语法**可以使用表达式,因为官网有提及,比较简单,就没有细说。上例我们使用**三元表达式** 进行判断,如果为`1`说明性别为男,我们就展示为男,否则展示为女。
265 |
266 | 目前为止,我们已经学会了根据用户信息,来判断展示不同的界面。 我们在拓展一下,展示一下 Vue 自动更新数据的魅力。在上例的基础上,给申请按钮添加一个真实的点击事件:
267 |
268 | ```html
269 |
270 | ```
271 |
272 | 我们把**申请按钮**进行如下修改,现在你看不懂没关系,下面我们会介绍。我们只是简单的设置一个 click 事件,点击后把`isMember`的值设置为 1。
273 |
274 | ::: warning 注意
275 | 这里为了展示效果,只是在`data`里面把值改变了,在真实的项目是需要发送`http`请求!
276 | :::
277 |
278 |
279 |
280 | 现在,点击 `点击申请` 按钮后, 页面会马上展示 会员的界面,同时把申请界面进行隐藏。这里面的工作全是 Vue 在“背后”进行的,我们只需要改变一下对应的值即可。
281 |
282 | ---
283 |
284 | 返回上一页
285 |
--------------------------------------------------------------------------------
/docs/advanced/components/emit.md:
--------------------------------------------------------------------------------
1 | # 子向父传值 emit
2 |
3 | [[toc]]
4 |
5 | ## 介绍
6 |
7 | 前面我们讲解了怎么封装一个单`.vue`文件组件,同时通过*父向子传值*来显示不同的内容和颜色。只不过目前这个`Button`按钮没有任何的作用,我们没有为它设置任何`v-on`监听器。所以,为了要真正的使用它,我们需要给它绑定一个事件。
8 |
9 | 比如说,一个登录页面,我们使用了这个组件,并且把它作为登录按钮。那么,用户点击了这个登录按钮(也就是这个组件)后,我们需要发送 http 请求,这时我们需要使用`v-on`监听`click`事件。
10 |
11 | 最“无脑”的写法,就是写在这个组件里,大概是这样:
12 |
13 | ```html
14 |
15 |
16 |
23 | ```
24 |
25 | 虽然以上写法能够满足我们的需求,但是如果别的页面引入了这个组件呢?它可不一定同样是登录按钮,可能是*取消*按钮。所以,我们在这个组件内写**监听事件**是不被允许的。
26 |
27 | 我们需要的是一个**通用事件**,每个页面都能触发这个`click`事件,并且触发不同的函数,传递不同的值。这时候`$emit`就派上了用场。
28 |
29 | ## `$emit`
30 |
31 | `$emit`用来触发当前实例上事件,附带参数都会传回给监听器回调。也就是说,通过`$emit`,我们能够在子组件内向父组件传递一个**事件**,这就是**子向父传值**。
32 |
33 | 下面我们来看一下如何使用它:
34 |
35 | ```html
36 |
37 | ```
38 |
39 | 跟以前的代码相比,我们多了一行`@click="$emit('btn-handler')"`。意思是:我们通过`v-on`监听 button 的`click`,使用`$emit`触发传递回一个`btn-handler`事件。
40 |
41 | ::: tip 提示
42 | 有关`$emit`传递事件名的命名规范,请参考[官方文档](https://cn.vuejs.org/v2/guide/components-custom-events.html#%E4%BA%8B%E4%BB%B6%E5%90%8D)
43 | :::
44 |
45 | 目前为止,我们在子组件内,通过`$emit`向使用它的父组件传递回一个`btn-handler`事件。注意,我这里说的是`btn-handler`**事件**,那么我们只需要在父组件里,监听这个事件就好了啊。那么监听事件我们知道,使用`v-on`就可以了。所以,我们在引用这个组件的地方,加上监听器就可以了:
46 |
47 | ```html
48 |
49 | ```
50 |
51 | 可以看到,我在引入组件的时候,添加了一个不完整的代码`@btn-handler="xxx"`。这表示监听`btn-handler`事件,并触发右侧的表达式或者函数(这里为 xxx)。因为我们需要的是登录功能,所以,我们可以写成这样:
52 |
53 | ```html
54 |
55 |
56 |
63 | ```
64 |
65 |
66 |
67 | 现在你打开控制台,然后点击上面蓝色按钮,发现能够打印出`login~`,说明我们已经监听成功啦。这样,我们就完成了从子组件向父组件传递事件的过程。
68 |
69 | ## 传递参数
70 |
71 | 除了能够传递一个事件外,同时还能传递一个值,官网里面的解释已经比较简单明了了,所以推荐大家直接去看官方文档,[点击这里](https://cn.vuejs.org/v2/guide/components.html#%E4%BD%BF%E7%94%A8%E4%BA%8B%E4%BB%B6%E6%8A%9B%E5%87%BA%E4%B8%80%E4%B8%AA%E5%80%BC)查看。
72 |
73 | 如果你看了后,对组件之间的互相传值还是有些困惑,没关系。下面我们通过一个例子来重新温习一下`Button`组件的使用。
74 |
75 | ## 小例子
76 |
77 | ### 点击按钮变色
78 |
79 | 还是基于我们的`Button`组件,这次我们来点不一样的:点击按钮变换不同的颜色。
80 |
81 | 首先是注册组件:
82 |
83 | ```html
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
108 |
109 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
158 |
159 |
160 | ```
161 |
162 |
163 |
164 | 我决定了,不要在丑下去了。所以,我改了些样式,并且把颜色更换了一下,这样美观多了是吧。
165 |
166 | 首先注册组件`Button.vue`,并使用`props`接收两个值,分别是`color`和`content`,代表颜色和按钮的内容。并初始化了一些 class 样式,这样我们才能通过`v-bind:class`来变换不同的颜色。
167 |
168 | 然后是`index.vue`,这里首先通过`import`引入`Button`组件,接着在`components`里面注册组件,最后在页面上使用。这里传递了两个值,分别为`color: 'primary'`和`content: 'change color'`。
169 |
170 | 接下来就是改变按钮颜色的逻辑了,我们需要通过`Button.vue`向`index.vue`传递事件和值,然后去改变颜色。首先定义一个颜色的列表,然后获取随机的 index,最后把随机的颜色值传回过去就可以了。
171 |
172 | ```html
173 |
174 |
175 |
176 |
192 | ```
193 |
194 | 在`Button.vue`组件内,我们定义了一个函数,这次不再直接`$emit`传递了,而是把它写在函数内部。在函数内部,我们一一进行分析:
195 |
196 | 1. 定义了一个颜色列表,所有的颜色都在这里
197 | 2. 获取到一个随机数,并赋值给`index`
198 | 3. 定义颜色列表的长度,并赋值给`l`
199 | 4. 这一步,是获取一个在颜色列表内的随机 index。其中,`l - 1`代表限定范围为 3,`*index`就是获取最后的随机数。最后的随机数应该为`0/1/2/3`
200 | 5. 为了不出错误,保险起见,我们在做一个处理,如果超过`0-3`的范围,我们就把它改变成边界(就是 0 或 3)。
201 | 6. 最后把获取到的**颜色值**传递获取。`value[index]`为颜色值。`$emit`的**第二个参数**为传递回去的参数!!!
202 |
203 | 接着我们在接收值:
204 |
205 | ```js
206 |
207 |
208 |
213 |
214 |
215 |
216 |
231 |
232 |
233 | ```
234 |
235 |
236 |
237 | `event` 就是子组件传过来的值,我们使用`{1}`处,就可以完成值的改变。
238 |
239 | 有人可能会觉得,为什么要这么麻烦的传来传去呢?为什么不直接在子组件内改变颜色的值呢?其实我们可以试一下,在子组件内,不使用`$emit`传递事件和值,直接修改通过`props`传过来的`color`的颜色。也就是`this.color = value[index]`。
240 |
241 | 如果你试了,会发现,虽然功能正常使用,但是控制台会报错。也就是说,实际上我们不能直接在子组件里面修改父组件传过来的值,因为这样并不容易区分改变来源。
242 |
243 | 所以,想要**便利**的使用“双向绑定”,Vue 提供了`update:xxx`模式,[点击这里](https://cn.vuejs.org/v2/guide/components-custom-events.html#sync-%E4%BF%AE%E9%A5%B0%E7%AC%A6)查看官方文档具体说明。其中,xxx 代表通过`props`传过来值的名字。`update:propsName`。
244 |
245 | 所以,我们要修改的是`color`的值
246 |
247 | - 第一步是在`Button`组件里,修改为`this.$emit('update:color', value[index])`。表示我们要直接修改`color`的值,值为`value[index]`。
248 | - 第二步是在使用组件的文件里(`index.vue`)把`@btn-handler="changeColor"`去掉,因为现在不需要监听事件了,修改`:color="color"` 为`:color.sync="color"`。
249 |
250 | 修改后的为``
251 |
252 | 进行以上修改后,点击按钮后还是能够正常的改变颜色。
253 |
254 |
255 |
256 | 不知道有没有人注意到,点击按钮的时候,有时候两次按钮的颜色是一样的,**为了追求完美**,我们在完善一下这个功能!
257 |
258 | 总的来说就是,当返回的颜色值和当前的颜色值一样时,在从新求一次值,直到不一样,并返回。
259 |
260 | 我们以前写的函数逻辑如下:
261 |
262 | ```js
263 | methods: {
264 | btnClick() {
265 | let value = ['success', 'error', 'primary', 'warning'] // {1}
266 | let index = Math.random() // {2}
267 | let l = value.length // {3}
268 | index = Math.round((l - 1 ) * index) // {4}
269 |
270 | // {5}
271 | if(index > 3) index = 3
272 | if(index < 0) index = 0
273 | // console.log('index', index)
274 | this.$emit('btn-handler', value[index]) // {6}
275 | }
276 | }
277 | ```
278 |
279 | 现在我们要继续在`btnClick`函数里面添加代码逻辑了,但是有没有发现,在此函数里面写代码会越写越多,最终会各种逻辑都有,变得非常难理解。所以,我们需要拆分函数,我准备把获取颜色的逻辑拆分成一个`getColor`函数
280 |
281 | ```js
282 | methods: {
283 | btnClick() {
284 | let result = this.getColor()
285 | console.log('result', result)
286 | this.$emit('update:color', result)
287 | },
288 | getColor() {
289 | let value = ['success', 'error', 'primary', 'warning']
290 | let index = Math.random()
291 | let l = value.length
292 | let color = this.color // {1}
293 | index = Math.round((l - 1 ) * index)
294 |
295 | // {2}
296 | if(value[index] === color) {
297 | return this.getColor()
298 | }
299 |
300 | if(index > 3) index = 3
301 | if(index < 0) index = 0
302 | return value[index]
303 | }
304 | }
305 | ```
306 |
307 |
308 |
309 | 我们在`{1}`处,储存了当前颜色。在`{2}`处,如果当前的颜色和随机颜色一样,则从新调用这个函数(这里一定要加上`return`,要不返回的颜色为第一次返回的颜色)。
310 |
311 |
312 |
313 | 除了从新调用函数外,我们还可以使用`while`循环来从新写一下这个函数
314 |
315 | ```js
316 | methods: {
317 | btnClick() {
318 | let result = this.getColor()
319 | console.log('result', result)
320 | this.$emit('update:color', result)
321 | },
322 | getColor() {
323 | let value = ['success', 'error', 'primary', 'warning']
324 | let index = Math.random()
325 | let l = value.length
326 | let color = this.color
327 | index = Math.round((l - 1 ) * index)
328 |
329 | // {1}
330 | do {
331 | index = Math.round((l - 1 ) * Math.random())
332 | }
333 | while(color === value[index])
334 |
335 | if(index > 3) index = 3
336 | if(index < 0) index = 0
337 | return value[index]
338 | }
339 | }
340 | ```
341 |
342 | 在`{1}`处,添加了`do/while`逻辑,首先执行一下从新获取随机数。如果不相同,则返回值;如果相同,则继续获取随机数,直到不相同退出 while 循环。
343 |
344 | 经过我个人测试,第二种方法使用 while 循环比第一种直接调用函数要快一些。
345 |
--------------------------------------------------------------------------------
/docs/basics/instructs/list.md:
--------------------------------------------------------------------------------
1 | # 列表渲染
2 |
3 | [[toc]]
4 |
5 | ## 介绍
6 |
7 | 如果要渲染**一组数据**,那么在 Vue 中可以使用 v-for 指令。
8 |
9 | ## 渲染数组
10 |
11 | [v-for](https://cn.vuejs.org/v2/guide/list.html)指令是用来遍历渲染一组数据的。以` in `的形式,`child`为数组的每一项子元素,也就是要被遍历的每一项;`parent`为数组本身。如`item in list`,那么,`list`就是一个数组,而`item`就是`list`数组里面的每一个子元素。`item` = `list[0]、list[1]、list[2]...list[n]`。下面看个例子:
12 |
13 | ```html
14 |
15 |
16 |
17 | -
18 | {{item.name}} --- {{item.age}}
19 |
20 |
21 |
22 |
23 |
24 |
46 |
47 |
48 | ```
49 |
50 |
51 |
52 | - tim --- 10
53 | - sam --- 15
54 | - colin --- 20
55 |
56 |
57 |
58 | 同时,`v-for`还提供了`index`属性,即当前遍历项的索引:
59 |
60 | ```html
61 |
62 | -
63 | {{item.name}} --- {{item.age}}
64 |
65 |
66 | ```
67 |
68 | `index`跟`item`一起,放在一个括号里面,用`,`分隔。同时,我们可以把`key`属性换为`index`。 那么这个`key`是干什么用的呢?[官网文档](https://cn.vuejs.org/v2/api/#key)里面指出,`key`是在 Vue 的 dom 算法的时候,用来对比新旧`vnodes`的。因为 vue 采用虚拟 dom,当页面发生改变时,不会把所有的 html 元素全都改变,只会改变 发生改动的部分。
69 | 同时还能够看到在 key 的前面有一个`:`,这个是`v-bind:`指令的简写形式。稍后会介绍`v-bind`。
70 |
71 | 继续上面的例子,假设我们有一组数据,存放在`data`里面的`list`中,在页面上,我们通过`ul`的形式展现出来,因为我们要得到的是一个展现用户名字和年龄的列表,所以`v-for`指令要添加在``标签上,这样我们能够得到一个``和一堆`- `,这也是我们需要的页面布局,如果我们把`v-for`指令放在`
`上面呢?我们来看看:
72 |
73 | ```html
74 |
75 | - {{item.name}} --- {{item.age}}
76 |
77 | ```
78 |
79 |
82 |
83 | 比较明显的是每个`- `之间的缝隙变大了,而在看页面结构能看出我们最终渲染出来的是一堆`
`标签和一个`- `,这说明`v-for`放在哪里,那么就遍历哪里。而我们需要的不过是一个展现列表而已,所以,第二种方式是错误的,我们**应该把`v-for`指令放在`
- `标签上**!
84 |
85 | ## 渲染对象
86 |
87 | 还记得上一节中的小例子吗,这里把`userInfo`的数据拿过来,然后使用`v-for`渲染出来:
88 |
89 | ```html
90 |
91 |
92 |
93 |
94 |
95 | {{name}}: {{value}}
96 |
97 |
98 |
99 |
100 |
你现在不是会员哦!
101 |
102 |
103 |
104 |
105 |
106 |
120 |
121 |
122 | ```
123 |
124 | 这里把渲染方式进行了修改,不是在手动写出每一项的内容,而是通过`v-for`进行了渲染。在`v-for`里面,我使用了`value, name, index`,这三个属性分别代表:
125 |
126 | - value 当前项的值,对于`userInfo`来说,就是`tim、10、1 和 0`
127 | - name 当前项的 key,对于`userInfo`来说,就是`name、age、sex 和 isMember`
128 | - index 当前项的索引,也就是`0、1、2 和 3`
129 |
130 | 通过以上分析,我们应该知道,我们以`name: value`的形式写出来,那么结果应该是这样的:
131 |
132 |
133 |
name: tim
age: 10
sex: 1
isMember: 1
134 |
135 |
136 | 以上就是关于`v-for`的全部内容了,可能有些人还对`:key`属性有些不解,没关系,我们将在下一小节讲解`v-bind:`的使用。不过在此之前,我们还是要通过几个例子来加深一下印象。
137 |
138 | ## 小例子
139 |
140 | ### 展示表格数据
141 |
142 | ```js
143 | ;[
144 | {
145 | date: '2016-05-02',
146 | name: '王小虎',
147 | address: '上海市普陀区金沙江路 1518 弄'
148 | },
149 | {
150 | date: '2016-05-04',
151 | name: '王小虎',
152 | address: '上海市普陀区金沙江路 1517 弄'
153 | },
154 | {
155 | date: '2016-05-01',
156 | name: '王小虎',
157 | address: '上海市普陀区金沙江路 1519 弄'
158 | },
159 | {
160 | date: '2016-05-03',
161 | name: '王小虎',
162 | address: '上海市普陀区金沙江路 1516 弄'
163 | }
164 | ]
165 | ```
166 |
167 | 现在假设我们有一组数据,然后使用`table`进行展示,我们当然不可能每一项都手动写出来,所以这时候可以使用`v-for`来渲染:
168 |
169 | ```html
170 |
171 |
172 |
173 |
174 |
175 | | data |
176 | name |
177 | address |
178 |
179 |
180 |
181 |
182 | | {{item.date}} |
183 | {{item.name}} |
184 | {{item.address}} |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
221 |
222 |
223 | ```
224 |
225 | 在接下来的大部分例子中,为了让大家关注业务逻辑和代码,我将会省略 css 样式。
226 |
227 | 在上面的代码中,我们使用`v-for`来遍历渲染一组数据 `list`,本身没有什么难点,但在实际项目中,我们需要在`data`里面定义`list`,然后在请求到数据后,把数据赋值给`list`,之后页面就会自动完成渲染工作。
228 |
229 | 为了给大家更真实的“游戏”体验,我给大家`Mock`了一组数据,并创建了一个使用`get`请求访问的接口,地址为:`https://easy-mock.com/mock/5e1980617f109b0caa4d2d5e/listExample`
230 |
231 | 那么,使用真实的接口后,代码为:
232 |
233 | ```html
234 |
235 |
236 |
237 |
238 |
239 | | data |
240 | name |
241 | address |
242 |
243 |
244 |
245 |
246 | | {{ item.date }} |
247 | {{ item.name }} |
248 | {{ item.address }} |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
283 |
284 |
285 | ```
286 |
287 | ::: warning 注意
288 |
289 | 上例使用了用于 Http 请求的`axios`。如果要使用的话,需要在`index.html`的``标签中放入如下 cdn:``。真实的项目里面,应该通过`npm install axios -S`的方式安装`axios`的 npm 包,然后在项目引用。
290 | :::
291 |
292 | 关于 axios 在本文的最后会进行讲解,这里不多做介绍,大家只需要关注以下几点:
293 |
294 | 1. 如果要使用`list`,则需要在`data`里面初始化一个`list: []`。`list: []`代表:初始化一个 list 变量,并赋值为一个空数组。
295 | 2. 在 js 代码里面访问`data`**里面的变量**,比如`list`,我们需要以`this.xxx`的形式,在本例中是`this.list`(在某些情况下可能使用 var \_this = this;)。
296 | 3. `res.data.data.list`就是我们通过接口获取到真实的数据。(可以通过 F12 控制台查看数据)
297 | 4. 我们通过`this.list` = `res.data.data.list` 把获取到的数据**赋值**给本地的`list`,然后页面就会自动渲染。
298 | 5. 如果 A 页面有 list 变量,B 页面也有 list 变量,那么会不会冲突呢?答案是**不会**,因为我们使用的是`data(){}`函数,上面也讲过,每个组件之间不会互相影响。
299 |
300 | 上面的代码使用的 es6 语法,那么使用 es5 语法的代码如下:
301 |
302 | ```html
303 |
335 | ```
336 |
337 | 因为使用 es5 语法,出现了 this 指向问题。所以,我们需要在函数开始位置把 this 储存起来,然后在函数内部依然能获取到 vue 实例。 也就是说,在`{1}`的时候,this 指向 vue 实例,而在`{2}`的时候,`this`指向`undefined`。
338 |
339 | 至于为什么使用 es6 语法的**箭头函数**能够通过`this`访问 vue 实例,而是用`es5`传统的函数方式不能够访问,大家可以在网上自行查找一下关于:**箭头函数 this 作用域** 的文章。
340 |
341 | ---
342 |
343 | 返回上一页
344 |
--------------------------------------------------------------------------------
/docs/basics/lifecycle.md:
--------------------------------------------------------------------------------
1 | # 生命周期 lifecycle
2 |
3 | [[toc]]
4 |
5 | 理解生命周期函数对于我们来说至关重要,但是也不需要你马上理解其所有内容,官方文档里有一张图片,展示了生命周期的过程。
6 |
7 | > 你不需要立马弄明白所有的东西,不过随着你的不断学习和使用,它的参考价值会越来越高。
8 |
9 |
10 |
11 | ## 介绍
12 |
13 | 生命周期,就是 Vue 实例从创建到销毁经过的一系列过程。不同的阶段可以访问的数据有很大区别,接下来的分析参考官方文档。
14 |
15 | ::: tip 提示
16 |
17 | 下面会使用 `debugger;` 语句进行断点分析,如果不了解 `debugger` 的可以查看一下[MDN](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/debugger)。
18 |
19 | :::
20 |
21 | ## beforeCreate
22 |
23 | > 在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。
24 |
25 | 在使用 `new Vue()`创建 Vue 实例后,进入初始化阶段。此时,不能访问到 data 里面的数据,也不能访问到 watch 和 methods 里面的函数。
26 |
27 | 在进行测试之前,先初始化如下模板:
28 |
29 | ```html
30 |
31 |
34 |
35 |
36 |
63 |
64 |
65 | ```
66 |
67 | 项目启动后,打开控制台可以看到如下信息
68 |
69 |
70 |
71 |
72 | 以上信息说明:
73 |
74 | - 控制台打印 `进入 beforeCreated`。表示首先开始进入 `beforeCreated` 生命周期函数。
75 | - 接着打印 `data` 里面的 `message`,发现值为 `undefined`。说明在此生命周期函数内,并不能访问到 `data` 里面的值。
76 | - 接着打印 body。虽然能打印出来,但是发现`#app`的 div 里面并没有任何内容,说明 **虚拟 dom** 还没有渲染出来。
77 | - 接着调用 `test` 函数。发现控制台报错了,提示 `test` 不是一个函数,说明在此不能访问`methods`里面的任何函数。
78 |
79 | 通过以上分析,此生命周期为初始化阶段,数据、函数、watch 等事件不能访问到。**轻度使用**。
80 |
81 | ## created
82 |
83 | > 在实例创建完成后被立即调用。在这一步,实例已完成以下的配置:数据观测 (data observer)、属性、方法的运算和 watch/event 事件回调。然而,挂载阶段还没开始,\$el 属性目前尚不可用。
84 |
85 | 在**实例创建完成后**被立即调用,而且实例已经完成了 数据观测,属性、方法的运算、watch 和事件的回调,说明我们已经能够访问`data`里面的数据和`methods`里面的方法了。加入以下生命周期函数进行测试
86 |
87 | ```js
88 | beforeCreate() {
89 | try {
90 | console.log('进入 beforeCreated')
91 | console.log(this.message, 'message')
92 | let body = document.querySelector('body')
93 | console.log(body, 'body')
94 | this.test()
95 | debugger
96 | } catch (e) {
97 | console.error(e)
98 | debugger
99 | }
100 | },
101 | // 新添加 !!!
102 | created() {
103 | try {
104 | console.log('进入 created')
105 | console.log(this.message, 'message')
106 | let body = document.querySelector('body')
107 | console.log(body, 'body')
108 | this.test()
109 | debugger
110 | } catch (e) {
111 | console.error(e)
112 | debugger
113 | }
114 | },
115 |
116 | ```
117 |
118 | ::: tip 提示
119 |
120 | 你可能已经看到,生命周期**函数**是一个函数,以`xxx(){}`的方式声明,以`,`进行分隔,跟`methods: {}`等方法不一样,这里需要注意。
121 |
122 | :::
123 |
124 |
125 |
126 |
127 | 以上信息说明:
128 |
129 | - 打印 `created` 表示进入 `created` 生命周期
130 | - 打印 `hello Vue` 表示在此我们**能够访问到 `data` 里面的值**!
131 | - 打印 body,跟上面一样,说明此时 dom 还未挂载完成,这里**并不能对 dom 进行任何操作**!
132 | - 最后调用 `test` 函数,控制台打印出了 `test function`,说明在此我们也**能够访问 `methods` 里面的函数了**!
133 |
134 | 此时我们已经能够访问 `data` 里面的函数,`methods` 里面的方法。还可以使用 `watch、props 和 computed` 等方法。**重度使用,需要重点关注**!
135 |
136 | ## beforeMount
137 |
138 | > 在挂载开始之前被调用,相关的 render 函数首次被调用。
139 |
140 | 在挂载开始之前被调用,也就表示还不能访问 dom,因为还没有开始挂载。
141 |
142 |
143 |
144 |
145 | 从图中可以看出,此时打印出的内容跟`created`里面打印出的内容差不多。因此也可以访问 `data、methods 和 watch` 等数据和方法。**轻度使用**。
146 |
147 | ## mounted
148 |
149 | > 实例被挂载后调用,这时 el 被新创建的 vm.$el 替换了。 如果根实例挂载到了一个文档内的元素上,当mounted被调用时vm.$el 也在文档内。
150 |
151 | 实例被挂载后调用,新创建的 Vue 实例的 el 替换对应的 dom 元素。这说明虚拟 dom 已经挂载完成,也就表示我们能够访问 dom 了。
152 |
153 |
154 |
155 |
156 | 此时能够看到,我们能展开`#app`里面的元素了,并看到`p`标签里面的内容了。说明在`mounted`里面,dom 已经加载完成,我们可以在此对 dom 进行操作。**重度使用,需要重点关注**!
157 |
158 | ## beforeUpdate
159 |
160 | > 数据更新时调用,发生在虚拟 DOM 打补丁之前。这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。
161 |
162 | - 数据更新时调用
163 |
164 | 说明**数据已经更新了**。
165 |
166 | - 发生在虚拟 dom 打补丁之前
167 |
168 | 也就是说,在 dom 修改之前,页面还没有更新。这里解释一下为什么叫*打补丁*,因为 Vue 采用虚拟 dom,并且采用优化算法,对比新旧`Vnode`(虚拟 dom),只修改 **改变的部分**。所以,如果我们只更改了部分值,那么只会更新页面的一部分。这也是 使用 Vue 构建的页面 显得特别快的原因。
169 |
170 | 还是通过断点调试,为了更好的观察 dom 发生的变化,设置了一个`test`方法,用来改变`message`的值。在`
315 | ```
316 |
317 | `@click="toggleComponent = !toggleComponent"` 表示把`toggleComponent`这个变量取反并赋值给他自己。为`true`取反后就为`false`。最后的效果就是点击后可以来回切换显示或隐藏。
318 |
319 | 在进入页面后,在`mounted`里面调用定时器函数,发现控制台开始打印`1`。同时页面如下,`component-a`组件存在。
320 |
321 |
322 |
323 |
324 |
325 |
326 | 当我们点击`toggle`按钮后,控制台打印如下:
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 | 发现虽然已经调用`destroyed`了,但是控制台还是能够打印`1`,说明定时器并没有被销毁。所以,需要我们自己手动清除。
335 |
336 | 清除定时器,大家都知道,如果是`setTimeout`,我们使用`clearTimeout`;如果是`setInterval`,我们使用`clearInterval`。所以,我们应该使用`clearInterval()`,并且加在`beforeDestroyed`或者`destroyed`里面。但是还有一个问题,就是清除哪个定时器,所以我们还需要把这个定时器赋值给一个变量:
337 |
338 | ```js
339 | interval() {
340 | let timer = setInterval(() => {
341 | console.log('1')
342 | }, 1000)
343 | }
344 |
345 | destroyed() {
346 | clearInterval(timer)
347 | console.log('component destroyed')
348 | }
349 | ```
350 |
351 | 这样可以吗?你可以试一下。因为函数作用域的问题,我们在**外部是无法访问到函数内部的变量的**。所以,在`destroyed`里面是访问不到 timer 的。聪明的你一定想到了,我们可以在`data`里面初始化一个变量`timer`,然后在定时器里面进行赋值,最后在`destroyed`里面进行销毁。让我们试一下:
352 |
353 | ```js
354 | data () {
355 | return {
356 | message: 'hello Vue',
357 | timer: null
358 | }
359 | }
360 |
361 | interval() {
362 | this.timer = setInterval(() => {
363 | console.log('1')
364 | }, 1000)
365 | }
366 |
367 |
368 | destroyed() {
369 | clearInterval(this.timer)
370 | console.log('component destroyed')
371 | }
372 | ```
373 |
374 | 访问`data`里面的变量,我们需要使用`this.xxx`的方式,所以我们通过`this.timer`进行赋值和销毁。现在重新加载页面,并点击`toggle`,控制台不会再打印`1`了。说明成功销毁定时器。
375 |
376 | 官方文档里面还有关于`destroyed`的使用例子,[点击这里](https://cn.vuejs.org/v2/cookbook/avoiding-memory-leaks.html)访问。
377 | **轻度使用**。
378 |
--------------------------------------------------------------------------------
/docs/basics/computed.md:
--------------------------------------------------------------------------------
1 | # 计算属性 computed
2 |
3 | [[toc]]
4 |
5 | ## 介绍
6 |
7 | computed 是 Vue 提供用来处理复杂逻辑,可以像绑定普通属性一样绑定计算属性,并且计算属性的值会被缓存,除非依赖的值更新。
8 |
9 | 首先解释一下`像绑定普通属性一样绑定计算属性`:绑定普通属性的方式为在`data`里面进行绑定
10 |
11 | ```js
12 | {{message}}
13 |
14 | data () {
15 | return {
16 | message: 'hello Vue'
17 | }
18 | }
19 |
20 | ```
21 |
22 | 绑定**计算属性**为:
23 |
24 | ```js
25 |
26 | {{reverseMessage}}
27 |
28 | computed: {
29 | // es6
30 | reverseMessage() {
31 | return this.message.split('').reverse().join('')
32 | }
33 |
34 | // es5
35 | reverseMessage: () {
36 | return this.message.split('').reverse().join('')
37 | }
38 | }
39 |
40 | ```
41 |
42 | 可以看到,使用方式是相同的,都是使用模板语法`{{}}`。唯一不同的是,计算属性需要在`computed:{}`里面声明,并且是一个函数。
43 |
44 | 同时注意,计算属性是一个函数,想要函数返回一个值需要使用`return`关键字,如果不使用`return`,会发现*这个计算属性*不会返回任何值,需要注意。
45 |
46 | 计算属性的值也会被放入 Vue 实例里面。所以,你依然可以通过`this.reverseMessage`来访问对应的计算属性。
47 |
48 | > 计算属性默认只有 getter ,不过在需要时你也可以提供一个 setter
49 |
50 | 计算属性默认只有 getter,意思是我们访问计算属性的时候,默认只返回值,不可以设置值。
51 |
52 | 不过在需要时你也可以提供一个 setter,意思是可以设置一个 setter,用来改变计算属性的值。方式为:
53 |
54 | ```js
55 | computed: {
56 | // es6
57 | reverseMessage: {
58 | get() {
59 | return this.message.split('').reverse().join('')
60 | },
61 | set(value) {
62 | this.message = this.message + value
63 | }
64 | }
65 |
66 | // es5
67 | reverseMessage: {
68 | get: function() {
69 | return this.message.split('').reverse().join('')
70 | },
71 | set: function(value) {
72 | this.message = this.message + value
73 | }
74 | }
75 | }
76 | ```
77 |
78 | 可以看到,`reverseMessage`改变了声明方式,使用了`get(){}`和`set(){}`。
79 |
80 | - `get`就是返回值,跟声明的时候一样。
81 | - `set`是设置值,接收一个`value`,`value`为我们设置的值。比如说,`this.reverseMessage = '哈哈哈'`,那么`value`就是`哈哈哈`。
82 |
83 | ## 小例子
84 |
85 | 计算属性应用来说相对比较多,下面我们分别看几个例子来理解一下计算属性的应用。一般情况下,使用计算属性的`getter`即可。
86 |
87 | ### 商品排序
88 |
89 | 大家看购物网站的时候,一般都会有排序的功能,比如说,按照价格排序,按照销量排序等。下面我们就来实现一下这个功能。同时为了真实的体验,我提供了一个简单的商品数据列表,接口地址为:`https://easy-mock.com/mock/5e1aa4ff7f109b0caa4d2e26/learnvue/commodity`(接口为 mock 数据(模拟数据),意味着每次请求的数据结果都不一样)。
90 |
91 | ```html
92 |
93 |
94 |
95 |
96 |
97 |
![]()
98 |
99 |
100 |
{{item.title}}
101 |
102 |
103 |
销量: {{item.sales}}
104 |
价格: {{item.price}}
105 |
106 |
107 |
108 |
109 |
110 |
111 |
140 |
141 |
157 | ```
158 |
159 |
160 |
161 | 如果你复制上去后,发现页面渲染出来数据了,很棒是不是,这就是真实的接口+数据渲染方式。除了没有完善的错误处理和样式等:hugs:,不过暂时先不考虑。
162 |
163 | 为了大家更好的理解计算属性,我准备把以上内容复习一下。
164 |
165 | - 获取数据
166 |
167 | 这里使用`axios`获取数据(axios 最后会讲怎么使用),并在`created`里面调用(`created/mounted`都可以)。可以看到函数里面使用了`let data = res.data.data`,因为嵌套的层级比较深,为了下面代码更好的使用,所以声明了一个`data`变量。如果你认为没必要的话,请看:
168 |
169 | ```js
170 | // 不使用变量
171 | this.list = res.data.data.list
172 | if (!res.data.data.list.length) return
173 | res.data.data.xxx // 操作其他值
174 |
175 | // 使用变量
176 | let data = res.data.data
177 |
178 | this.list = data.list
179 | if (!data.list) return
180 | data.xxx // 操作其他值
181 | ```
182 |
183 | - 渲染操作
184 |
185 | `list`获取到对应的值后,`v-for`会自动进行渲染,`(item, index)` ,`item`代表被遍历的数组的每一项,即`list[0]、list[1]...list[n]`。`index`为当前遍历的索引值,也就是`0、1 ... n`。`:key=index`为`v-bind:key=index`,就是把当前的索引项的值绑定到`key`上。
186 |
187 | 此时,通过`item.xxx`就能获取到对应的数据了,那么我们通过`{{}}`模板语法渲染数据即可。
188 |
189 | 下面开始写具体的排序逻辑,我们选择按照**价格排序**。具体的逻辑就是使用计算属性对每个商品的价格,使用`sort`进行排序,排序好了之后 `v-for` 会自动渲染。
190 |
191 | 首先我们先声明一个计算属性,叫`new_priceList`:
192 |
193 | ```js
194 | computed: {
195 | new_priceList() {
196 | return this.list
197 | }
198 | }
199 |
200 | ```
201 |
202 | 首先声明了一个计算属性`new_priceList`,不过目前我只写了返回`list`。那么意味着我们把`v-for`的`list`替换成`new_priceList`,页面还是正常渲染,不会有任何问题和报错。那么我们来试一下:
203 |
204 | ```html
205 |
206 | ```
207 |
208 | 进行替换后,发现页面还是能够正常渲染的,这是我们使用计算属性的第一步。**学会这一步**,因为这可能是你以后经常使用计算属性的第一步。
209 |
210 | ::: tip 提示
211 |
212 | 记住,计算属性同样是响应式的,也就是说,如果`list`里面的内容变化了([官网文档](https://cn.vuejs.org/v2/guide/list.html#%E6%95%B0%E7%BB%84%E6%9B%B4%E6%96%B0%E6%A3%80%E6%B5%8B)有明确指出什么操作是响应式的),那么`new_priceList`里面的值同样会变化,如果`list`不变,那么`new_priceList`会被缓存起来,每次访问会直接返回。
213 |
214 | :::
215 |
216 | 接下来就是排序的逻辑了,javaScript 提供了`sort`方法,用来排序。不过,大家可能知道,这个方法并不完美。所以我们需要改进一下:
217 |
218 | ```js
219 | new_priceList() {
220 | return this.list.sort((a, b) => {
221 | return a.price - b.price
222 | })
223 | }
224 | ```
225 |
226 | `sort`方法可以接收一个函数,如果返回值为负数,说明第一个值应该在第二个值前面,即第一个值小于第二个值。所以,返回后的数组为升序。
227 |
228 |
229 |
230 | 可以看到,现在的新数组已经是排序后的数组了。不过,我们需要的智能排序,需要我们手动点击升序或降序。那么就需要一个**值**来控制一下,到底是升序还是降序。
231 |
232 | ```html
233 |
234 |
235 |
236 |
237 | ```
238 |
239 | ```js
240 | data () {
241 | return {
242 | list: [],
243 | status: 0 // 默认为0 即默认排序 1 为升序 2 为降序
244 | }
245 | }
246 |
247 | computed: {
248 | new_priceList() {
249 | if (!this.status) return this.list
250 | return this.list.sort((a, b) => {
251 | return this.status === 1 ? a.price - b.price : b.price - a.price
252 | })
253 | }
254 | },
255 |
256 | ```
257 |
258 |
259 |
260 | 现在我们可以点击升序或降序按钮,可以控制商品的排序了。那么我们来解释一下最后一步的操作:
261 |
262 | - 添加控制升序或降序的值
263 |
264 | 本例为`status`,为 0 说明默认排序,为 1 说明为升序,为 2 说明为降序。(这里的 0、1 和 2 是由自己定义的,你可以定义任何你想定义的值)
265 |
266 | - 添加按钮来改变`status`的状态
267 | 使用`v-on:click`来改变状态,升序按钮为`status = 1`,降序按钮为`status = 2`
268 |
269 | - 计算属性逻辑
270 |
271 | - `if (!this.status) return this.list`
272 |
273 | 这里表示,如果为 0 的情况下,就返回默认的`list`数组。`!this.status`表示取 0 的反值。
274 |
275 | - `return this.status === 1 ? a.price - b.price : b.price - a.price`
276 |
277 | 这里使用三元表达式对当前的状态进行求值。如果为 1,则说明为**升序**,那么就返回`a.price - b.price`;如果为其他的值,就说明为**降序**,那么就返回`b.price - a.price`。
278 |
279 | **注意嗷**,`status`还有为 0 的情况,所以我们在进入函数之前就判断了,如果为 0,那么就返回默认的`list`数组,而不会走到三元表达式这里。
280 |
281 | 到这里基本就讲完了一个排序小例子,看完后,记得自己重新实践一下。如果还没有理解到位,那么请看下面的另一个小例子。
282 |
283 | ### 商品搜索
284 |
285 | 一般来说,搜索是通过 http 请求,来跟后台进行交互,然后只渲染后台返回过来筛选后的数据就可以了。不过,我们通过计算属性也可以做到筛选功能,而且更快,更高效。唯一的缺点就是,如果有分页,那么搜索只会搜索到当前页的数据,而通过 http 请求,后台会把所有关于搜索的数据发送过来。
286 |
287 | 商品搜索,可以根据商品标题进行搜索,返回标题中带有搜索关键字的商品就可以了。我们通过`includes`或者`indexOf`实现这个功能。还需要一个方法来遍历数组,并返回一个新数组,大家可以先思考一下使用哪个方法。
288 |
289 | ```html
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
![]()
299 |
300 |
301 |
{{ item.title }}
302 |
303 |
304 |
销量: {{ item.sales }}
305 |
价格: {{ item.price }}
306 |
307 |
308 |
309 |
310 |
311 |
312 |
346 |
347 |
363 | ```
364 |
365 |
366 |
367 | 还记得`v-model`吗?本例就通过`v-model`把`input`绑定到变量`search`上。那么,我们输入的任何值,都储存在`search`变量上。
368 |
369 | 还是跟以前一样,第一步先初始化一个`new_titleList`的计算属性,表示这是一个新的数组。并把`v-for`里面的`list`替换为`new_titleList`。接着,我们就开始具体的搜索逻辑
370 |
371 | ```js
372 | computed: {
373 | new_titleList() {
374 | if (this.search === '') {
375 | return this.list
376 | }
377 |
378 | let arr = this.list.filter(item => {
379 | return item.title.indexOf(this.search) > -1
380 | })
381 | return arr
382 | }
383 | },
384 | ```
385 |
386 | - `if (this.search === '') { return this.list }`
387 |
388 | 这一步表示,如果搜索内容为空的话,就返回默认的数组。可以写成这种形式:
389 |
390 | ```js
391 | if (!this.search) return this.list
392 | ```
393 |
394 | - 接下来就是遍历数组,然后返回标题中带有搜索关键字的商品了。我们选择使用`filter`,因为`filter`能够**返回一个新数组**。`arr`代表着过滤后的数组,所以返回它即可。下面我们来看具体的过滤逻辑
395 |
396 | ```js
397 | this.list.filter(item => {
398 | return item.title.includes(this.search)
399 | })
400 | ```
401 |
402 | - 上面的`item`同样代表 list 里面的每一项
403 | - `item.title`就是我们要过滤的标题了
404 | - `this.search`就是用户输入过滤的关键字
405 | - 我们通过 js 的`includes`方法对标题进行过滤,表示**如果标题中含有搜索关键字,那么就返回这一项**。这一步可以写成
406 |
407 | ```js
408 | return item.title.includes(this.search)
409 | ```
410 |
411 | - 还有一点,我们可以完善一下,就是`let arr`这一步,因为我们知道,`arr`里面存放的就是过滤后的数组,那么我们为何不省略这一步把`let arr`换成直接 `return`呢
412 |
413 | 经过以上分析,我们可以把上面代码优化成:
414 |
415 | ```js
416 | computed: {
417 | new_titleList() {
418 | if(!this.search )return this.list
419 |
420 | return this.list.filter(item => {
421 | return item.title.includes(this.search)
422 | })
423 | }
424 | },
425 | ```
426 |
427 | 相信大家经过以上两个小例子能够对计算属性有了一个深入的了解,计算属性应用非常广泛,大家应该重点掌握。
428 |
--------------------------------------------------------------------------------