vuex2.0-例子學習-counter_byKL

線上猛如虎發表於2017-02-26

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

相關文章