├── .babelrc
├── .gitignore
├── README.md
├── index.html
├── package.json
├── src
├── App.vue
├── assets
│ └── logo.png
├── components
│ ├── ProductListOne.vue
│ └── ProductListTwo.vue
└── main.js
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["env", { "modules": false }]
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | dist/
4 | npm-debug.log
5 | yarn-error.log
6 |
7 | # Editor directories and files
8 | .idea
9 | *.suo
10 | *.ntvs*
11 | *.njsproj
12 | *.sln
13 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ### **一. 什么是Vuex?**
2 | Vuex是一个专门为Vue.js应用程序开发的状态管理模式, 它采用集中式存储管理所有组件的公共状态, 并以相应的规则保证状态以一种可预测的方式发生变化.
3 |
4 | ![Vuex核心][1]
5 |
6 | 上图中绿色虚线包裹起来的部分就是Vuex的核心, `state`中保存的就是公共状态, 改变`state`的唯一方式就是通过`mutations`进行更改. 可能你现在看这张图有点不明白, 等经过本文的解释和案例演示, 再回来看这张图, 相信你会有更好的理解.
7 |
8 | ### **二. 为什么要使用Vuex???**
9 | 试想这样的场景, 比如一个Vue的根实例西面有一个根组件名为`App.vue`, 它下面有两个子组件`A.vue`和`B.vue`, `App.vue`想要与`A.vue`或者`B.vue`通讯可以通过props传值的方式, 但是如果`A.vue`和`B.vue`之间的通讯就很麻烦了, 他们需要共有的父组件通过__自定义事件__进行实现, A组件想要和B组件通讯往往是这样的:
10 | ![组件通讯][2]
11 | - A组件说: "报告老大, 能否帮我托个信给小弟B" => dispatch一个事件给App
12 | - App老大说: "包在我身上, 它需要监听A组件的dispatch的时间, 同时需要broadcast一个事件给B组件"
13 | - B小弟说: "信息已收到", 它需要on监听App组件分发的事件
14 |
15 | 这只是一条通讯路径, 如果父组件下有多个子组件, 子组件之间通讯的路径就会变的很繁琐, 父组件需要监听大量的事件, 还需要负责分发给不同的子组件, 很显然这并不是我们想要的组件化的开发体验.
16 |
17 | > __Vuex就是为了解决这一问题出现的__
18 |
19 |
20 |
21 | ### **三.如何引入Vuex?**
22 |
23 | 1. 下载`vuex`: `npm install vuex --save`
24 | 2. 在`main.js`添加:
25 | ```javascript
26 | import Vuex from 'vuex'
27 |
28 | Vue.use( Vuex );
29 |
30 | const store = new Vuex.Store({
31 | //待添加
32 | })
33 |
34 | new Vue({
35 | el: '#app',
36 | store,
37 | render: h => h(App)
38 | })
39 | ```
40 |
41 | ### **四. Vuex的核心概念?**
42 | 在介绍Vuex的核心概念之前, 我使用`vue-cli`初始化了一个demo, 准备以代码的形式来说明Vuex的核心概念, 大家可以在github上的[master分支](https://github.com/Lee-Tanghui/Vuex-Demo)进行下载.这个demo分别有两个组件`ProductListOne.vue`和`ProductListTwo.vue`, 在`App.vue`的`datat`中保存着共有的商品列表, 代码和初始化的效果如下图所示:
43 | ![初始化效果][3]
44 |
45 | ```javascript
46 | //App.vue中的初始化代码
47 |
48 |
49 |
53 |
54 |
55 |
77 |
78 |
84 |
85 | ```
86 |
87 | ```javascript
88 | //ProductListOne.vue
89 |
90 |
91 |
Product List One
92 |
93 | -
94 | {{ product.name }}
95 | ${{ product.price }}
96 |
97 |
98 |
99 |
100 |
101 |
111 |
112 |
134 | ```
135 |
136 | ```javascript
137 | //ProductListTwo.vue
138 |
139 |
140 |
Product List Two
141 |
142 | -
143 | {{ product.name }}
144 | ${{ product.price }}
145 |
146 |
147 |
148 |
149 |
150 |
160 |
161 |
184 |
185 | ```
186 |
187 | #### **核心概念1: State**
188 |
189 | `state`就是Vuex中的公共的状态, 我是将`state`看作是所有组件的`data`, 用于保存所有组件的公共数据.
190 |
191 | - 此时我们就可以把`App.vue`中的两个组件共同使用的data抽离出来, 放到`state`中,代码如下:
192 | ```javascript
193 | //main.js
194 | import Vue from 'vue'
195 | import App from './App.vue'
196 | import Vuex from 'vuex'
197 |
198 | Vue.use( Vuex )
199 |
200 | const store = new Vuex.Store({
201 | state:{
202 | products: [
203 | {name: '鼠标', price: 20},
204 | {name: '键盘', price: 40},
205 | {name: '耳机', price: 60},
206 | {name: '显示屏', price: 80}
207 | ]
208 | }
209 | })
210 |
211 | new Vue({
212 | el: '#app',
213 | store,
214 | render: h => h(App)
215 | })
216 | ```
217 |
218 | - 此时,`ProductListOne.vue`和`ProductListTwo.vue`也需要做相应的更改
219 | ```javascript
220 | //ProductListOne.vue
221 | export default {
222 | data () {
223 | return {
224 | products : this.$store.state.products //获取store中state的数据
225 | }
226 | }
227 | }
228 | ```
229 | ```javascript
230 | //ProductListTwo.vue
231 | export default {
232 | data () {
233 | return {
234 | products: this.$store.state.products //获取store中state的数据
235 | }
236 | }
237 | }
238 | ```
239 | - 此时的页面如下图所示, 可以看到, 将公共数据抽离出来后, 页面没有发生变化.
240 | ![state效果][4]
241 |
242 | > 到此处的Github仓库中代码为: [分支code01](https://github.com/Lee-Tanghui/Vuex-Demo/tree/code01)
243 |
244 | #### **核心概念2: Getters**
245 | 我将`getters`属性理解为所有组件的`computed`属性, 也就是计算属性. vuex的官方文档也是说到可以将getter理解为store的计算属性, getters的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
246 |
247 | - 此时,我们可以在`main.js`中添加一个`getters`属性, 其中的`saleProducts`对象将`state`中的价格减少一半(除以2)
248 | ```javascript
249 | //main.js
250 | const store = new Vuex.Store({
251 | state:{
252 | products: [
253 | {name: '鼠标', price: 20},
254 | {name: '键盘', price: 40},
255 | {name: '耳机', price: 60},
256 | {name: '显示屏', price: 80}
257 | ]
258 | },
259 | getters:{ //添加getters
260 | saleProducts: (state) => {
261 | let saleProducts = state.products.map( product => {
262 | return {
263 | name: product.name,
264 | price: product.price / 2
265 | }
266 | })
267 | return saleProducts;
268 | }
269 | }
270 | })
271 | ```
272 |
273 | - 将`productListOne.vue`中的`products`的值更换为`this.$store.getters.saleProducts`
274 |
275 | ```javascript
276 | export default {
277 | data () {
278 | return {
279 | products : this.$store.getters.saleProducts
280 | }
281 | }
282 | }
283 | ```
284 |
285 | - 现在的页面中,Product List One中的每项商品的价格都减少了一半
286 |
287 | ![getters效果][5]
288 |
289 | > 到此处的Github仓库中代码为: [分支code02](https://github.com/Lee-Tanghui/Vuex-Demo/tree/code02)
290 |
291 | #### **核心概念3: Mutations**
292 |
293 | 我将`mutaions`理解为`store`中的`methods`, `mutations`对象中保存着更改数据的回调函数,该函数名官方规定叫`type`, 第一个参数是`state`, 第二参数是`payload`, 也就会自定义的参数.
294 |
295 | - 下面,我们在`main.js`中添加`mutations`属性,其中`minusPrice`这个回调函数用于将商品的价格减少`payload`这么多, 代码如下:
296 |
297 | ```javascript
298 | //main.js
299 | const store = new Vuex.Store({
300 | state:{
301 | products: [
302 | {name: '鼠标', price: 20},
303 | {name: '键盘', price: 40},
304 | {name: '耳机', price: 60},
305 | {name: '显示屏', price: 80}
306 | ]
307 | },
308 | getters:{
309 | saleProducts: (state) => {
310 | let saleProducts = state.products.map( product => {
311 | return {
312 | name: product.name,
313 | price: product.price / 2
314 | }
315 | })
316 | return saleProducts;
317 | }
318 | },
319 | mutations:{ //添加mutations
320 | minusPrice (state, payload ) {
321 | let newPrice = state.products.forEach( product => {
322 | product.price -= payload
323 | })
324 | }
325 | }
326 | })
327 | ```
328 |
329 | - 在`ProductListTwo.vue`中添加一个按钮,为其添加一个点击事件, 给点击事件触发`minusPrice`方法
330 | ```javascript
331 | //ProductListTwo.vue
332 |
333 |
334 |
Product List Two
335 |
336 | -
337 | {{ product.name }}
338 | ${{ product.price }}
339 |
340 | //添加按钮
341 |
342 |
343 |
344 | ```
345 |
346 | - 在`ProductListTwo.vue`中注册`minusPrice`方法, 在该方法中commit`mutations`中的`minusPrice`这个回调函数
347 | **注意:调用mutaions中回调函数, 只能使用store.commit(type, payload)**
348 | ```javascript
349 | //ProductListTwo.vue
350 | export default {
351 | data () {
352 | return {
353 | products: this.$store.state.products
354 | }
355 | },
356 | methods: {
357 | minusPrice() {
358 | this.$store.commit('minusPrice', 2); //提交`minusPrice,payload为2
359 | }
360 | }
361 | }
362 | ```
363 |
364 |
365 | - 添加按钮, 可以发现, Product List Two中的价格减少了2, 当然你可以自定义`payload`,以此自定义减少对应的价格.
366 | ![mutations效果][6]
367 | (Product List One中的价格没有发生变化, 是因为`getters`将价格进行了缓存)
368 |
369 | > 到此处的Github仓库中代码为: [分支code03](https://github.com/Lee-Tanghui/Vuex-Demo/tree/code03)
370 |
371 | #### **核心概念4: Actions**
372 | `actions` 类似于 `mutations`,不同在于:
373 | - `actions`提交的是`mutations`而不是直接变更状态
374 | - `actions`中可以包含异步操作, `mutations`中绝对不允许出现异步
375 | - `actions`中的回调函数的第一个参数是`context`, 是一个与`store`实例具有相同属性和方法的对象
376 |
377 | - 此时,我们在`store`中添加`actions`属性, 其中`minusPriceAsync`采用`setTimeout`来模拟异步操作,延迟2s执行 该方法用于异步改变我们刚才在`mutaions`中定义的`minusPrice`
378 | ```javascript
379 | //main.js
380 | const store = new Vuex.Store({
381 | state:{
382 | products: [
383 | {name: '鼠标', price: 20},
384 | {name: '键盘', price: 40},
385 | {name: '耳机', price: 60},
386 | {name: '显示屏', price: 80}
387 | ]
388 | },
389 | getters:{
390 | saleProducts: (state) => {
391 | let saleProducts = state.products.map( product => {
392 | return {
393 | name: product.name,
394 | price: product.price / 2
395 | }
396 | })
397 | return saleProducts;
398 | }
399 | },
400 | mutations:{
401 | minusPrice (state, payload ) {
402 | let newPrice = state.products.forEach( product => {
403 | product.price -= payload
404 | })
405 | }
406 | },
407 | actions:{ //添加actions
408 | minusPriceAsync( context, payload ) {
409 | setTimeout( () => {
410 | context.commit( 'minusPrice', payload ); //context提交
411 | }, 2000)
412 | }
413 | }
414 | })
415 | ```
416 |
417 | - 在`ProductListTwo.vue`中添加一个按钮,为其添加一个点击事件, 给点击事件触发`minusPriceAsync`方法
418 |
419 | ```javascript
420 |
421 |
422 |
Product List Two
423 |
424 | -
425 | {{ product.name }}
426 | ${{ product.price }}
427 |
428 |
429 | //添加按钮
430 |
431 |
432 |
433 | ```
434 |
435 | - 在`ProductListTwo.vue`中注册`minusPriceAsync`方法, 在该方法中dispatch`actions`中的`minusPriceAsync`这个回调函数
436 | ```javascript
437 | export default {
438 | data () {
439 | return {
440 | products: this.$store.state.products
441 | }
442 | },
443 | methods: {
444 | minusPrice() {
445 | this.$store.commit('minusPrice', 2);
446 | },
447 | minusPriceAsync() {
448 | this.$store.dispatch('minusPriceAsync', 5); //分发actions中的minusPriceAsync这个异步函数
449 | }
450 | }
451 | }
452 | ```
453 |
454 | - 添加按钮, 可以发现, Product List Two中的价格延迟2s后减少了5
455 | ![actions效果][7]
456 |
457 | > 到此处的Github仓库中代码为: [分支code04](https://github.com/Lee-Tanghui/Vuex-Demo/tree/code04)
458 |
459 | #### **核心概念5: Modules**
460 |
461 | > 由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割
462 |
463 | ```javascript
464 | const moduleA = {
465 | state: { ... },
466 | mutations: { ... },
467 | actions: { ... },
468 | getters: { ... }
469 | }
470 |
471 | const moduleB = {
472 | state: { ... },
473 | mutations: { ... },
474 | actions: { ... }
475 | }
476 |
477 | const store = new Vuex.Store({
478 | modules: {
479 | a: moduleA,
480 | b: moduleB
481 | }
482 | })
483 |
484 | store.state.a // -> moduleA 的状态
485 | store.state.b // -> moduleB 的状态
486 | ```
487 |
488 | ---
489 |
490 | **【相关链接】**
491 | 1. 本文代码地址: https://github.com/Lee-Tanghui/Vuex-Demo
492 | 2. Vuex官方文档: https://vuex.vuejs.org/zh-cn/intro.html
493 | 3. Vuex官方案例演示源码: https://github.com/vuejs/vuex/tree/dev/examples
494 |
495 | [1]: http://static.zybuluo.com/leeahui424/p0v57hlvqg6qr5t9kbae159u/image_1bqru9fcu1pq917q61ktk18cn1sov9.png
496 | [2]: http://static.zybuluo.com/leeahui424/mpwwmoxknvaqxe02wfzmufyr/image_1bqruul922q0146t1tjr1u91173bm.png
497 | [3]: http://static.zybuluo.com/leeahui424/mh643lwsmg2ufxhwbe4yf66h/image_1bqs3mi8no0iv0t18qh1er0dks2a.png
498 | [4]: http://static.zybuluo.com/leeahui424/q30mdxsmt7v5dz7qu1cypz2q/image_1bqs5ai241no61uto1v6og3urtj2n.png
499 | [5]: http://static.zybuluo.com/leeahui424/p6jwqaf733rik4imrnoqfqx1/image_1bqse85kedec1qeqr761rp9ng134.png
500 | [6]: http://static.zybuluo.com/leeahui424/nh7widztlnwe60eqx6609k50/image_1bqsfvvv81bpc11to5tfb4917r23h.png
501 | [7]: http://static.zybuluo.com/leeahui424/hkktwcky7xn8tjvsxea51qlw/image_1bqshrq7ku1s5e44qc6551ehl4b.png
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | vuex-playlist
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vuex-playlist",
3 | "description": "A Vue.js project",
4 | "version": "1.0.0",
5 | "author": "iamshaunjp ",
6 | "private": true,
7 | "scripts": {
8 | "dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot",
9 | "build": "cross-env NODE_ENV=production webpack --progress --hide-modules"
10 | },
11 | "dependencies": {
12 | "vue": "^2.3.3"
13 | },
14 | "devDependencies": {
15 | "babel-core": "^6.0.0",
16 | "babel-loader": "^6.0.0",
17 | "babel-preset-env": "^1.5.1",
18 | "cross-env": "^3.0.0",
19 | "css-loader": "^0.25.0",
20 | "file-loader": "^0.9.0",
21 | "vue-loader": "^12.1.0",
22 | "vue-template-compiler": "^2.3.3",
23 | "webpack": "^2.6.1",
24 | "webpack-dev-server": "^2.4.5"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
30 |
31 |
37 |
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KapiAI/Vuex-Demo/5eb774968ed922faa58ceac3df8137543179ce35/src/assets/logo.png
--------------------------------------------------------------------------------
/src/components/ProductListOne.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Product List One
4 |
5 | -
6 | {{ product.name }}
7 | ${{ product.price }}
8 |
9 |
10 |
11 |
12 |
13 |
23 |
24 |
46 |
--------------------------------------------------------------------------------
/src/components/ProductListTwo.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Product List Two
4 |
5 | -
6 | {{ product.name }}
7 | ${{ product.price }}
8 |
9 |
10 |
11 |
12 |
13 |
23 |
24 |
47 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import App from './App.vue'
3 |
4 | new Vue({
5 | el: '#app',
6 | render: h => h(App)
7 | })
8 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 | var webpack = require('webpack')
3 |
4 | module.exports = {
5 | entry: './src/main.js',
6 | output: {
7 | path: path.resolve(__dirname, './dist'),
8 | publicPath: '/dist/',
9 | filename: 'build.js'
10 | },
11 | module: {
12 | rules: [
13 | {
14 | test: /\.vue$/,
15 | loader: 'vue-loader',
16 | options: {
17 | loaders: {
18 | }
19 | // other vue-loader options go here
20 | }
21 | },
22 | {
23 | test: /\.js$/,
24 | loader: 'babel-loader',
25 | exclude: /node_modules/
26 | },
27 | {
28 | test: /\.(png|jpg|gif|svg)$/,
29 | loader: 'file-loader',
30 | options: {
31 | name: '[name].[ext]?[hash]'
32 | }
33 | }
34 | ]
35 | },
36 | resolve: {
37 | alias: {
38 | 'vue$': 'vue/dist/vue.esm.js'
39 | }
40 | },
41 | devServer: {
42 | historyApiFallback: true,
43 | noInfo: true
44 | },
45 | performance: {
46 | hints: false
47 | },
48 | devtool: '#eval-source-map'
49 | }
50 |
51 | if (process.env.NODE_ENV === 'production') {
52 | module.exports.devtool = '#source-map'
53 | // http://vue-loader.vuejs.org/en/workflow/production.html
54 | module.exports.plugins = (module.exports.plugins || []).concat([
55 | new webpack.DefinePlugin({
56 | 'process.env': {
57 | NODE_ENV: '"production"'
58 | }
59 | }),
60 | new webpack.optimize.UglifyJsPlugin({
61 | sourceMap: true,
62 | compress: {
63 | warnings: false
64 | }
65 | }),
66 | new webpack.LoaderOptionsPlugin({
67 | minimize: true
68 | })
69 | ])
70 | }
71 |
--------------------------------------------------------------------------------