由于下载的测试demo是https://github.com/vuejs/vuex/tree/dev/examples,因此里面文件比较多,这里截取一部分跟counter的demo部分相关的内容作学习.css
counter的demo的主要文件是如下这些html
├── ./counter │ ├── ./counter/Counter.vue │ ├── ./counter/app.js │ ├── ./counter/index.html │ └── ./counter/store.js
要使用webpack和babel和npm,并且他们分别在外层的项目的根目录中,可是考虑本次demo他们不是主角,因此没有太过详细描述,不过须要知道的是,要理解vuex,就必需要掌握一部分的es6语法和webpack打包和babel转译es5的知识,否则看起来会一头雾水vue
另外就是MVVM的设计,对于vuex来讲,在学习demo的时候要知道哪里的代码放在哪里,为何放在这里,其实就是跟MVVM有关node
稍稍介绍一下npm,在本次demo学习中,是使用npm对一块儿来打包和转译等操做进行脚本化处理的webpack
package.json 在项目的跟位置,可是counter的demo是项目的一个example子目录git
//其余目录能够暂时不关注,不影响学习 .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:es6
{ "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的热加载,就是每次修改都能自动加载到浏览器 } }
除了主要关注部分,其余稍稍了解一下就行了,通常都是webpack打包的一些基本插件,多出来的都是有其余特殊用途的,但跟vuex自己关系不大github
这里并无vuex的包,主要是由于这个github demo里有vuex的源代码,而后在编译的时候直接使用vuex的源代码来运行了web
这里有2个文件,server.js和webpack.config.jsvuex
由于在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`) })
关于webpack dev server 这里有一个回答蛮好的:從頭說起的話就是 webpack 自己只負責打包編譯的功能 bundle, webpack-dev-server 當然就是協助我們開發的伺服器,這個伺服器底層是靠 express 來實做的,接著思考一下我們要如何更新(live reload)呢? 當然是须要取得 webpack 編好的資料啊,於是就须要在從 request 到 response 的過程中透過 express 的 middleware 取得資料,而方法就是透過 webpack-dev-middleware 。
热加载就是那种相似live reload的东西,自动刷新.
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() ] }
CommonsChunkPlugin的效果是:在你的多个页面(入口)所引用的代码中,找出其中知足条件(被多少个页面引用过)的代码段,断定为公共代码并打包成一个独立的js文件。至此,你只须要在每一个页面都加载这个公共代码的js文件,就能够既保持代码的完整性,又不会重复下载公共代码了(多个页面间会共享此文件的缓存)。引用参考:webpack.optimize.CommonsChunkPlugin和怎么打包公共代码才能避免重复?
chunkFilename用来打包require.ensure方法中引入的模块,本次demo并无这种方法引入的模块,因此不会打包出来,引用参考
稍稍提一下.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连用,目的是模拟出原生浏览器的全部功能的环境
babel-runtime 的做用是模拟 ES2015 环境,包含各类分散的 polyfill 模块
babel-polyfill 是针对全局环境的,引入它浏览器就好像具有了规范里定义的完整的特性,一旦引入,就会跑一个 babel-polyfill 实例。
这两个模块功能几乎相同,就是转码新增 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
<!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>
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里面的属性或者方法
这是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没什么好看的,对比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 })
参考引用: