Browse Source

basic counter example working

Evan You 9 years ago
parent
commit
25e0f1e881

+ 0 - 10
build/build.js

@@ -37,16 +37,6 @@ rollup.rollup({
   }).code
   }).code
   return write('dist/vuex.min.js', minified)
   return write('dist/vuex.min.js', minified)
 })
 })
-.then(function () {
-  return rollup.rollup({
-    entry: 'src/plugins/logger.js',
-    plugins: [babel()]
-  }).then(function (bundle) {
-    return write('logger.js', bundle.generate({
-      format: 'cjs'
-    }).code)
-  })
-})
 .catch(logError)
 .catch(logError)
 
 
 function write (dest, code) {
 function write (dest, code) {

+ 1 - 0
build/dev-entry.js

@@ -0,0 +1 @@
+module.exports = require('../src').default

+ 8 - 7
examples/counter/Counter.vue

@@ -9,14 +9,15 @@
 </template>
 </template>
 
 
 <script>
 <script>
-import * as actions from './actions'
+import { mapGetters, mapActions } from 'vuex'
 
 
 export default {
 export default {
-  vuex: {
-    getters: {
-      count: state => state.count
-    },
-    actions: actions
-  }
+  computed: mapGetters(['count']),
+  methods: mapActions([
+    'increment',
+    'decrement',
+    'incrementIfOdd',
+    'incrementAsync'
+  ])
 }
 }
 </script>
 </script>

+ 0 - 14
examples/counter/actions.js

@@ -1,14 +0,0 @@
-export const increment = ({ dispatch }) => dispatch('INCREMENT')
-export const decrement = ({ dispatch }) => dispatch('DECREMENT')
-
-export const incrementIfOdd = ({ dispatch, state }) => {
-  if ((state.count + 1) % 2 === 0) {
-    dispatch('INCREMENT')
-  }
-}
-
-export const incrementAsync = ({ dispatch }) => {
-  setTimeout(() => {
-    dispatch('INCREMENT')
-  }, 1000)
-}

+ 22 - 3
examples/counter/store.js

@@ -1,5 +1,5 @@
 import Vue from 'vue'
 import Vue from 'vue'
-import Vuex from '../../src'
+import Vuex from 'vuex'
 
 
 Vue.use(Vuex)
 Vue.use(Vuex)
 
 
@@ -15,19 +15,38 @@ const state = {
 // mutations must be synchronous and can be recorded by plugins
 // mutations must be synchronous and can be recorded by plugins
 // for debugging purposes.
 // for debugging purposes.
 const mutations = {
 const mutations = {
-  INCREMENT (state) {
+  increment (state) {
     state.count++
     state.count++
   },
   },
-  DECREMENT (state) {
+  decrement (state) {
     state.count--
     state.count--
   }
   }
 }
 }
 
 
+const actions = {
+  increment: ({ dispatch }) => dispatch('increment'),
+  decrement: ({ dispatch }) => dispatch('decrement'),
+  incrementIfOdd ({ dispatch, state }) {
+    if ((state.count + 1) % 2 === 0) {
+      dispatch('increment')
+    }
+  },
+  incrementAsync ({ dispatch, state }) {
+    setTimeout(() => {
+      dispatch('increment')
+    }, 1000)
+  }
+}
+
 // A Vuex instance is created by combining the state, the actions,
 // A Vuex instance is created by combining the state, the actions,
 // and the mutations. Because the actions and mutations are just
 // and the mutations. Because the actions and mutations are just
 // functions that do not depend on the instance itself, they can
 // functions that do not depend on the instance itself, they can
 // be easily tested or even hot-reloaded (see counter-hot example).
 // be easily tested or even hot-reloaded (see counter-hot example).
 export default new Vuex.Store({
 export default new Vuex.Store({
   state,
   state,
+  getters: {
+    count: state => state.count
+  },
+  actions,
   mutations
   mutations
 })
 })

+ 7 - 0
examples/webpack.build-all.config.js

@@ -1,3 +1,5 @@
+var path = require('path')
+
 var examples = [
 var examples = [
   'chat',
   'chat',
   'counter',
   'counter',
@@ -17,6 +19,11 @@ module.exports = {
     path: __dirname,
     path: __dirname,
     filename: '[name]/build.js'
     filename: '[name]/build.js'
   },
   },
+  resolve: {
+    alias: {
+      vuex: path.resolve(__dirname, '../build/dev-entry')
+    }
+  },
   module: {
   module: {
     loaders: [
     loaders: [
       {
       {

+ 7 - 0
examples/webpack.shared.config.js

@@ -1,9 +1,16 @@
+var path = require('path')
+
 module.exports = {
 module.exports = {
   entry: './main.js',
   entry: './main.js',
   output: {
   output: {
     path: process.cwd(),
     path: process.cwd(),
     filename: 'build.js'
     filename: 'build.js'
   },
   },
+  resolve: {
+    alias: {
+      vuex: path.resolve(__dirname, '../build/dev-entry')
+    }
+  },
   module: {
   module: {
     loaders: [
     loaders: [
       {
       {

+ 25 - 0
src/helpers.js

@@ -0,0 +1,25 @@
+export function mapGetters (getters) {
+  const res = {}
+  normalizeMap(getters).forEach(({ key, val }) => {
+    res[key] = function () {
+      return this.$store.getters[val]
+    }
+  })
+  return res
+}
+
+export function mapActions (actions) {
+  const res = {}
+  normalizeMap(actions).forEach(({ key, val }) => {
+    res[key] = function () {
+      return this.$store.call(val)
+    }
+  })
+  return res
+}
+
+function normalizeMap (map) {
+  return Array.isArray(map)
+    ? map.map(key => ({ key, val: key }))
+    : Object.keys(map).map(key => ({ key, val: map[key] }))
+}

+ 88 - 21
src/index.js

@@ -1,6 +1,6 @@
 import devtoolPlugin from './plugins/devtool'
 import devtoolPlugin from './plugins/devtool'
-import strictPlugin from './plugins/strict'
 import applyMixin from './mixin'
 import applyMixin from './mixin'
+import { mapGetters, mapActions } from './helpers'
 
 
 let Vue // bind on install
 let Vue // bind on install
 
 
@@ -38,24 +38,18 @@ class Store {
     this.call = bind(this.call, this)
     this.call = bind(this.call, this)
     this.dispatch = bind(this.dispatch, this)
     this.dispatch = bind(this.dispatch, this)
 
 
-    // use a Vue instance to store the state tree
-    // suppress warnings just in case the user has added
-    // some funky global mixins
-    const silent = Vue.config.silent
-    Vue.config.silent = true
-    this._vm = new Vue({ data: { state }})
-    Vue.config.silent = silent
+    // init state and getters
+    extractModuleGetters(getters, modules)
+    initStoreState(this, state, getters)
 
 
     // apply root module
     // apply root module
     this.module([], options)
     this.module([], options)
 
 
+    // strict mode
+    if (strict) enableStrictMode(this)
+
     // apply plugins
     // apply plugins
-    plugins = plugins.concat(
-      strict
-        ? [devtoolPlugin, strictPlugin]
-        : [devtoolPlugin]
-    )
-    plugins.forEach(plugin => plugin(this))
+    plugins.concat(devtoolPlugin).forEach(plugin => plugin(this))
   }
   }
 
 
   get state () {
   get state () {
@@ -88,7 +82,7 @@ class Store {
 
 
     // set state
     // set state
     if (!isRoot && !hot) {
     if (!isRoot && !hot) {
-      const parentState = get(this.state, path.slice(-1))
+      const parentState = getNestedState(this.state, path.slice(-1))
       const moduleName = path[path.length - 1]
       const moduleName = path[path.length - 1]
       Vue.set(parentState, moduleName, state || {})
       Vue.set(parentState, moduleName, state || {})
     }
     }
@@ -117,7 +111,7 @@ class Store {
     entry.push(payload => {
     entry.push(payload => {
       handler(getNestedState(this.state, path), payload)
       handler(getNestedState(this.state, path), payload)
     })
     })
-   },
+  }
 
 
   action (type, handler, path = []) {
   action (type, handler, path = []) {
     const entry = this._actions[type] || (this._actions[type] = [])
     const entry = this._actions[type] || (this._actions[type] = [])
@@ -153,7 +147,7 @@ class Store {
     this._dispatching = true
     this._dispatching = true
     entry.forEach(handler => handler(payload))
     entry.forEach(handler => handler(payload))
     this._dispatching = false
     this._dispatching = false
-    this._subscribers.forEach(sub => sub(mutation, state))
+    this._subscribers.forEach(sub => sub(mutation, this.state))
   }
   }
 
 
   call (type, payload, cb) {
   call (type, payload, cb) {
@@ -177,14 +171,14 @@ class Store {
       subs.push(fn)
       subs.push(fn)
     }
     }
     return () => {
     return () => {
-      let i = subs.indexOf(fn)
+      const i = subs.indexOf(fn)
       if (i > -1) {
       if (i > -1) {
         subs.splice(i, 1)
         subs.splice(i, 1)
       }
       }
     }
     }
   }
   }
 
 
-  update (newOptions) {
+  hotUpdate (newOptions) {
     this._actions = Object.create(null)
     this._actions = Object.create(null)
     this._mutations = Object.create(null)
     this._mutations = Object.create(null)
     const options = this._options
     const options = this._options
@@ -200,11 +194,82 @@ class Store {
       }
       }
     }
     }
     this.module([], options, true)
     this.module([], options, true)
+
+    // update getters
+    const getters = extractModuleGetters(newOptions.getters || {}, newOptions.modules)
+    if (Object.keys(getters).length) {
+      const oldVm = this._vm
+      initStoreState(this, this.state, getters)
+      if (this.strict) {
+        enableStrictMode(this)
+      }
+      // trigger changes in all subscribed watchers
+      // to force getter re-evaluation.
+      this._dispatching = true
+      oldVm.state = null
+      this._dispatching = false
+      Vue.nextTick(() => oldVm.$destroy())
+    }
   }
   }
 }
 }
 
 
+function initStoreState (store, state, getters) {
+  // bind getters
+  store.getters = {}
+  const computed = {}
+  Object.keys(getters).forEach(key => {
+    const fn = getters[key]
+    // use computed to leverage its lazy-caching mechanism
+    computed[key] = () => fn(store._vm.state)
+    Object.defineProperty(store.getters, key, {
+      get: () => store._vm[key]
+    })
+  })
+
+  // use a Vue instance to store the state tree
+  // suppress warnings just in case the user has added
+  // some funky global mixins
+  const silent = Vue.config.silent
+  Vue.config.silent = true
+  store._vm = new Vue({
+    data: { state },
+    computed
+  })
+  Vue.config.silent = silent
+}
+
+function extractModuleGetters (getters, modules, path = []) {
+  if (!modules) return
+  Object.keys(modules).forEach(key => {
+    const module = modules[key]
+    if (module.getters) {
+      Object.keys(module.getters).forEach(getterKey => {
+        const rawGetter = module.getters[getterKey]
+        if (getters[getterKey]) {
+          console.warn(`[vuex] duplicate getter key: ${getterKey}`)
+          return
+        }
+        getters[getterKey] = state => rawGetter(getNestedState(state, path))
+      })
+    }
+    extractModuleGetters(getters, module.modules, path.concat(key))
+  })
+}
+
+function enableStrictMode (store) {
+  store._vm.watch('state', () => {
+    if (!store._dispatching) {
+      throw new Error(
+        '[vuex] Do not mutate vuex store state outside mutation handlers.'
+      )
+    }
+  }, { deep: true, sync: true })
+}
+
 function bind (fn, ctx) {
 function bind (fn, ctx) {
-  return () => fn.apply(ctx, arguments)
+  return function () {
+    return fn.apply(ctx, arguments)
+  }
 }
 }
 
 
 function isObject (obj) {
 function isObject (obj) {
@@ -237,5 +302,7 @@ if (typeof window !== 'undefined' && window.Vue) {
 
 
 export default {
 export default {
   Store,
   Store,
-  install
+  install,
+  mapGetters,
+  mapActions
 }
 }

+ 1 - 1
src/plugins/devtool.js

@@ -11,7 +11,7 @@ export default function devtoolPlugin (store) {
     store.replaceState(targetState)
     store.replaceState(targetState)
   })
   })
 
 
-  store.subscribe(mutation, state) => {
+  store.subscribe((mutation, state) => {
     hook.emit('vuex:mutation', mutation, state)
     hook.emit('vuex:mutation', mutation, state)
   })
   })
 }
 }

+ 0 - 10
src/plugins/strict.js

@@ -1,10 +0,0 @@
-export default store => {
-  const Watcher = getWatcher(store._vm)
-  store._vm.watch('state', () => {
-    if (!store._dispatching) {
-      throw new Error(
-        '[vuex] Do not mutate vuex store state outside mutation handlers.'
-      )
-    }
-  }, { deep: true, sync: true })
-}