├── .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 | 54 | 55 | 77 | 78 | 84 | 85 | ``` 86 | 87 | ```javascript 88 | //ProductListOne.vue 89 | 100 | 101 | 111 | 112 | 134 | ``` 135 | 136 | ```javascript 137 | //ProductListTwo.vue 138 | 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 | 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 | 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 | 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 | 12 | 13 | 23 | 24 | 46 | -------------------------------------------------------------------------------- /src/components/ProductListTwo.vue: -------------------------------------------------------------------------------- 1 | 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 | --------------------------------------------------------------------------------