UNPKG

3.92 kBJavaScriptView Raw
1/* @flow */
2
3import { isRegExp, remove } from 'shared/util'
4import { getFirstComponentChild } from 'core/vdom/helpers/index'
5
6type CacheEntry = {
7 name: ?string;
8 tag: ?string;
9 componentInstance: Component;
10};
11
12type CacheEntryMap = { [key: string]: ?CacheEntry };
13
14function getComponentName (opts: ?VNodeComponentOptions): ?string {
15 return opts && (opts.Ctor.options.name || opts.tag)
16}
17
18function matches (pattern: string | RegExp | Array<string>, name: string): boolean {
19 if (Array.isArray(pattern)) {
20 return pattern.indexOf(name) > -1
21 } else if (typeof pattern === 'string') {
22 return pattern.split(',').indexOf(name) > -1
23 } else if (isRegExp(pattern)) {
24 return pattern.test(name)
25 }
26 /* istanbul ignore next */
27 return false
28}
29
30function pruneCache (keepAliveInstance: any, filter: Function) {
31 const { cache, keys, _vnode } = keepAliveInstance
32 for (const key in cache) {
33 const entry: ?CacheEntry = cache[key]
34 if (entry) {
35 const name: ?string = entry.name
36 if (name && !filter(name)) {
37 pruneCacheEntry(cache, key, keys, _vnode)
38 }
39 }
40 }
41}
42
43function pruneCacheEntry (
44 cache: CacheEntryMap,
45 key: string,
46 keys: Array<string>,
47 current?: VNode
48) {
49 const entry: ?CacheEntry = cache[key]
50 if (entry && (!current || entry.tag !== current.tag)) {
51 entry.componentInstance.$destroy()
52 }
53 cache[key] = null
54 remove(keys, key)
55}
56
57const patternTypes: Array<Function> = [String, RegExp, Array]
58
59export default {
60 name: 'keep-alive',
61 abstract: true,
62
63 props: {
64 include: patternTypes,
65 exclude: patternTypes,
66 max: [String, Number]
67 },
68
69 methods: {
70 cacheVNode() {
71 const { cache, keys, vnodeToCache, keyToCache } = this
72 if (vnodeToCache) {
73 const { tag, componentInstance, componentOptions } = vnodeToCache
74 cache[keyToCache] = {
75 name: getComponentName(componentOptions),
76 tag,
77 componentInstance,
78 }
79 keys.push(keyToCache)
80 // prune oldest entry
81 if (this.max && keys.length > parseInt(this.max)) {
82 pruneCacheEntry(cache, keys[0], keys, this._vnode)
83 }
84 this.vnodeToCache = null
85 }
86 }
87 },
88
89 created () {
90 this.cache = Object.create(null)
91 this.keys = []
92 },
93
94 destroyed () {
95 for (const key in this.cache) {
96 pruneCacheEntry(this.cache, key, this.keys)
97 }
98 },
99
100 mounted () {
101 this.cacheVNode()
102 this.$watch('include', val => {
103 pruneCache(this, name => matches(val, name))
104 })
105 this.$watch('exclude', val => {
106 pruneCache(this, name => !matches(val, name))
107 })
108 },
109
110 updated () {
111 this.cacheVNode()
112 },
113
114 render () {
115 const slot = this.$slots.default
116 const vnode: VNode = getFirstComponentChild(slot)
117 const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions
118 if (componentOptions) {
119 // check pattern
120 const name: ?string = getComponentName(componentOptions)
121 const { include, exclude } = this
122 if (
123 // not included
124 (include && (!name || !matches(include, name))) ||
125 // excluded
126 (exclude && name && matches(exclude, name))
127 ) {
128 return vnode
129 }
130
131 const { cache, keys } = this
132 const key: ?string = vnode.key == null
133 // same constructor may get registered as different local components
134 // so cid alone is not enough (#3269)
135 ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
136 : vnode.key
137 if (cache[key]) {
138 vnode.componentInstance = cache[key].componentInstance
139 // make current key freshest
140 remove(keys, key)
141 keys.push(key)
142 } else {
143 // delay setting the cache until update
144 this.vnodeToCache = vnode
145 this.keyToCache = key
146 }
147
148 vnode.data.keepAlive = true
149 }
150 return vnode || (slot && slot[0])
151 }
152}