美文网首页
keep-alive

keep-alive

作者: 5cc9c8608284 | 来源:发表于2022-12-17 19:06 被阅读0次

面试题:请阐述keep-alive组件的作用和原理

keep-alive组件是vue的内置组件,用于缓存内部组件实例。这样做的目的在于,keep-alive内部的组件切回时,不用重新创建组件实例,而直接使用缓存中的实例,一方面能够避免创建组件带来的开销,另一方面可以保留组件的状态。

keep-alive具有include和exclude属性,通过它们可以控制哪些组件进入缓存。另外它还提供了max属性,通过它可以设置最大缓存数,当缓存的实例超过该数时,vue会移除最久没有使用的组件缓存。

受keep-alive的影响,其内部所有嵌套的组件都具有两个生命周期钩子函数,分别是activateddeactivated,它们分别在组件激活和失活时触发。第一次activated触发是在mounted之后

在具体的实现上,keep-alive在内部维护了一个key数组和一个缓存对象

// keep-alive 内部的声明周期函数
created () {
  this.cache = Object.create(null)
  this.keys = []
}

key数组记录目前缓存的组件key值,如果组件没有指定key值,则会为其自动生成一个唯一的key值

cache对象以key值为键,vnode为值,用于缓存组件对应的虚拟DOM

在keep-alive的渲染函数中,其基本逻辑是判断当前渲染的vnode是否有对应的缓存,如果有,从缓存中读取到对应的组件实例;如果没有则将其缓存。

当缓存数量超过max数值时,keep-alive会移除掉key数组的第一个元素

render(){
  const slot = this.$slots.default; // 获取默认插槽
  const vnode = getFirstComponentChild(slot); // 得到插槽中的第一个组件的vnode
  const name = getComponentName(vnode.componentOptions); //获取组件名字
  const { cache, keys } = this; // 获取当前的缓存对象和key数组
  const key = ...; // 获取组件的key值,若没有,会按照规则自动生成
  if (cache[key]) {
    // 有缓存
    // 重用组件实例
    vnode.componentInstance = cache[key].componentInstance
    remove(keys, key); // 删除key
    // 将key加入到数组末尾,这样是为了保证最近使用的组件在数组中靠后,反之靠前
    keys.push(key); 
  } else {
    // 无缓存,进行缓存
    cache[key] = vnode
    keys.push(key)
    if (this.max && keys.length > parseInt(this.max)) {
      // 超过最大缓存数量,移除第一个key对应的缓存
      pruneCacheEntry(cache, keys[0], keys, this._vnode)
    }
  }
  return vnode;
}

缺点:keep-alive的代价就是占用了更多的内存空间,当组件比较大时,里面可能可能会包含很多内容,这样会导致内存被占用,使用keep-alive相当于是用空间换取时间.
案例:一个很常见的场景,就是做后台管理系统的时候,可能某个页面有较多的需要输入的内容,当时当我们填写到一半的时候,离开了当前页面,去做了别的事情,再次回到该页面的时候,之前输入的内容都没了,这个时候我们的keep-alive就闪亮登场了,以下案例就是keep-alive的一个简单应用:
App.vue页面

<template>
  <div id="app">
    <!-- 左侧导航 -->
    <div class="leftmenu">
      <ul>
        <li v-for="(menu,index) in meuns" :key="menu.path">
          <router-link tag="span" :to="{name:menu.name}">{{menu.name}}</router-link>
          <button @click="addPage(menu)">+</button>
        </li>
      </ul>
    </div>
    <div class="right-content">
      <div>
        <h2>选项卡</h2>
        <ul style="display:flex">
          <li v-for="(item,index) in activeTabs" :key="index">
            <router-link tag="span" :to="{name:item}">{{item}}</router-link>
          </li>
        </ul>
      </div>
      <keep-alive>
        <router-view></router-view>
      </keep-alive>
    </div>
  </div>
</template>
<script>
export default {
  name: "App",
  data() {
    return {};
  },
  mounted() {
    console.log(this.$store.state.tabs.currentActiveTabNames, "router");
  },
  computed: {
    meuns() {
      return this.$router.options.routes || [];
    },
    activeTabs() {
      return this.$store.state.tabs.currentActiveTabNames || [];
    }
  },
  methods: {
    addPage(menu) {
      console.log(menu, "menu");
      this.$store.commit("tabs/addPage", menu.name);
    }
  }
};
</script>
<style lang="scss">
li {
  list-style: none;
}
ul {
  margin: 0;
  padding: 0;
}
.leftmenu {
  height: 100vh;
  width: 200px;
  background-color: rgb(4, 29, 54);
  li {
    width: 100%;
    line-height: 45px;
    color: white;
    text-align: center;
  }
}
#app {
  display: flex;
}
.right-content {
  padding-left: 15px;
  width: calc(100vw - 200px);
}
span {
  padding: 0 5px;
}
</style>

Page1.vue

<template>
  <div>
    <h1>Page1</h1>
    <input type="text">
    <button @click="count++">+</button>
    <span>{{count}}</span>
    <button @click="count--">-</button>
  </div>
</template>

<script>
export default {
 name:"Page1",
 data(){
    return {
       count:0 
    }
 }
}
</script>

page2和page3的内容和page1是一样的,只不过将h1的内容改成了page2和page3,
router/index.js

import Vue from 'vue'
import VueRouter from 'vue-router'
import Page1 from '../views/Page1.vue'
import Page2 from '../views/Page2.vue'
import Page3 from '../views/Page3.vue'

Vue.use(VueRouter)
const routes = [
  {
    path: '/page1',
    name: 'Page1',
    component: Page1
  },
  {
    path: '/page2',
    name: 'Page2',
    component: Page2
  },
  {
    path: '/page3',
    name: 'Page3',
    component: Page3
  },
]

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})

export default router

store/index.js

import Vue from 'vue'
import Vuex from 'vuex'
import tabs from './tabs.js'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
  },
  getters: {
  },
  mutations: {
  },
  actions: {
  },
  modules: {
    tabs
  }
})

store/tabs.js

export default{
    namespaced:true,
    state:{
        currentActiveTabNames:[]
    },
    mutations:{
        addPage(state,newPageName){
            if(!newPageName) return;
            if(!state.currentActiveTabNames.includes(newPageName)){
                state.currentActiveTabNames.push(newPageName)
            }
        },
        removePage(state,pageName){
            //找到在数组中的索引
            let index=state.currentActiveTabNames.indexOf(pageName);
            index>=0&&state.currentActiveTabNames.splice(index,1);
        }
    }
}

main.js

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

Vue.config.productionTip = false


new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

这样被保存在选项卡中的tab页就可以实现缓存的功能,比如下图中的page2和page3,当我们page3选项卡下输入了内容,并点击+号,把count增加到3,这时我们切换到其他页面,再回到page3的时候,之前输入的内容还是完好无损的保留在页面中.


image.png

相关文章

网友评论

      本文标题:keep-alive

      本文链接:https://www.haomeiwen.com/subject/yqzjqdtx.html