Это 124-я оригинальная статья без воды.Если вы хотите получить больше оригинальных статей, выполните поиск в официальном аккаунте и подпишитесь на нас~ Эта статья была впервые опубликована в блоге Zhengcaiyun:Принцип реализации LRU-кэш-поддержки
предисловие
Я полагаю, что у большинства студентов более или менее будет сцена, в которой состояние компонента нужно сохранять, а не перерисовывать при разработке повседневных потребностей.Студенты, знакомые с vue, обязательно придумают это.keep-alive Это встроенный компонент.
так что жеkeep-aliveШерстяная ткань?
keep-aliveявляется встроенным компонентом Vue.js. Он может хранить экземпляры неактивных компонентов в памяти, а не уничтожать их напрямую. Это абстрактный компонент, который не будет отображаться в реальном DOM и не появится в цепочке родительских компонентов. Проще говоря, keep-alive используется для сохранения состояния рендеринга компонентов, предотвращения повторного создания и рендеринга компонентов и эффективного повышения производительности системы.keep-aliveизmaxАтрибуты для ограничения количества кэшируемых экземпляров компонента, когда это число достигает верхнего предела, перед созданием нового экземпляра пример, в котором к кэшированному компоненту не было доступа, и используемый здесь механизм кэшированияАлгоритм LRU
Алгоритм устранения кеша LRU
LRU (наименее недавно использовавшийся) отсеивает данные на основе своей истории, сосредотачиваясь наЗащитите недавно использованные/использованные данные и удалите данные, к которым не обращались в течение длительного времени на этом этапе.
Основная идея LRU заключается в том, что если доступ к данным был осуществлен недавно, то вероятность обращения к ним в будущем также выше.
- Новые данные вставляются в конец связанного списка;
- Всякий раз, когда происходит попадание в кеш (то есть доступ к кэшированным данным), данные перемещаются в конец связанного списка.
- Когда связанный список заполнен, данные в начале связанного списка отбрасываются.
Структура данных, реализующая LRU
Обычно используется классический LRU.
hashMap+双向链表. Учитывая, что может быть необходимо часто удалять элемент и указывать предыдущий узел этого элемента на следующий узел, наиболее целесообразно использовать двойную ссылку. И он хранится в хронологическом порядке самых последних использованных узлов. Если узел посещается, у нас есть основания полагать, что его вероятность быть посещенным в следующий период больше, чем у других узлов.
Но поскольку он использовался в jsMapТеперь вы будете использовать готовый итератор для получения значения ключа следующего узла (keys().next(
))
// ./LRU.ts
export class LRUCache {
capacity: number; // 容量
cache: Map<number, number | null>; // 缓存
constructor(capacity: number) {
this.capacity = capacity;
this.cache = new Map();
}
get(key: number): number {
if (this.cache.has(key)) {
let temp = this.cache.get(key) as number;
//访问到的 key 若在缓存中,将其提前
this.cache.delete(key);
this.cache.set(key, temp);
return temp;
}
return -1;
}
put(key: number, value: number): void {
if (this.cache.has(key)) {
this.cache.delete(key);
//存在则删除,if 结束再提前
} else if (this.cache.size >= this.capacity) {
// 超过缓存长度,淘汰最近没使用的
this.cache.delete(this.cache.keys().next().value);
console.log(`refresh: key:${key} , value:${value}`)
}
this.cache.set(key, value);
}
toString(){
console.log('capacity',this.capacity)
console.table(this.cache)
}
}
// ./index.ts
import {LRUCache} from './lru'
const list = new LRUCache(4)
list.put(2,2) // 入 2,剩余容量3
list.put(3,3) // 入 3,剩余容量2
list.put(4,4) // 入 4,剩余容量1
list.put(5,5) // 入 5,已满 从头至尾 2-3-4-5
list.put(4,4) // 入4,已存在 ——> 置队尾 2-3-5-4
list.put(1,1) // 入1,不存在 ——> 删除队首 插入1 3-5-4-1
list.get(3) // 获取3,刷新3——> 置队尾 5-4-1-3
list.toString()
// ./index.ts
import {LRUCache} from './lru'
const list = new LRUCache(4)
list.put(2,2) // 入 2,剩余容量3
list.put(3,3) // 入 3,剩余容量2
list.put(4,4) // 入 4,剩余容量1
list.put(5,5) // 入 5,已满 从头至尾 2-3-4-5
list.put(4,4) // 入4,已存在 ——> 置队尾 2-3-5-4
list.put(1,1) // 入1,不存在 ——> 删除队首 插入1 3-5-4-1
list.get(3) // 获取3,刷新3——> 置队尾 5-4-1-3
list.toString()
Результат выглядит следующим образом:
Keep-Alive в vue
принцип
- Используйте механизм кэширования LRU для кэширования, max ограничивает максимальную емкость кэш-таблицы.
- В соответствии с набором include/exclude (если есть) выполняется условное сопоставление, чтобы решить, нужно ли кэшировать. Не соответствует, напрямую возвращает экземпляр компонента
- Создайте ключ кэша на основе идентификатора компонента и тега и проверьте, был ли экземпляр компонента кэширован в объекте кэша. Если он существует, напрямую извлеките кэшированное значение и обновите позицию ключа в this.keys (обновление позиции ключа является ключом к реализации стратегии замены LRU).
- Получите название узла или узла в соответствии с заклинанием Название текущей информации компонента CID
- Получите первый объект дочернего компонента, обернутый keep-alive, и имя его компонента
Анализ исходного кода
Инициализировать компонент keepAlive
const KeepAliveImpl: ComponentOptions = {
name: `KeepAlive`,
props: {
include: [String, RegExp, Array],
exclude: [String, RegExp, Array],
max: [String, Number],
},
setup(props: KeepAliveProps, { slots }: SetupContext) {
// 初始化数据
const cache: Cache = new Map();
const keys: Keys = new Set();
let current: VNode | null = null;
// 当 props 上的 include 或者 exclude 变化时移除缓存
watch(
() => [props.include, props.exclude],
([include, exclude]) => {
include && pruneCache((name) => matches(include, name));
exclude && pruneCache((name) => !matches(exclude, name));
},
{ flush: "post", deep: true }
);
// 缓存组件的子树 subTree
let pendingCacheKey: CacheKey | null = null;
const cacheSubtree = () => {
// fix #1621, the pendingCacheKey could be 0
if (pendingCacheKey != null) {
cache.set(pendingCacheKey, getInnerChild(instance.subTree));
}
};
// KeepAlive 组件的设计,本质上就是空间换时间。
// 在 KeepAlive 组件内部,
// 当组件渲染挂载和更新前都会缓存组件的渲染子树 subTree
onMounted(cacheSubtree);
onUpdated(cacheSubtree);
onBeforeUnmount(() => {
// 卸载缓存表里的所有组件和其中的子树...
}
return ()=>{
// 返回 keepAlive 实例
}
}
}
return ()=>{
// 省略部分代码,以下是缓存逻辑
pendingCacheKey = null
const children = slots.default()
let vnode = children[0]
const comp = vnode.type as Component
const name = getName(comp)
const { include, exclude, max } = props
// key 值是 KeepAlive 子节点创建时添加的,作为缓存节点的唯一标识
const key = vnode.key == null ? comp : vnode.key
// 通过 key 值获取缓存节点
const cachedVNode = cache.get(key)
if (cachedVNode) {
// 缓存存在,则使用缓存装载数据
vnode.el = cachedVNode.el
vnode.component = cachedVNode.component
if (vnode.transition) {
// 递归更新子树上的 transition hooks
setTransitionHooks(vnode, vnode.transition!)
}
// 阻止 vNode 节点作为新节点被挂载
vnode.shapeFlag |= ShapeFlags.COMPONENT_KEPT_ALIVE
// 刷新key的优先级
keys.delete(key)
keys.add(key)
} else {
keys.add(key)
// 属性配置 max 值,删除最久不用的 key ,这很符合 LRU 的思想
if (max && keys.size > parseInt(max as string, 10)) {
pruneCacheEntry(keys.values().next().value)
}
}
// 避免 vNode 被卸载
vnode.shapeFlag |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
current = vnode
return vnode;
}
Переместить компоненты из кэш-таблицы
// 遍历缓存表
function pruneCache(filter?: (name: string) => boolean) {
cache.forEach((vnode, key) => {
const name = getComponentName(vnode.type as ConcreteComponent);
if (name && (!filter || !filter(name))) {
// !filter(name) 即 name 在 includes 或不在 excludes 中
pruneCacheEntry(key);
}
});
}
// 依据 key 值从缓存表中移除对应组件
function pruneCacheEntry(key: CacheKey) {
const cached = cache.get(key) as VNode;
if (!current || cached.type !== current.type) {
/* 当前没有处在 activated 状态的组件
* 或者当前处在 activated 组件不是要删除的 key 时
* 卸载这个组件
*/
unmount(cached); // unmount方法里同样包含了 resetShapeFlag
} else if (current) {
// 当前组件在未来应该不再被 keepAlive 缓存
// 虽然仍在 keepAlive 的容量中但是需要刷新当前组件的优先级
resetShapeFlag(current);
// resetShapeFlag
}
cache.delete(key);
keys.delete(key);
}
function resetShapeFlag(vnode: VNode) {
let shapeFlag = vnode.shapeFlag; // shapeFlag 是 VNode 的标识
// ... 清除组件的 shapeFlag
}
случай поддержания активности
В этом разделе будут использоваться новые функции vue 3.x для имитацииkeep-aliveконкретные сценарии применения
В index.vue мы представили CountUp, таймер и ColorRandom с тремя компонентами состояния.
вместимостью 2<keep-alive>оборачивает динамический компонент в
// index.vue
<script setup>
import { ref } from "vue"
import CountUp from '../components/CountUp.vue'
import ColorRandom from '../components/ColorRandom.vue'
import Timer from '../components/Timer.vue'
const tabs = ref([ // 组件列表
{
title: "ColorPicker",
comp: ColorRandom,
},
{
title: "timer1",
comp: Timer,
},
{
title: "timer2",
comp: Timer,
},
{
title: "CountUp",
comp: CountUp,
},
])
const currentTab = ref(tabs.value[0]) // tab 默认展示第一个组件
const tabSwitch = (tab) => {
currentTab.value = tab
}
</script>
<template>
<div id="main-page">keep-alive demo below</div>
<div class="tab-group">
<button
v-for="tab in tabs"
:key="tab"
:class="['tab-button', { active: currentTab === tab }]"
@click="tabSwitch(tab)"
>
{{ tab.title }}
</button>
</div>
<keep-alive max="2">
<!-- 动态组件渲染 tab 当前的组件 -->
<component
v-if="currentTab"
:is="currentTab.comp"
:key="currentTab.title"
:name="currentTab.title"
/>
</keep-alive>
</template>
состояние кэша
Процесс кэширования выглядит следующим образом:
можно увидеть завернутым вkeep-aliveДинамический компонент кэша кэширует состояние предыдущего компонента.
Наблюдая за изменениями узлов в vue devtools, вы можете видеть, что keepAlive содержитColorRandomа такжеTimerДва компонента, текущий отображаемый компонент будет в активированном состоянии, а другие кэшированные компоненты будут в активированном состоянии.inactivatedположение дел
Если мы аннотируем обаkeep-aliveВы обнаружите, что независимо от того, как вы переключаете компоненты, они будут только повторно отображаться и не сохранят предыдущее состояние.
удалить компоненты
Процесс удаления выглядит следующим образом:
Чтобы проверить, можно ли успешно удалить компонент при переключении вкладок, в каждом компонентеonUnmountedдобавлен журнал
onUnmounted(()=>{
console.log(`${props.name} 组件被卸载`)
})
- Когда длина кэшированных данных меньше или равна max , переключение компонентов не приведет к удалению других компонентов, как показано выше в vue devtools, оно только вызовет запуск компонента.
activatedа такжеdeactivatedдва жизненных цикла - Если в это время длина кэшированных данных больше max, компонент с более низким приоритетом будет удален из списка кэшей, и соответственно будет виден компонент, который будет удален первым.
umountedТриггеры жизненного цикла.
оптимизация производительности
После использования KeepAlive vnode и DOM компонента, обернутого KeepAlive, будут кэшироваться после первого рендеринга, а затем при следующем повторном рендеринге компонента соответствующие vnode и DOM будут получены непосредственно из кеша, а затем выполняется рендеринг. не нужно проходить ряд процессов, таких как инициализация компонента, рендеринг и исправление, что сокращает время выполнения скрипта и повышает производительность.
Суммировать
Vue внутренне абстрагирует узлы DOM в узлы VNode, а кеш компонентов поддержки активности также основан на узлах VNode вместо непосредственного хранения структуры DOM. Он кэширует компоненты, которые соответствуют условиям (включают и исключают) в объекте кеша, а затем берет узел vnode из объекта кеша и визуализирует его, когда требуется повторный рендеринг.
Конкретный процесс кэширования выглядит следующим образом:
-
Объявите упорядоченный набор ключей в качестве контейнера кеша и сохраните уникальное значение ключа компонента.
-
В ключах контейнера кеша чем выше значение ключа, тем меньше доступа и приоритета, которые нужно исключить.
-
Когда функция рендеринга выполняется, если кеш попал, текущая нажатая клавиша удаляется из клавиш, а значение ключа добавляется в конец клавиш, чтобы обновить приоритет ключа.
-
Когда кеш не попадает, ключи добавляют значение ключа данных кеша.Если длина данных кеша больше максимального значения, самые старые данные будут удалены.
-
При запуске жизненного цикла beforeMount/update кэшировать данные поддерева текущего активированного компонента
Ссылаться на
- Алгоритм удаления кеша - Алгоритм LRU (знание)
- Документация по набору карт (MDN)
- Введение в структуру данных кэша LRU
Рекомендуемое чтение
- Руководство по разработке плагинов для Sketch
- Почему не рекомендуется использовать индекс в качестве ключа в Vue
- Анализ технической схемы и реализация записи веб-экрана
работы с открытым исходным кодом
- Zhengcaiyun интерфейсный таблоид
адрес с открытым исходным кодомwww.zoo.team/openweekly/(На главной странице официального сайта таблоида есть группа обмена WeChat)
- Плагин выбора товара
адрес с открытым исходным кодомGitHub.com/Chinese Patent Medicine-Inc/Reservoir…
Карьера
ZooTeam, молодая, увлеченная и творческая команда, связанная с отделом исследований и разработок продукции Zhengcaiyun, базируется в живописном Ханчжоу. В настоящее время в команде более 60 фронтенд-партнеров, средний возраст которых составляет 27 лет, и почти 40% из них — инженеры полного стека, настоящая молодежная штурмовая группа. В состав членов входят «ветераны» солдат из Ali и NetEase, а также первокурсники из Чжэцзянского университета, Университета науки и технологий Китая, Университета Хандянь и других школ. В дополнение к ежедневным деловым связям, команда также проводит технические исследования и фактические боевые действия в области системы материалов, инженерной платформы, строительной платформы, производительности, облачных приложений, анализа и визуализации данных, а также продвигает и внедряет ряд внутренних технологий. Откройте для себя новые горизонты передовых технологических систем.
Если вы хотите измениться, вас забрасывают вещами, и вы надеетесь начать их бросать; если вы хотите измениться, вам сказали, что вам нужно больше идей, но вы не можете сломать игру; если вы хотите изменить , у вас есть возможность добиться этого результата, но вы не нужны; если вы хотите изменить то, чего хотите достичь, вам нужна команда для поддержки, но вам некуда вести людей; если вы хотите изменить установившийся ритм, это будет "5 лет рабочего времени и 3 года стажа работы"; если вы хотите изменить исходный Понимание хорошее, но всегда есть размытие того слоя оконной бумаги.. , Если вы верите в силу веры, верьте, что обычные люди могут достичь необыкновенных вещей, и верьте, что они могут встретить лучшего себя. Если вы хотите участвовать в процессе становления бизнеса и лично способствовать росту фронтенд-команды с глубоким пониманием бизнеса, надежной технической системой, технологиями, создающими ценность, и побочным влиянием, я думаю, что мы должны говорить. В любое время, ожидая, пока вы что-нибудь напишете, отправьте это наZooTeam@cai-inc.com