CD's blog CD's blog
首页
  • HTMLCSS
  • JavaScript
  • Vue
  • TypeScript
  • React
  • Node
  • Webpack
  • Git
  • Nestjs
  • 小程序
  • 浏览器网络
  • 学习笔记

    • 《TypeScript 从零实现 axios》
    • Webpack笔记
  • JS/TS教程

    • 《现代JavaScript》教程
🔧工具方法
  • 网站
  • 资源
  • Vue资源
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

CD_wOw

内卷的行情,到不了的梦
首页
  • HTMLCSS
  • JavaScript
  • Vue
  • TypeScript
  • React
  • Node
  • Webpack
  • Git
  • Nestjs
  • 小程序
  • 浏览器网络
  • 学习笔记

    • 《TypeScript 从零实现 axios》
    • Webpack笔记
  • JS/TS教程

    • 《现代JavaScript》教程
🔧工具方法
  • 网站
  • 资源
  • Vue资源
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Vue2基础及原理

    • Vue2的生命周期
    • Vue2实用方法及原理
    • vue2 的 keepAlive 实现原理
      • vue 的 keepAlive 实现
    • Vue2的extend与手动挂载$mount
    • Vue2如何做同构SSR
    • Vue2.x之$set是怎么实现的
    • vue2在TypeScript中如何使用vuex
    • Vue2.x中一些技巧及痛点问题解决方法
    • Vue2实现原理
    • Vue2 题解QA
  • Vue3基础及原理

  • 周边工具

  • Vue笔记
  • Vue2基础及原理
CD
2021-07-08
目录

vue2 的 keepAlive 实现原理

# vue 的 keepAlive 实现

keepAlive 的实现简单来说就是通过拿到在 keepalive 中渲染的 vue 对象,并判断其是否需要被缓存,若需要则拿到这个 vue 对象内一些独一无二的值作为存储的键,并将该 vue 对象作为值存入缓存的对象中,再次访问时去缓存中查找是否有该对象,有的话就将其抛出,没有的话便渲染新的就完事了。是个人都能看懂所以直接就放源码了。

// type CacheEntry = {
//   name: ?string;
//   tag: ?string;
//   componentInstance: Component;
// };

function isDef(v) {
  return v !== undefined && v !== null;
}

function isAsyncPlaceholder(node) {
  return node.isComment && node.asyncFactory;
}

function isRegExp(v) {
  return Object.prototype.toString.call(v) === "[object RegExp]";
}

function remove(arr, item) {
  if (arr.length) {
    const index = arr.indexOf(item);
    if (index > -1) {
      return arr.splice(index, 1);
    }
  }
}

function getFirstComponentChild(children) {
  if (Array.isArray(children)) {
    for (let i = 0; i < children.length; i++) {
      const c = children[i];
      if (isDef(c) && (isDef(c.componentOptions) || isAsyncPlaceholder(c))) {
        return c;
      }
    }
  }
}

// type CacheEntryMap = { [key: string]: ?CacheEntry };
function matches(pattern /** Includes or Excludes */, name /** string */) {
  if (Array.isArray(pattern)) {
    return pattern.includes(name) > -1;
  } else if (typeof pattern === "string") {
    return pattern.split(",").indexOf(name) > -1;
  } else if (isRegExp(pattern)) {
    return pattern.test(name);
  }
  return false;
}
function getComponentName(opts) {
  return opts && (opts.Ctor.name || opts.tag);
}
function pruneCache(keepAliveInstance /** this */, filterFn /** Function */) {
  const { cache, keys, _vnode } = keepAliveInstance;
  for (const key in cache) {
    if (Object.hasOwnProperty.call(cache, key)) {
      const entry = cache[key];
      if (entry) {
        const name = entry.name; // ''
        // 如果名字存在但是并不在缓存数组内,则销毁并移除出缓存
        if (name && !filterFn(name)) {
          pruneCacheEntry(cache, key, keys, _vnode);
        }
      }
    }
  }
}
function pruneCacheEntry(cache, key, keys, current /** VNode */) {
  const entry = cache[key];
  if (entry && (!current || entry.tag !== current.tag)) {
    entry.componentInstance.$destory(); // 销毁组件
  }
  cache[key] = null;
  remove(keys, key);
}
export default {
  name: "KeepAliveNext",
  abstract: true, // 所有路由模型选项将被简化成一个单个的 mode 选项
  props: {
    includes: [String, RegExp, Array],
    exclude: [String, RegExp, Array],
    max: {
      type: [String, Number],
      default: 8, // 这里默认最多缓存8个页面
    },
  },
  created() {
    this.cache = Object.create(null);
    this.keys = [];
  },
  mounted() {
    console.log("mounted");
    this.cacheVNode();
    this.$watch("include", (val) => {
      pruneCache(this, (name) => matches(val, name));
    });
    this.$watch("exclude", (val) => {
      pruneCache(this, (name) => !matches(val, name));
    });
  },
  updated() {
    this.cacheVNode();
  },
  destroyed() {
    // 销毁所有组件 清空缓存
    for (const key in this.cache) {
      pruneCacheEntry(this.cache, key, this.keys);
    }
  },
  methods: {
    cacheVNode() {
      const { cache, keys, vnodeToCache, keyToCache } = this;
      console.log(cache, keys, vnodeToCache, keyToCache);
      if (vnodeToCache) {
        const { tag, componentInstance, componentOptions } = vnodeToCache;
        // CacheEntry
        cache[keyToCache] = {
          name: getComponentName(componentOptions),
          tag,
          componentInstance,
        };
        keys.push(keyToCache);
        // 超出长度限制时把最先缓存的内容移除
        if (this.max && keys.length > parseInt(this.max)) {
          pruneCacheEntry(cache, keys[0], keys, this._vnode);
        }
        this.vnodeToCache = null;
      }
    },
  },
  render() {
    const slot = this.$slot.default;
    const vnode = getFirstComponentChild(slot); // 找到最外层第一个子组件vnode
    const componentOptions = vnode && vnode.componentOptions;
    if (componentOptions) {
      const name = getComponentName(componentOptions);
      const { include, exclude } = this;
      if (
        // 不存在includes里 或是存在excludes里时,返回新的vnode,否则从缓存中拿
        (include && (!name || !matches(include, name))) ||
        (exclude && name && matches(exclude, name))
      ) {
        return vnode;
      }

      const { cache, keys } = this;
      // key 可能是 undefined 或者 null  ,js的bug == 时浅判断
      const key =
        vnode.key == null
          ? componentOptions.Ctor.cid +
            (componentOptions.tag ? `::${componentOptions.tag}` : "")
          : vnode.key;

      if (cache[key]) {
        vnode.componentInstance = cache[key].componentInstance;
        remove(keys, key);
        keys.push(key);
      } else {
        this.vnodeToCache = vnode;
        this.keyToCache = key;
      }
      vnode.data.keepAlive = true;
    }
    return vnode || (slot && slot[0]);
  },
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
编辑 (opens new window)
#Vue2
上次更新: 2023/03/27, 21:41:56
Vue2实用方法及原理
Vue2的extend与手动挂载$mount

← Vue2实用方法及原理 Vue2的extend与手动挂载$mount→

最近更新
01
gsap动画库学习笔记 - 持续~
06-05
02
远程组件加载方案笔记
05-03
03
小程序使用笔记
03-29
更多文章>
Theme by Vdoing | Copyright © 2020-2023 CD | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式