由於下載的測試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 })
參考引用: