vuex2.0 例子学习-counter

因为下载的测试demo是https://github.com/vuejs/vuex/tree/dev/examples,所以里面文件比较多,这里截取一部分跟counter的demo部分相关的内容做学习.

  • counter的demo的主要文件是以下这些

├── ./counter
│   ├── ./counter/Counter.vue
│   ├── ./counter/app.js
│   ├── ./counter/index.html
│   └── ./counter/store.js
  • 要使用webpack和babel和npm,而且他们分别在外层的项目的根目录中,但是考虑本次demo他们不是主角,所以没有太过详细描述,不过需要知道的是,要理解vuex,就必须要掌握一部分的es6语法和webpack打包和babel转译es5的知识,不然看起来会一头雾水

  • 另外就是MVVM的设计,对于vuex来说,在学习demo的时候要知道哪里的代码放在哪里,为什么放在这里,其实就是跟MVVM有关

npm

稍稍介绍一下npm,在本次demo学习中,是使用npm对一起来打包和转译等操作进行脚本化处理的

package.json 在项目的跟位置,但是counter的demo是项目的一个example子目录

//其他目录可以暂时不关注,不影响学习
.babelrc  //这里是babel的配置文件,也是在项目根目录
├── LICENSE
├── README.md
├── bower.json
├── build
├── circle.yml
├── dist
├── docs
├── examples  //里面其中一个就是counter
    ├── chat
    ├── counter
    ├── counter-hot
    ├── global.css
    ├── index.html
    ├── server.js
    ├── shopping-cart
    ├── todomvc
    └── webpack.config.js
├── node_modules
├── package.json // 在这里
├── src
├── test
├── types
└── yarn.lock

查看package.json:

{
  "name": "vuex",
  "version": "2.1.3",
  "description": "state management for Vue.js",
  "main": "dist/vuex.js",
  "typings": "types/index.d.ts",
  "files": [
    "dist",
    "src",
    "types/index.d.ts",
    "types/helpers.d.ts",
    "types/vue.d.ts"
  ],
  "scripts": {
    "dev": "node examples/server.js", // 主要关注这里,我们要做开发需要测试就是用这个命令
    "dev:dist": "rollup -wm -c build/rollup.config.js",
    "build": "npm run build:main && npm run build:logger", //这是发版本build的
    "build:main": "rollup -c build/rollup.config.js && uglifyjs dist/vuex.js -cm --comments -o dist/vuex.min.js",
    "build:logger": "rollup -c build/rollup.logger.config.js",
    "lint": "eslint src test",
    "test": "npm run lint && npm run test:types && npm run test:unit && npm run test:e2e",
    "test:unit": "rollup -c build/rollup.config.js && jasmine JASMINE_CONFIG_PATH=test/unit/jasmine.json",
    "test:e2e": "node test/e2e/runner.js",
    "test:types": "tsc -p types/test",
    "release": "bash build/release.sh",
    "docs": "cd docs && gitbook serve",
    "docs:deploy": "cd docs && ./deploy.sh"
  },
...................
  "homepage": "https://github.com/vuejs/vuex#readme",
  "devDependencies": { //了解一下这个demo需要的那些依赖模块
    "babel-core": "^6.22.1", 
    "babel-eslint": "^7.1.1",
    "babel-loader": "^6.2.10",
    "babel-plugin-transform-runtime": "^6.22.0",
    "babel-polyfill": "^6.22.0", //将es6转译es5的api的babel
    "babel-preset-es2015": "^6.22.0", //将es6转译es5的babel
    "babel-preset-es2015-rollup": "^3.0.0",
    "babel-preset-stage-2": "^6.22.0", 
    "babel-runtime": "^6.22.0",
    "chromedriver": "^2.27.2",
    "cross-spawn": "^5.0.1",
    "css-loader": "^0.26.1", //webpack处理css的工具
    "eslint": "^3.15.0",
    "eslint-config-vue": "^2.0.2",
    "eslint-plugin-vue": "^2.0.1",
    "express": "^4.14.1",
    "jasmine": "2.5.3",
    "jasmine-core": "2.5.2",
    "nightwatch": "^0.9.12",
    "nightwatch-helpers": "^1.2.0",
    "phantomjs-prebuilt": "^2.1.14",
    "rollup": "^0.41.4",
    "rollup-plugin-buble": "^0.15.0",
    "rollup-plugin-replace": "^1.1.1",
    "rollup-watch": "^3.2.2",
    "selenium-server": "^2.53.1",
    "todomvc-app-css": "^2.0.6",
    "typescript": "^2.1.5",
    "uglify-js": "^2.7.5",
    "vue": "^2.1.10", //vue库
    "vue-loader": "^11.0.0", //*.vue文件的处理的
    "vue-template-compiler": "^2.1.10",
    "webpack": "^2.2.1",
    "webpack-dev-middleware": "^1.10.0",
    "webpack-hot-middleware": "^2.16.1" //webpack的热加载,就是每次修改都能自动加载到浏览器
  }
}
  1. 除了主要关注部分,其他稍稍了解一下就好了,一般都是webpack打包的一些基本插件,多出来的都是有其他特殊用途的,但跟vuex本身关系不大

  2. 这里并没有vuex的包,主要是因为这个github demo里有vuex的源代码,然后在编译的时候直接使用vuex的源代码来运行了

webpack

这里有2个文件,server.js和webpack.config.js

server.js

  • 因为在package.json里面指定了开发的时候npm run dev会执行这个文件

  • 这个执行这个文件会启动一个本地web server,监听8080端口

const express = require('express')
const webpack = require('webpack')
const webpackDevMiddleware = require('webpack-dev-middleware') //webpack通过这个模块去捕获到内存中,方便开发使用
const webpackHotMiddleware = require('webpack-hot-middleware') //会使用热加载模块
const WebpackConfig = require('./webpack.config') //加载webpack配置文件

const app = express()
const compiler = webpack(WebpackConfig)

app.use(webpackDevMiddleware(compiler, {
  publicPath: '/__build__/', //webpackDevMiddleware的公共目录,在dev模式下,浏览器可以使用这个位置引用文件,例如引用js
  stats: {
    colors: true,
    chunks: false
  }
}))

app.use(webpackHotMiddleware(compiler))

app.use(express.static(__dirname))

const port = process.env.PORT || 8080 //本地webserver服务器的8080端口
module.exports = app.listen(port, () => {
  console.log(`Server listening on http://localhost:${port}, Ctrl+C to stop`)
})
  1. 关于webpack dev server 这里有一个回答蛮好的:從頭說起的話就是 webpack 本身只負責打包編譯的功能 bundle, webpack-dev-server 當然就是協助我們開發的伺服器,這個伺服器底層是靠 express 來實作的,接著思考一下我們要如何更新(live reload)呢? 當然是需要取得 webpack 編好的資料啊,於是就需要在從 request 到 response 的過程中透過 express 的 middleware 取得資料,而方法就是透過 webpack-dev-middleware 。

  2. 热加载就是那种类似live reload的东西,自动刷新.

webpack.config.js

webpack的配置文件

const fs = require('fs')
const path = require('path')
const webpack = require('webpack') //引用了webpack模块

module.exports = {

  devtool: 'inline-source-map',

  entry: fs.readdirSync(__dirname).reduce((entries, dir) => {
    const fullDir = path.join(__dirname, dir)
    const entry = path.join(fullDir, 'app.js') //使用app.js作为入口,后面可以看到app.js的内容,这里只需要知道这一个总入口
    if (fs.statSync(fullDir).isDirectory() && fs.existsSync(entry)) {
      entries[dir] = ['webpack-hot-middleware/client', entry]
    }

    return entries
  }, {}),

  output: {
    path: path.join(__dirname, '__build__'), 
    filename: '[name].js', //一般的js就按照名字命名
    chunkFilename: '[id].chunk.js', //这个是暂时用不到
    publicPath: '/__build__/' //publicPath指定了你在浏览器中用什么地址来引用你的静态文件,它会包括你的图片、脚本以及样式加载的地址,一般用于线上发布以及CDN部署的时候使用。
  module: {
    rules: [
      { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader' }, //js会被babel-loader捕获,然后解析翻译
      { test: /\.vue$/, loader: 'vue-loader' } //.vue文件会被vue-loader解析翻译
    ]
  },

  resolve: {
    alias: { //这里建立了一个别名vuex,然后指向源代码目录来调用vuex,所以在package.json里面没看到vuex,因为他在这里调用了源代码的vuex
      vuex: path.resolve(__dirname, '../build/dev-entry')
    }
  },

  plugins: [
    new webpack.optimize.CommonsChunkPlugin({ //将公共部分输出到指定的js里面,给公共使用
      name: 'shared', //给这个包含公共代码的chunk命个名(唯一标识)。
      filename: 'shared.js' //命名打包后生产的js文件
    }),
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')
    }),
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NoEmitOnErrorsPlugin()
  ]

}
  1. CommonsChunkPlugin的效果是:在你的多个页面(入口)所引用的代码中,找出其中满足条件(被多少个页面引用过)的代码段,判定为公共代码并打包成一个独立的js文件。至此,你只需要在每个页面都加载这个公共代码的js文件,就可以既保持代码的完整性,又不会重复下载公共代码了(多个页面间会共享此文件的缓存)。引用参考:webpack.optimize.CommonsChunkPlugin怎么打包公共代码才能避免重复?

  2. chunkFilename用来打包require.ensure方法中引入的模块,本次demo并没有这种方法引入的模块,所以不会打包出来,引用参考

babel

稍稍提一下.babelrc,babel的配置文件,因为webpack+babel一般都是连着使用了,所以也需要大概了解一下

{
  "presets": [
    ["es2015", { "modules": false }], //使用将es6转译为es5的插件
    "stage-2" //使用将es6的stage-2的语法转译为es5的插件
  ],
  "plugins": ["transform-runtime"], //这个相对有点复杂,大概知道意思即可
  "comments": false,
  "env": {
    "test": {
      "plugins": [ "istanbul" ]
    }
  }
}

大概了解一下下......
transform-runtime一般跟babel-polyfill连用,目的是模拟出原生浏览器的所有功能的环境

  1. babel-runtime 的作用是模拟 ES2015 环境,包含各种分散的 polyfill 模块

  2. babel-polyfill 是针对全局环境的,引入它浏览器就好像具备了规范里定义的完整的特性,一旦引入,就会跑一个 babel-polyfill 实例。

  3. 这两个模块功能几乎相同,就是转码新增 api,模拟 es6 环境,但实现方法完全不同。babel-polyfill 的做法是将全局对象通通污染一遍,比如想在 node 0.10 上用 Promise,调用 babel-polyfill 就会往 global 对象挂上 Promise 对象。对于普通的业务代码没有关系,但如果用在模块上就有问题了,会把模块使用者的环境污染掉。

引用:https://zhuanlan.zhihu.com/p/20904140?refer=mirreal
引用:https://github.com/brunoyang/blog/issues/20

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>vuex counter example</title>
    <link rel="stylesheet" href="/global.css">
  </head>
  <body>
  <!--vue实例的绑定位置-->
    <div id="app"></div> 
    <!--两个编译出来的js文件,名字和目录是webpack里面指定的-->
    <script src="/__build__/shared.js"></script>
    <script src="/__build__/counter.js"></script>
  </body>
</html>

app.js

import 'babel-polyfill' //引入babel-polyfill,之前已经引入了transform-runtime,但是因为他要调用babel-polyfill,所以要另外import
import Vue from 'vue' //引入vue.js
import Counter from './Counter.vue' //引入Counter.vue
import store from './store' //引入store.js

new Vue({ //初始化vue实例
  el: '#app',
  store, //这个store是上面的引入的store.js
  render: h => h(Counter) //用render函数来渲染,并且渲染的是Counter函数
})

这里有个地方需要注意,在es6里,import的时候会自动提升执行优先级,也就是会提升到当前模块的头部,那么这里即使先import Counter再import store,在Counter里面依然可以调用store里面的属性或者方法

Counter.vue

这是vue的文件写法,template,script,css的结构,这里忽略了css

<template>
  <div id="app">
    //并且这里使用了$store.state.count的值,直接访问store里面的值
    Clicked: {{ $store.state.count }} times, count is {{ evenOrOdd }}. //可以evenOrOdd计算属性
    <button @click="increment">+</button> //可以直接使用increment方法
    <button @click="decrement">-</button>
    <button @click="incrementIfOdd">Increment if odd</button>
    <button @click="incrementAsync">Increment async</button>
  </div>
</template>

<script>
import { mapGetters, mapActions } from 'vuex' //从vuex导入 mapGetters, mapActions

export default { //导出默认的对外接口
  computed: mapGetters([ //mapGetters辅助函数仅仅是将 store 中的 getters 映射到局部计算属性:
    'evenOrOdd'
  ]),
  methods: mapActions([ //类似,并且映射之后可以在当前vue组件使用
    'increment',
    'decrement',
    'incrementIfOdd',
    'incrementAsync'
  ])
}
</script>

store.js

反而这个store.js没什么好看的,对比vuex的官网文档几乎都能找到解释

import Vue from 'vue' //引入vue
import Vuex from 'vuex' //引入vuex

Vue.use(Vuex) //vue使用vuex

// root state object.
// each Vuex instance is just a single state tree.
const state = { 
  count: 0
}

// mutations are operations that actually mutates the state.
// each mutation handler gets the entire state tree as the
// first argument, followed by additional payload arguments.
// mutations must be synchronous and can be recorded by plugins
// for debugging purposes.
const mutations = { 
  increment (state) {
    state.count++
  },
  decrement (state) {
    state.count--
  }
}

// actions are functions that causes side effects and can involve
// asynchronous operations.
const actions = {
  increment: ({ commit }) => commit('increment'),
  decrement: ({ commit }) => commit('decrement'),
  incrementIfOdd ({ commit, state }) {
    if ((state.count + 1) % 2 === 0) {
      commit('increment')
    }
  },
  incrementAsync ({ commit }) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        commit('increment')
        resolve()
      }, 1000)
    })
  }
}

// getters are functions
const getters = { 
  evenOrOdd: state => state.count % 2 === 0 ? 'even' : 'odd'
}

// A Vuex instance is created by combining the state, mutations, actions,
// and getters.
export default new Vuex.Store({
  state,
  getters,
  actions,
  mutations
})

参考引用:

  1. https://github.com/vuejs/vuex/tree/dev/examples

  2. http://www.imooc.com/article/10969

  3. https://webpack.github.io/docs/list-of-plugins.html#commonschunkplugin

  4. http://vuex.vuejs.org/zh-cn/getters.html


线上猛如虎
2.2k 声望178 粉丝

你们都有梦想的,是吧.怀抱着梦想并且正朝着梦想努力的人,寻找着梦想的人,我想为这些人加油呐喊!