Explorar o código

make getters globally cached

Evan You %!s(int64=9) %!d(string=hai) anos
pai
achega
0c01cb75bf

+ 3 - 7
examples/chat/components/MessageSection.vue

@@ -15,18 +15,14 @@
 <script>
 import Message from './Message.vue'
 import { sendMessage } from '../vuex/actions'
+import { currentThread, currentMessages } from '../vuex/getters'
 
 export default {
   components: { Message },
   vuex: {
     getters: {
-      thread ({ currentThreadID, threads }) {
-        return currentThreadID ? threads[currentThreadID] : {}
-      },
-      messages ({ messages }) {
-        const messageIds = this.thread.messages
-        return messageIds && messageIds.map(id => messages[id])
-      }
+      thread: currentThread,
+      messages: currentMessages
     },
     actions: {
       sendMessage

+ 2 - 4
examples/chat/components/Thread.vue

@@ -1,7 +1,7 @@
 <template>
   <li
     class="thread-list-item"
-    :class="{ active: isCurrentThread }"
+    :class="{ active: thread.id === currentThreadID }"
     @click="switchThread(thread.id)">
     <h5 class="thread-name">{{ thread.name }}</h5>
     <div class="thread-time">
@@ -20,9 +20,7 @@ export default {
   props: ['thread'],
   vuex: {
     getters: {
-      isCurrentThread ({ currentThreadID }) {
-        return this.thread.id === currentThreadID
-      }
+      currentThreadID: state => state.currentThreadID
     },
     actions: {
       switchThread

+ 12 - 0
examples/chat/vuex/getters.js

@@ -0,0 +1,12 @@
+export function currentThread (state) {
+  return state.currentThreadID
+    ? state.threads[state.currentThreadID]
+    : {}
+}
+
+export function currentMessages (state) {
+  const thread = currentThread(state)
+  return thread.messages
+    ? thread.messages.map(id => state.messages[id])
+    : []
+}

+ 4 - 7
src/index.js

@@ -1,8 +1,9 @@
-import { mergeObjects, deepClone } from './util'
+import { mergeObjects, deepClone, getWatcher } from './util'
 import devtoolMiddleware from './middlewares/devtool'
 import override from './override'
 
 let Vue
+let uid = 0
 
 class Store {
 
@@ -22,6 +23,7 @@ class Store {
     middlewares = [],
     strict = false
   } = {}) {
+    this._getterCacheId = 'vuex_store_' + uid++
     this._dispatching = false
     this._rootMutations = this._mutations = mutations
     this._modules = modules
@@ -184,12 +186,7 @@ class Store {
    */
 
   _setupMutationCheck () {
-    // a hack to get the watcher constructor from older versions of Vue
-    // mainly because the public $watch method does not allow sync
-    // watchers.
-    const unwatch = this._vm.$watch('__vuex__', a => a)
-    const Watcher = this._vm._watchers[0].constructor
-    unwatch()
+    const Watcher = getWatcher(this._vm)
     /* eslint-disable no-new */
     new Watcher(this._vm, '$data', () => {
       if (!this._dispatching) {

+ 39 - 7
src/override.js

@@ -1,3 +1,5 @@
+import { getWatcher, getDep } from './util'
+
 export default function (Vue) {
   // override init and inject vuex init procedure
   const _init = Vue.prototype._init
@@ -38,28 +40,58 @@ export default function (Vue) {
       if (getters) {
         options.computed = options.computed || {}
         for (let key in getters) {
-          options.computed[key] = makeBoundGetter(getters[key])
+          defineVuexGetter(this, key, getters[key])
         }
       }
       // actions
       if (actions) {
         options.methods = options.methods || {}
         for (let key in actions) {
-          options.methods[key] = makeBoundAction(actions[key])
+          options.methods[key] = makeBoundAction(actions[key], this.$store)
         }
       }
     }
   }
 
-  function makeBoundGetter (getter) {
-    return function vuexBoundGetter () {
-      return getter.call(this, this.$store.state)
+  function defineVuexGetter (vm, key, getter) {
+    Object.defineProperty(vm, key, {
+      enumerable: true,
+      configurable: true,
+      get: makeComputedGetter(vm.$store, getter)
+    })
+  }
+
+  function makeComputedGetter (store, getter) {
+    const id = store._getterCacheId
+    // cached
+    if (getter[id]) {
+      return getter[id]
+    }
+    const vm = store._vm
+    const Watcher = getWatcher(vm)
+    const Dep = getDep(vm)
+    const watcher = new Watcher(
+      vm,
+      state => getter(state),
+      null,
+      { lazy: true }
+    )
+    const computedGetter = () => {
+      if (watcher.dirty) {
+        watcher.evaluate()
+      }
+      if (Dep.target) {
+        watcher.depend()
+      }
+      return watcher.value
     }
+    getter[id] = computedGetter
+    return computedGetter
   }
 
-  function makeBoundAction (action) {
+  function makeBoundAction (action, store) {
     return function vuexBoundAction (...args) {
-      return action.call(this, this.$store, ...args)
+      return action.call(this, store, ...args)
     }
   }
 

+ 23 - 0
src/util.js

@@ -47,3 +47,26 @@ export function deepClone (obj) {
     return obj
   }
 }
+
+/**
+ * Hacks to get access to Vue internals.
+ * Maybe we should expose these...
+ */
+
+let Watcher
+export function getWatcher (vm) {
+  if (!Watcher) {
+    const unwatch = vm.$watch('__vuex__', a => a)
+    Watcher = vm._watchers[0].constructor
+    unwatch()
+  }
+  return Watcher
+}
+
+let Dep
+export function getDep (vm) {
+  if (!Dep) {
+    Dep = vm._data.__ob__.dep.constructor
+  }
+  return Dep
+}