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的热加载,就是每次修改都能自动加载到浏览器
}
}
除了主要关注部分,其他稍稍了解一下就好了,一般都是webpack打包的一些基本插件,多出来的都是有其他特殊用途的,但跟vuex本身关系不大
这里并没有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`)
})
关于webpack dev server 这里有一个回答蛮好的:從頭說起的話就是 webpack 本身只負責打包編譯的功能 bundle, webpack-dev-server 當然就是協助我們開發的伺服器,這個伺服器底層是靠 express 來實作的,接著思考一下我們要如何更新(live reload)呢? 當然是需要取得 webpack 編好的資料啊,於是就需要在從 request 到 response 的過程中透過 express 的 middleware 取得資料,而方法就是透過 webpack-dev-middleware 。
热加载就是那种类似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()
]
}
CommonsChunkPlugin的效果是:在你的多个页面(入口)所引用的代码中,找出其中满足条件(被多少个页面引用过)的代码段,判定为公共代码并打包成一个独立的js文件。至此,你只需要在每个页面都加载这个公共代码的js文件,就可以既保持代码的完整性,又不会重复下载公共代码了(多个页面间会共享此文件的缓存)。引用参考:webpack.optimize.CommonsChunkPlugin和怎么打包公共代码才能避免重复?
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连用,目的是模拟出原生浏览器的所有功能的环境
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
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
})
参考引用: