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
})
參考引用: