Skip to content
On this page

KeepAlive基本使用

// 1.组件
const My1 = {
  name: "My1",
  setup() {
    onMounted(() => {
      console.log("my1 mounted");
    });
    return () => h("h1", "my1");
  },
};
// 2.组件
const My2 = {
  name: "My2",
  setup() {
    onMounted(() => {
      console.log("my2 mounted");
    });
    return () => h("h1", "my2");
  },
};
// keepAlive会对渲染的组件进行缓存
render(
  h(KeepAlive, null, {
    default: () => h(My1), // 缓存1
  }),
  app
);
setTimeout(() => {
  render(
    h(KeepAlive, null, {
      default: () => h(My2), // 缓存2
    }),
    app
  );
}, 1000);
setTimeout(() => {
  render(
    h(KeepAlive, null, {
      default: () => h(My1), // 复用1
    }),
    app
  );
}, 2000);

创建上下文对象,存储keepAlive组件渲染时所需的属性

const instance = {
  // 组件的实例
  ctx: {}, // instance上下文
};
export const KeepAliveImpl = {
  // keepAlive本身没有任何功能
  __isKeepAlive: true,
  setup(props, { slots }) {
    return () => {
      let vnode = slots.default();
      return vnode; // 渲染插槽的内容
    };
  },
};
export const isKeepAlive = (vnode) => vnode.type.__isKeepAlive;
const updateComponentPreRender = (instance, next) => {
  instance.next = null;
  instance.vnode = next;
  updataProps(instance, instance.props, next.props || {});

  Object.assign(instance.slots, next.children); // 渲染的时候需要更新插槽
};
const mountComponent = (vnode,container,anchor,parentComponent) =>{
     // 1) 要创造一个组件的实例
     let instance = vnode.component = createComponentInstance(vnode,parentComponent);
     if(isKeepAlive(vnode)){
         (instance.ctx as any).renderer = {
             patch,
             createElement:hostCreateElement,
             move(vnode,container){
                 hostInsert(vnode.component.subTree.el,container)
             },
             unmount
         }
     }
}

缓存组件

在渲染完毕后需要对subTree进行缓存,需要保证渲染完毕后在调用mounted事件

export const KeepAliveImpl = {
  __isKeepAlive: true,
  setup(props, { slots }) {
    const keys = new Set(); // 缓存的key
    const cache = new Map(); // 缓存key对应的虚拟节点
    const instance = getCurrentInstance();

    let pendingCacheKey = null;
    onMounted(() => {
      cache.set(pendingCacheKey, instance.subTree);
    });
    return () => {
      let vnode = slots.default();
      // 如果
      if (
        !isVnode(vnode) ||
        !(vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT)
      ) {
        return vnode;
      }
      const comp = vnode.type; // 拿到组件
      // 获取组件的key
      const key = vnode.key == null ? comp : vnode.key;
      const cacheVNode = cache.get(key);
      pendingCacheKey = key;

      if (cacheVNode) {
      } else {
        keys.add(key);
      }
      // 标识组件
      vnode.shapeFlag |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE;
      return vnode;
    };
  },
};

复用组件

export const KeepAliveImpl = {
  __isKeepAlive: true,
  setup(props, { slots }) {
    // ...
    let { createElement, move, unmount: _unmount } = instance.ctx.renderer;
    const storageContainer = createElement("div"); // 缓存盒子
    instance.ctx.activate = (vnode, container, anchor) => {
      // 激活则移动到容器中
      move(vnode, container, anchor);
    };
    instance.ctx.deactivate = (vnode) => {
      // 卸载则移动到缓存盒子中
      move(vnode, storageContainer, null);
    };
    return () => {
      // ...
      if (cacheVNode) {
        // 缓存中有
        vnode.component = cacheVNode.component; // 复用组件,并且标识不需要真正的创建
        vnode.shapeFlag |= ShapeFlags.COMPONENT_KEPT_ALIVE;
        // make this key the freshest
        keys.delete(key);
        keys.add(key);
      }
    };
  },
};
let {
  h,
  render,
  reactive,
  provide,
  inject,
  Teleport,
  defineAsyncComponent,
  KeepAlive,
  ref,
} = VueRuntimeDOM;

// keepAlive会对渲染的组件进行缓存
const state = ref(true);
const My1 = {
  render: () => h("h1", "hello"),
};
const My2 = {
  render: () => h("h1", "world"),
};
render(
  h(KeepAlive, null, {
    // 渲染My1
    default: () => h(My1),
  }),
  app
);

setTimeout(() => {
  render(
    h(KeepAlive, null, {
      // 渲染My2
      default: () => h(My2),
    }),
    app
  );
}, 1000);

setTimeout(() => {
  render(
    h(KeepAlive, null, {
      // 在渲染My1
      default: () => h(My1),
    }),
    app
  );
}, 2000);

卸载组件

const unmount = (vnode, parentComponent) => {
  const { shapeFlag } = vnode;
  if (shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) {
    parentComponent.ctx.deactivate(vnode);
    return;
  }
};

挂载组件

const processComponent = (n1, n2, container, anchor, parentComponent) => {
  // 统一处理组件, 里面在区分是普通的还是 函数式组件
  if (n1 == null) {
    if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {
      parentComponent.ctx.activate(n2, container, anchor);
    } else {
      mountComponent(n2, container, anchor, parentComponent);
    }
  } else {
    // 组件更新靠的是props
    updateComponent(n1, n2);
  }
};
const cacheSubtree = () => {
  cache.set(pendingCacheKey, instance.subTree);
};
onMounted(cacheSubtree);
onUpdated(cacheSubtree); // 在更新时进行重新缓存

max 控制缓存

function unmount(vnode) {
  resetShapeFlag(vnode);
  _unmount(vnode, instance);
}
function pruneCacheEntry(key) {
  const cached = cache.get(key);
  unmount(cached);
  cache.delete(key);
  keys.delete(key);
}
if (cacheVNode) {
  // 缓存中有
  vnode.component = cacheVNode.component; // 复用组件,并且标识不需要真正的创建
  vnode.shapeFlag |= ShapeFlags.COMPONENT_KEPT_ALIVE;
} else {
  keys.add(key);
  if (max && keys.size > max) {
    // 超过限制删除第一个
    pruneCacheEntry(keys.values().next().value);
  }
}
vnode.shapeFlag |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE;
function resetShapeFlag(vnode) {
  let shapeFlag = vnode.shapeFlag;
  if (shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) {
    shapeFlag -= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE;
  }
  if (shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {
    shapeFlag -= ShapeFlags.COMPONENT_KEPT_ALIVE;
  }
  vnode.shapeFlag = shapeFlag;
}

Released under the MIT License.