|
| 1 | +import { |
| 2 | + getCurrentVM, |
| 3 | + isDef, |
| 4 | + getStateId, |
| 5 | + resolvePushedVm, |
| 6 | + genKey, |
| 7 | + replaceFirstKeyAndCache, |
| 8 | + getFirstComponentChild, |
| 9 | + isPlaceHolderVm, |
| 10 | + setCurrentVnodeKey, |
| 11 | + replaceState, |
| 12 | +} from "./utils"; |
| 13 | +import HistoryStack from "./historyStack"; |
| 14 | + |
| 15 | +export default class VueRouterKeepAliveHelper{ |
| 16 | + get currentVm() { |
| 17 | + return getCurrentVM(this.router); |
| 18 | + } |
| 19 | + get isPush() { |
| 20 | + if (!this.isReplace) { |
| 21 | + const stateId = getStateId(); |
| 22 | + return !isDef(stateId) || this.preStateId <= stateId; |
| 23 | + } |
| 24 | + return false; |
| 25 | + } |
| 26 | + get stackPointer() { |
| 27 | + return this.router._stack; |
| 28 | + } |
| 29 | + constructor({ Vue, router, replaceStay }) { |
| 30 | + this.Vue = Vue; |
| 31 | + this.router = router; |
| 32 | + this.router._stack = 0; |
| 33 | + this.mode = router.mode; // hash or history |
| 34 | + this.historyShouldChange = false; |
| 35 | + this.isReplace = false; |
| 36 | + this.replacePrePath = undefined; |
| 37 | + this.preStateId = 0; |
| 38 | + this.pre = null; |
| 39 | + this.replaceStay = replaceStay || []; |
| 40 | + this.hacked = false; |
| 41 | + this.historyStack = new HistoryStack(); |
| 42 | + this.init(); |
| 43 | + } |
| 44 | + init() { |
| 45 | + this.routerHooks(); |
| 46 | + this.hackRouter(); |
| 47 | + } |
| 48 | + /** |
| 49 | + * use afterEach hook to set state.key and add the reference of vm to the historyStack |
| 50 | + */ |
| 51 | + routerHooks() { |
| 52 | + const router = this.router; |
| 53 | + router.afterEach((to, from) => { |
| 54 | + this.historyShouldChange = true; |
| 55 | + // get the vm instance after render |
| 56 | + this.Vue.nextTick(() => { |
| 57 | + const current = this.currentVm; |
| 58 | + const pendingToPushVm = resolvePushedVm(current); |
| 59 | + if (this.pre === null) { |
| 60 | + this.onInitial(pendingToPushVm); |
| 61 | + } else if (this.isReplace) { |
| 62 | + this.onReplace(pendingToPushVm); |
| 63 | + } else if (this.isPush) { |
| 64 | + this.onPush(pendingToPushVm); |
| 65 | + } else { |
| 66 | + this.onBack(pendingToPushVm); |
| 67 | + } |
| 68 | + this.pre = current; |
| 69 | + this.preStateId = this.stackPointer; |
| 70 | + if (!isPlaceHolderVm(pendingToPushVm)) { |
| 71 | + setCurrentVnodeKey(router, genKey(this.stackPointer, router)); |
| 72 | + if (!this.hacked && current) { |
| 73 | + this.hackKeepAliveRender(current.$vnode.parent.componentInstance); |
| 74 | + } |
| 75 | + this.historyShouldChange = false; |
| 76 | + } |
| 77 | + }); |
| 78 | + }); |
| 79 | + } |
| 80 | + /** |
| 81 | + * @description hack router go , replace and push functions to tell replace from back and push |
| 82 | + */ |
| 83 | + hackRouter() { |
| 84 | + const router = this.router; |
| 85 | + const rtmp = router.replace; |
| 86 | + const rtmpf = (location, onComplete, onAbort) => { |
| 87 | + this.isReplace = true; |
| 88 | + this.replacePrePath = router.history.current.path; |
| 89 | + rtmp.call(router, location, onComplete, (e) => { |
| 90 | + this.isReplace = false; |
| 91 | + this.replacePrePath = undefined; |
| 92 | + isDef(onAbort) && onAbort(e); |
| 93 | + }); |
| 94 | + }; |
| 95 | + router.replace = function (ocation, onComplete, onAbort) { |
| 96 | + rtmpf(ocation, onComplete, onAbort); |
| 97 | + }; |
| 98 | + |
| 99 | + const gstmp = router.go; |
| 100 | + const gstmpf = (number) => { |
| 101 | + this.isReplace = false; |
| 102 | + return gstmp.call(router, number); |
| 103 | + }; |
| 104 | + router.go = function (num) { |
| 105 | + return gstmpf(num); |
| 106 | + }; |
| 107 | + const pstmp = router.push; |
| 108 | + const pstmpf = (location, onComplete, onAbort) => { |
| 109 | + this.isReplace = false; |
| 110 | + if (!onComplete && !onAbort && typeof Promise !== "undefined") { |
| 111 | + return pstmp.call(router, location, onComplete, onAbort); |
| 112 | + } else { |
| 113 | + pstmp.call(router, location, onComplete, onAbort); |
| 114 | + } |
| 115 | + }; |
| 116 | + router.push = function (location, onComplete, onAbort) { |
| 117 | + return pstmpf(location, onComplete, onAbort); |
| 118 | + }; |
| 119 | + } |
| 120 | + /** |
| 121 | + * @description hack the render function of keep-alive component, modify the key of vnode for cache |
| 122 | + * @param {*} vm keep-alive component instance |
| 123 | + */ |
| 124 | + hackKeepAliveRender(vm) { |
| 125 | + // modify the first keep alive key and catch |
| 126 | + replaceFirstKeyAndCache(vm, genKey(this.stackPointer, this.router)); |
| 127 | + |
| 128 | + const tmp = vm.$options.render; |
| 129 | + const self = this; |
| 130 | + const router = this.router; |
| 131 | + vm.$options.render = function () { |
| 132 | + const slot = this.$slots.default; |
| 133 | + const vnode = getFirstComponentChild(slot); // vnode is a keep-alive-component-vnode |
| 134 | + if (self.historyShouldChange) { |
| 135 | + if (vnode && !isDef(vnode.key)) { |
| 136 | + if (self.isReplace) { |
| 137 | + vnode.key = genKey(self.stackPointer, router); |
| 138 | + } else if (self.isPush) { |
| 139 | + vnode.key = genKey(self.stackPointer + 1, router); |
| 140 | + } else { |
| 141 | + vnode.key = genKey(self.stackPointer - 1, router); |
| 142 | + } |
| 143 | + } |
| 144 | + } else { |
| 145 | + // when historyShouldChange is false should rerender only, should not create new vm ,use the same vnode.key issue#7 |
| 146 | + vnode.key = genKey(self.stackPointer, router); |
| 147 | + } |
| 148 | + return tmp.apply(this, arguments); |
| 149 | + }; |
| 150 | + this.hacked = true; |
| 151 | + } |
| 152 | + /********** callback functions ************/ |
| 153 | + onInitial(vm) { |
| 154 | + const currentStateId = getStateId(); |
| 155 | + if (isDef(currentStateId)) { |
| 156 | + this.setStackPointer(currentStateId); |
| 157 | + } else { |
| 158 | + this.setState(0); |
| 159 | + } |
| 160 | + this.historyStack.push(vm, this.stackPointer); |
| 161 | + } |
| 162 | + onPush(vm) { |
| 163 | + this.setState(this.increaseStackPointer()); |
| 164 | + this.historyStack.push(vm, this.stackPointer); |
| 165 | + } |
| 166 | + onBack(vm) { |
| 167 | + this.historyStack.pop(); |
| 168 | + this.decreaseStackPointer(); |
| 169 | + this.historyStack.push(vm, this.stackPointer); |
| 170 | + } |
| 171 | + onReplace(vm) { |
| 172 | + const shouldDestroy = !( |
| 173 | + isDef(this.replacePrePath) && |
| 174 | + this.replaceStay.includes(this.replacePrePath) |
| 175 | + ); |
| 176 | + if (shouldDestroy) { |
| 177 | + this.pre.$keepAliveDestroy(); |
| 178 | + } |
| 179 | + this.setState(this.stackPointer); |
| 180 | + this.historyStack.push(vm, this.stackPointer); |
| 181 | + this.isReplace = false; |
| 182 | + this.replacePrePath = undefined; |
| 183 | + } |
| 184 | + setState(id) { |
| 185 | + this.setStackPointer(id); |
| 186 | + replaceState(this.mode, this.router, id); |
| 187 | + } |
| 188 | + setStackPointer(val) { |
| 189 | + this.router._stack = val; |
| 190 | + } |
| 191 | + increaseStackPointer() { |
| 192 | + return (this.router._stack += 1); |
| 193 | + } |
| 194 | + decreaseStackPointer() { |
| 195 | + return (this.router._stack -= 1); |
| 196 | + } |
| 197 | +} |
0 commit comments