hezhongfeng / vue-page-stack

Routing and navigation for your Vue SPA. Vue3.0 单页应用导航管理器
https://vue-page-stack-example.onrender.com
MIT License
718 stars 80 forks source link

vue-page-stack@next for vue3 #92

Closed liuweiGL closed 3 years ago

liuweiGL commented 3 years ago
  1. 支持 vue3
  2. 使用 typescript 重构
  3. api 变更

82

liuweiGL commented 3 years ago

作者有打算合并吗?

hezhongfeng commented 3 years ago

我给你新开一个分支吧?

liuweiGL commented 3 years ago

可以开个 next 分支

hezhongfeng commented 3 years ago

👌🏻

hezhongfeng commented 3 years ago

@liuweiGL OK了

0x30 commented 3 years ago

https://github.com/vuejs/vue-next/blob/master/packages/runtime-core/src/components/KeepAlive.ts#L134

还是要处理下activated生命周期的

liuweiGL commented 3 years ago

很多方法 vue 没暴露出来,还在琢磨。

0x30 commented 3 years ago

vue-router-next 会处理 window.history.state 可以从里面拿到 position,记录这个 position ,通过判断position 和 replace 可以知道 前进还是后退或者 替换,可以类似于 客户端 navgation 一样的,前进不缓存,后退读取缓存的方式,来管理。

@liuweiGL 是的,只能自己去看一些官方的方法,去理解了,好在现在版本基本稳定了,应该不会有大变动了,早前beta的时候,histroy state 里面有个参数直接告诉你 是 前进,后退还是替换,现在被去掉了

hezhongfeng commented 3 years ago

@0x30 如果可以去掉,URI上的key,那就最好了,不过还是很难

0x30 commented 3 years ago
import {
  callWithAsyncErrorHandling,
  ComponentInternalInstance,
  defineComponent,
  getCurrentInstance,
  onBeforeUnmount,
  PropType,
  queuePostFlushCb,
  RendererElement,
  RendererNode,
  SuspenseBoundary,
  VNode,
} from "vue";
import { isArray, invokeArrayFns } from "@vue/shared";

let position: number | undefined = undefined;

type ComponentContext = {
  renderer: any;
  activate: (
    vnode: VNode,
    container: RendererElement,
    anchor: RendererNode | null,
    isSVG: boolean,
    optimized: boolean
  ) => void;
  deactivate: (vnode: VNode) => void;
};

export function queueEffectWithSuspense(
  fn: Function | Function[],
  suspense: SuspenseBoundary | null
): void {
  if (suspense && suspense.pendingBranch) {
    if (isArray(fn)) {
      suspense.effects.push(...fn);
    } else {
      suspense.effects.push(fn);
    }
  } else {
    queuePostFlushCb(fn);
  }
}

export function invokeVNodeHook(
  hook: any,
  instance: ComponentInternalInstance | null,
  vnode: VNode,
  prevVNode: VNode | null = null
) {
  callWithAsyncErrorHandling(hook, instance, 7, [vnode, prevVNode]);
}

function resetShapeFlag(vnode: VNode) {
  let shapeFlag = vnode.shapeFlag;
  if (shapeFlag & 256) {
    shapeFlag -= 256;
  }
  if (shapeFlag & 512) {
    shapeFlag -= 512;
  }
  vnode.shapeFlag = shapeFlag;
}

function getInnerChild(vnode: VNode) {
  return vnode.shapeFlag & 128 ? vnode.ssContent! : vnode;
}

export const Navigation = defineComponent({
  inheritRef: true,
  __isKeepAlive: true,
  props: {
    Component: {
      type: Object as PropType<VNode>,
      default: undefined,
    },
  },
  setup: (props) => {
    const stacks: VNode[] = [];

    const instance = getCurrentInstance() as any;
    const sharedContext = instance.ctx as ComponentContext;
    const parentSuspense = instance.suspense;
    const {
      renderer: {
        p: patch,
        m: move,
        um: _unmount,
        o: { createElement },
      },
    } = sharedContext;
    const storageContainer = createElement("div");

    sharedContext.activate = (vnode, container, anchor, isSVG, optimized) => {
      const instance = vnode.component as ComponentInternalInstance;
      move(vnode, container, anchor, 0, parentSuspense);
      // in case props have changed
      patch(
        instance.vnode,
        vnode,
        container,
        anchor,
        instance,
        parentSuspense,
        isSVG,
        optimized
      );

      queueEffectWithSuspense(() => {
        instance.isDeactivated = false;
        if ((instance as any).a) {
          invokeArrayFns((instance as any).a);
        }
        const vnodeHook = vnode.props && vnode.props.onVnodeMounted;
        if (vnodeHook) {
          invokeVNodeHook(vnodeHook, instance.parent, vnode);
        }
      }, parentSuspense);
    };

    sharedContext.deactivate = (vnode: VNode) => {
      const instance = vnode.component! as any;
      move(vnode, storageContainer, null, 1, parentSuspense);
      queueEffectWithSuspense(() => {
        if ((instance as any).da) {
          invokeArrayFns((instance as any).da);
        }
        const vnodeHook = vnode.props && vnode.props.onVnodeUnmounted;
        if (vnodeHook) {
          invokeVNodeHook(vnodeHook, instance.parent, vnode);
        }
        instance.isDeactivated = true;
      }, parentSuspense);
    };

    function unmount(vnode: VNode) {
      // reset the shapeFlag so it can be properly unmounted
      resetShapeFlag(vnode);
      _unmount(vnode, instance, parentSuspense);
    }

    onBeforeUnmount(() => {
      stacks.forEach((cached) => {
        const { subTree, suspense } = instance;
        const vnode = getInnerChild(subTree);
        if (cached.type === vnode.type) {
          // current instance will be unmounted as part of keep-alive's unmount
          resetShapeFlag(vnode);
          // but invoke its deactivated hook here
          const da = (vnode.component as any).da;
          da && queueEffectWithSuspense(da, suspense);
          return;
        }
        unmount(cached);
      });
    });

    return () => {
      const { Component } = props;

      if (Component === undefined) return null;
      let component = Component;

      if (position === undefined) {
        console.log("初始化", component.type);
        stacks.push(component);
      } else if (position < window.history.state.position) {
        console.log("前进", component.type);
        component.shapeFlag |= 256;
        stacks.push(component);
      } else if (position > window.history.state.position) {
        console.log("后退", component.type);
        const comp = stacks.pop();
        comp && unmount(comp);
        component = stacks[stacks.length - 1] ?? component;
        component.shapeFlag |= 512;
      } else if (position == window.history.state.position) {
        console.log("替换", component.type);
        const comp = stacks.pop();
        comp && unmount(comp);
        stacks.push(component);
      }
      position = window.history.state.position;
      return stacks[stacks.length - 1];
    };
  },
});

刚才写了一下,没测试,不过效果来看 是可以实现的 就是刚才我说的 前进缓存 后退销毁,没有 key

hezhongfeng commented 3 years ago

核心就是如何区分前进和后退,这个可以解决掉就真的棒,最近太忙了~

hezhongfeng commented 3 years ago

标记下,看看position是否可以尝试去除URI上的Key