Last active
May 25, 2023 10:02
-
-
Save wangziling/c6b6103a590a9e236b2cf3f79dc1f62d to your computer and use it in GitHub Desktop.
Vue manual render manager.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Vue, { ComponentOptions, CreateElement } from 'vue'; | |
import { VNodeData } from 'vue/types/vnode'; | |
import { AsyncComponentFactory, AsyncComponentPromise, FunctionalComponentOptions } from 'vue/types/options'; | |
import _ from 'lodash'; | |
import $ from 'jquery'; | |
export type TArrayOrPrimitive<T> = T extends Array<any> | ReadonlyArray<any> | |
? T | TArrayMember<T> | |
: Array<T> | T; | |
export type TArrayMember<T> = T extends Array<infer P> | Readonly<Array<infer P>> | |
? P | |
: never; | |
type TNormalVueCompRenderingEventView = Parameters<CreateElement>[0]; | |
interface INormalVueCompRenderingEventParamsExtra { | |
entryData: Parameters<CreateElement>[1], | |
entryChildren: Parameters<CreateElement>[2]; | |
componentOptions?: ComponentOptions<Vue>; | |
} | |
interface INormalVueCompRenderingEventParams extends Partial<INormalVueCompRenderingEventParamsExtra> { | |
target?: string | HTMLElement | Element | ((ins?: Vue) => string | HTMLElement | Element); | |
useDeferMountSetTimeout?: boolean; | |
useDeferMountNextTick?: boolean; | |
useManualMount?: boolean; | |
instanceCreatedCallback?: (ins?: Vue) => any; | |
} | |
interface INormalVueCompRenderingOnceEventParams extends INormalVueCompRenderingEventParams { | |
existedInstanceCondition?: INormalVueCompRenderingEventInstanceIterator; | |
instanceResolvedPhase?: `hook:${ 'beforeCreate' | 'created' | 'beforeMount' | 'mounted' }`; | |
renderPhasePreTask?: Function; | |
} | |
interface INormalVueCompRenderingEventInstance { | |
target: Exclude<INormalVueCompRenderingEventParams['target'], Function>; | |
instance: Vue; | |
options: INormalVueCompRenderingEventParams; | |
} | |
interface INormalVueCompRenderingEventGenerateSlotParams { | |
target: string | HTMLElement | Element; | |
loadingText: string; | |
idLength: number; | |
id: string; | |
} | |
interface INormalVueCompRenderingEventRemoveSlotParams { | |
target: string | HTMLElement | Element; | |
filterSelector?: string; | |
} | |
interface INormalVueCompRenderingEventInstanceIterator<Result = boolean> { | |
( | |
ins: TArrayMember<NormalVueCompRenderingEvent['instances']>, | |
idx: number, | |
arr: NormalVueCompRenderingEvent['instances'] | |
): Result; | |
} | |
interface IVueMergeListenersOptions { | |
validator: (eventName: string, func: Function) => boolean; // Validate function. False means bypass. | |
useFnWrappedAsOneMode: boolean; // True means we wrap duplicate same eventName functions as one. (Aggregate them to one). False mean Array like []. | |
useNewlyFnsTakePriorityMode: boolean; // Should we let the subsequent function be triggered first. | |
} | |
export interface IVueAsyncLoadingComponentWrapperConfig { | |
useHiddenPlaceholder: boolean; | |
loadingCompEntryData: Partial<VNodeData>; | |
loadingCompOptions: Partial<ComponentOptions<Vue>>; | |
errorCompEntryData: Partial<VNodeData>; | |
errorCompOptions: Partial<ComponentOptions<Vue>>; | |
asyncCompEntryData: Partial<VNodeData>; | |
asyncHandlerReplaceConfig: Partial<ReturnType<AsyncComponentFactory>>; | |
} | |
/** | |
* Random string. | |
* @param len {string} | |
* @return {string} | |
*/ | |
export function randomString(len?: number): string { | |
len = len || 32; | |
const $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678'; | |
const maxPos = $chars.length; | |
let pwd = ''; | |
for (let i = 0; i < len; i++) { | |
pwd += $chars.charAt(Math.floor(Math.random() * maxPos)); | |
} | |
return pwd; | |
} | |
/** | |
* Load an async component. | |
* @param component {AsyncComponentPromise} | |
* @param [config] {Partial<IVueAsyncLoadingComponentWrapperConfig>} | |
* @return {Promise<FunctionalComponentOptions>} | |
*/ | |
export function vueAsyncLoadingComponentWrapper ( | |
component: AsyncComponentPromise | any, /* 'any' for `import()` */ | |
config?: Partial<IVueAsyncLoadingComponentWrapperConfig> | |
): Promise<FunctionalComponentOptions> { | |
const useHiddenPlaceholder = _.get(config, 'useHiddenPlaceholder'); | |
const commonTemplateSpanDataSet = {}; | |
if (useHiddenPlaceholder) { | |
_.merge( | |
commonTemplateSpanDataSet, | |
{ style: 'display: none;' } | |
); | |
} | |
function calcTemplateSpanStringBindings (templateSpanDataSet: object) { | |
return Object.entries(templateSpanDataSet) | |
.map(function (v) { | |
return [v[0], `"${ v[1] }"`].join('='); | |
}) | |
.join(' '); | |
} | |
const AsyncHandler: AsyncComponentFactory = () => _.merge( | |
{ | |
component, | |
loading: { | |
functional: true, | |
// @ts-ignore | |
render (h, { data, children } = {}) { | |
const templateSpanDataSet = _.merge( | |
commonTemplateSpanDataSet, | |
{ | |
'data-name': 'asyncComponentLoadingPlaceholder' | |
} | |
); | |
return h( | |
_.merge( | |
{ | |
template: `<span ${ calcTemplateSpanStringBindings(templateSpanDataSet) }>Loading...</span>` | |
}, | |
_.get(config, 'loadingCompOptions') | |
), | |
_.merge( | |
// No need 'on', especially the lifeCycle hooks, it will trigger here unexpectedly. | |
_.has(data, 'on') ? _.omit(data, 'on') : data, | |
_.get(config, 'loadingCompEntryData') | |
), | |
children | |
); | |
} | |
}, | |
error: { | |
functional: true, | |
// @ts-ignore | |
render (h, { data, children } = {}) { | |
const templateSpanDataSet = _.merge( | |
commonTemplateSpanDataSet, | |
{ | |
'data-name': 'asyncComponentErrorPlaceholder' | |
} | |
); | |
return h( | |
_.merge( | |
{ | |
template: `<span ${ calcTemplateSpanStringBindings(templateSpanDataSet) }>Error occurred. Please try again later.</span>`, | |
}, | |
_.get(config, 'errorCompOptions') | |
), | |
_.merge( | |
// No need 'on', especially the lifeCycle hooks, it will trigger here unexpectedly. | |
_.has(data, 'on') ? _.omit(data, 'on') : data, | |
_.get(config, 'errorCompEntryData') | |
), | |
children | |
); | |
} | |
} | |
} as ReturnType<AsyncComponentFactory>, | |
_.omit(config, 'asyncHandlerReplaceConfig') | |
); | |
return Promise.resolve({ | |
functional: true, | |
render (h, { data, children }) { | |
return h( | |
AsyncHandler, | |
_.merge(data, _.get(config, 'asyncCompEntryData')), | |
children | |
); | |
} | |
}); | |
} | |
/** | |
* The event base manager of rendering the RS. | |
* @Constructor | |
*/ | |
export class NormalVueCompRenderingEventBase { | |
instances: INormalVueCompRenderingEventInstance[] = []; | |
store: Record<string, any> = {}; // Store something. | |
findInstance (options: Partial<Pick<INormalVueCompRenderingEventInstance, 'target' | 'instance'> & { find: INormalVueCompRenderingEventInstanceIterator }>) { | |
const findFunc = _.get(options, 'find'); | |
if (typeof findFunc === 'function') { | |
return this.instances.find(findFunc as any); | |
} | |
return this.instances.find(v => _.isMatch(v, options)); | |
} | |
filterInstances (options: Partial<Pick<INormalVueCompRenderingEventInstance, 'target' | 'instance'> & { filter: INormalVueCompRenderingEventInstanceIterator }>) { | |
const filterFunc = _.get(options, 'filter'); | |
if (typeof filterFunc === 'function') { | |
return this.instances.filter(filterFunc as any); | |
} | |
return this.instances.filter(v => _.isMatch(v, options)); | |
} | |
eachInstance (iterator: INormalVueCompRenderingEventInstanceIterator<boolean | void>) { | |
// Return true will stop loop early. | |
return this.instances.some(iterator); | |
} | |
getViewInstance (instance: INormalVueCompRenderingEventInstance['instance']) { | |
return _.get(instance, '$children.0') as Vue | undefined; | |
} | |
registerInstance (instanceParams: INormalVueCompRenderingEventInstance) { | |
if (!instanceParams) { | |
return this; | |
} | |
this.instances.push(instanceParams); | |
return this; | |
} | |
unregisterInstance (options: Partial<Pick<INormalVueCompRenderingEventInstance, 'target' | 'instance'>>) { | |
if (!options) { | |
return; | |
} | |
const targetIndex = this.instances.findIndex(v => { | |
return v.target === options.target || v.instance === options.instance; | |
}); | |
if (targetIndex !== -1) { | |
return this.instances.splice(targetIndex, 1)[0]; | |
} | |
} | |
generateSlot (params?: Partial<INormalVueCompRenderingEventGenerateSlotParams>) { | |
const id = _.get(params, 'id') || randomString(_.get(params, 'idLength')); | |
const $slot = $(`<div id="${ id }" data-name="slotLoadingPlaceholder">${ _.get(params, 'loadingText') || 'Loading...' }</div>`); | |
let target = _.get(params, 'target'); | |
if (typeof target === 'string') { | |
target = $(target)[0]; | |
} | |
if (!target) { | |
target = document.body; | |
} | |
$(target).append($slot); | |
return { | |
id, | |
$slot, | |
slot: $slot[0] | |
}; | |
} | |
removeSlot (params: INormalVueCompRenderingEventRemoveSlotParams) { | |
$(_.get(params, 'target') as any).remove(_.get(params, 'filterSelector')); | |
} | |
mergeListeners (...args: Parameters<typeof mergeVueListeners>): ReturnType<typeof mergeVueListeners> { | |
return mergeVueListeners.apply(this, args); | |
} | |
aggregateDuplicateListeners (...args: Parameters<typeof aggregateVueDuplicateListeners>): ReturnType<typeof aggregateVueDuplicateListeners> { | |
return aggregateVueDuplicateListeners.apply(this, args); | |
} | |
destroy (options: Partial<Pick<INormalVueCompRenderingEventInstance, 'target' | 'instance'> & { removeDom: boolean }>) { | |
const targetIndex = this.instances.findIndex(v => { | |
return v.target === options.target || v.instance === options.instance; | |
}); | |
if (targetIndex !== -1) { | |
const targetInstance = this.instances.splice(targetIndex, 1)[0]; | |
// Need 'try...catch'. It may throw out errors. | |
try { | |
targetInstance.instance.$destroy(); | |
} catch (e) {} | |
if (options.removeDom) { | |
if (targetInstance.instance.$el) { | |
$(targetInstance.instance.$el).remove(); | |
return this; | |
} | |
// Maybe currently the $el hadn't been generated. | |
// OK add the mounted hook. | |
targetInstance.instance.$on('hook:mounted', () => { | |
$(targetInstance.instance.$el).remove(); | |
}); | |
} | |
} | |
return this; | |
} | |
destroyAll (options?: Partial<Pick<INormalVueCompRenderingEventInstance, 'target' | 'instance'> & { removeDom: boolean }>) { | |
const presetCondition = _.pick(options, ['target', 'instance']); | |
const isNeedToRemoveDom = _.get(options, 'removeDom'); | |
const removeFunc = (v: TArrayMember<NormalVueCompRenderingEvent['instances']>) => { | |
this.destroy({ | |
..._.pick(v, ['instance', 'target']), | |
removeDom: isNeedToRemoveDom | |
}); | |
}; | |
_.forEach( | |
this.instances, | |
v => { | |
if (!_.isEmpty(presetCondition) && _.isMatch(v, presetCondition)) { | |
return removeFunc(v); | |
} | |
removeFunc(v); | |
} | |
); | |
return this; | |
} | |
patchStore (...args: Array<any>) { | |
return _.merge(this.store, ...args); | |
} | |
getStoreProp (path: Parameters<typeof _['get']>[1]) { | |
return _.get(this.store, path); | |
} | |
} | |
/** | |
* The event manager of rendering the RS. | |
* @Constructor | |
*/ | |
export class NormalVueCompRenderingEvent extends NormalVueCompRenderingEventBase { | |
View: TNormalVueCompRenderingEventView; | |
constructor (View: TNormalVueCompRenderingEventView) { | |
super(); | |
this.View = View; | |
} | |
render (options: INormalVueCompRenderingEventParams = {} as any) { | |
const { | |
target: _target, | |
entryData, | |
entryChildren, | |
useDeferMountSetTimeout, | |
useDeferMountNextTick, | |
useManualMount, | |
componentOptions, | |
instanceCreatedCallback | |
} = options; | |
const self = this; | |
const mergedCustomHooksKey = '__NormalVueCompRenderingEventMergedCustomHooks__'; | |
// Stored once, as a referer. | |
const clonedEntryDataOn = _.cloneDeep(_.get(entryData, 'on')); | |
const instance = new Vue( | |
_.merge( | |
{ | |
render: (h: CreateElement) => { | |
const isInstanceAlreadyMergedCustomHooks = _.get(instance, mergedCustomHooksKey); | |
if (entryData) { | |
// If rendered, the 'entryData.on' will be undefined. | |
// Then if 'entryData.props' own dynamic value and changed. | |
// Here will re-render. With no 'entryData.on' listeners. | |
_.set(entryData, 'on', clonedEntryDataOn); | |
let entryDataOn = _.get(entryData, 'on'); | |
if (!isInstanceAlreadyMergedCustomHooks) { | |
_.set(instance, mergedCustomHooksKey, true); | |
entryDataOn = mergeVueListeners([ | |
{ | |
'hook:destroyed' () { | |
self.destroy({ instance }); | |
} | |
}, | |
entryDataOn! | |
]); | |
} | |
return h( | |
this.View, | |
// Use assign. Do Not merge. | |
_.assign(entryData, { on: entryDataOn }), | |
entryChildren | |
); | |
} | |
_.set(instance, mergedCustomHooksKey, true); | |
return h( | |
this.View, | |
{ | |
on: { | |
'hook:destroyed' () { | |
self.destroy({ instance }); | |
} | |
} | |
}, | |
entryChildren | |
); | |
} | |
}, | |
componentOptions | |
) | |
); | |
// If we need the instance immediately. | |
if (typeof instanceCreatedCallback === 'function') { | |
instanceCreatedCallback(instance); | |
} | |
// Target can be Functionality. | |
const target = typeof _target === 'function' | |
? _target(instance) | |
: _target; | |
this.instances.push({ | |
target, | |
instance, | |
options | |
}); | |
// Next tick. | |
if (useDeferMountNextTick) { | |
// No matter the target exists or not, Vue will help us. | |
Vue.nextTick(() => instance.$mount(target)); | |
return instance; | |
} | |
// Set timeout. | |
if (useDeferMountSetTimeout) { | |
// No matter the target exists or not, Vue will help us. | |
setTimeout(() => instance.$mount(target)); | |
return instance; | |
} | |
// Manual mount. | |
if (!useManualMount) { | |
// No matter the target exists or not, Vue will help us. | |
instance.$mount(target); | |
} | |
return instance; | |
} | |
renderOnceDeferred (options: INormalVueCompRenderingOnceEventParams = {} as any): ReturnType<typeof $.Deferred<ReturnType<NormalVueCompRenderingEvent['render']>>> { | |
const deferInstanceKey = '__DeferInstance__'; | |
const existedDefer = this.getStoreProp(deferInstanceKey) as ReturnType<typeof $.Deferred<any>>; | |
if (existedDefer) { | |
if (existedDefer.state() === 'pending') { | |
return existedDefer; | |
} | |
} | |
const defer = $.Deferred(); | |
this.patchStore({ [deferInstanceKey]: defer }); | |
const existedInstanceCondition = _.get(options, 'existedInstanceCondition'); | |
const existedVueInstance = typeof existedInstanceCondition === 'function' | |
? this.findInstance({ | |
find: function (...args) { | |
// Should find the instance which is under 'deferred' mode. | |
return _.get(args, '0.instance.__deferred__') && existedInstanceCondition(...args); | |
} | |
}) | |
: this.findInstance({ | |
find (v) { | |
// First deferred. | |
return _.get(v, 'instance.__deferred__'); | |
} | |
}); | |
// OK. already own one. | |
if (existedVueInstance) { | |
return defer.resolve(_.get(existedVueInstance, 'instance')); | |
} | |
// Here. This time is a totally new rendering event. | |
const renderPhasePreTask = _.get(options, 'renderPhasePreTask'); | |
if (typeof renderPhasePreTask === 'function') { | |
renderPhasePreTask(); | |
} | |
let instance: any; | |
const instanceResolver = function instanceResolver () { | |
// Should use this. | |
// If we want to mount instance immediately. | |
// It will cause an issue that if 'instanceResolver' is triggered on 'hook:mounted'. | |
// At that moment, the 'instance' here isn't valued but 'instanceResolver' is triggered. | |
if (instanceResolvedPhase === defaultInstanceResolvedPhaseMounted) { | |
Vue.nextTick(function instanceResolver () { | |
return defer.resolve(instance); | |
}); | |
} | |
return defer.resolve(instance); | |
}; | |
// Default Mounted. | |
const defaultInstanceResolvedPhaseMounted = 'hook:mounted'; | |
const instanceResolvedPhase = typeof _.get(options, 'instanceResolvedPhase') === 'string' | |
? _.get(options, 'instanceResolvedPhase') || defaultInstanceResolvedPhaseMounted | |
: defaultInstanceResolvedPhaseMounted; | |
// Use entryData. | |
let entryData = _.get(options, 'entryData'); | |
if (!entryData) { | |
_.set(options, 'entryData', entryData = {}); | |
} | |
// Merge listeners. Use Assign. | |
_.assign(entryData, { | |
on: this.mergeListeners([ | |
{ [instanceResolvedPhase!]: instanceResolver }, | |
_.get(entryData, 'on')! | |
]) | |
}); | |
// Load instance. | |
instance = this.render.call(this, _.omit(options, 'existedInstanceCondition') as any); | |
// Record this instance is rendered with 'deferred' mode. | |
// @ts-ignore | |
instance.__deferred__ = true; | |
// If manually. | |
if (_.get(options, 'useManualMount')) { | |
return instanceResolver(); | |
} | |
return defer; | |
} | |
resetView () { | |
// Finally. I found the solution by reading Vue2 source code. | |
if (typeof this.View === 'function') { | |
// Reset the 'async component' related records. | |
// So that we can reload the async component. | |
if (_.has(this.View, 'resolved')) { | |
_.set(this.View, 'resolved', undefined); | |
_.set(this.View, 'owners', undefined); | |
} | |
} | |
return this; | |
} | |
resetDeferred () { | |
const deferInstanceKey = '__DeferInstance__'; | |
// Remove the stored 'defer'. | |
this.patchStore({ [deferInstanceKey]: null }); | |
return this; | |
} | |
reset (options?: Partial<{ removeDom: boolean }>) { | |
this.resetView(); | |
this.resetDeferred(); | |
this.destroyAll(options); | |
} | |
} | |
/** | |
* Merge multiple listeners as a single listener. | |
* @param listenersArr {*} | |
* @param [options] {{[validator]: (func: Function) => boolean, [useFnWrappedAsOneMode]: boolean, [useNewlyFnsTakePriorityMode]: boolean}} | |
* @return {*} | |
*/ | |
export function mergeVueListeners ( | |
listenersArr: Array<Record<string, TArrayOrPrimitive<Function>>>, | |
options?: Partial<IVueMergeListenersOptions> | |
): Record<string, TArrayOrPrimitive<Function>> { | |
const finalListeners: TArrayMember<typeof listenersArr> = {}; | |
const validator = _.get(options, 'validator'); | |
// Whether to return as 'Function[]' or 'Function'. | |
const useFnArrayFormatMode = !_.get(options, 'useFnWrappedAsOneMode'); | |
// Should the previous listeners take the priority to be invoked. | |
const usePreviousFnsTakePriorityMode = !_.get(options, 'useNewlyFnsTakePriorityMode'); | |
listenersArr.forEach(function (ls) { | |
if (!(ls && typeof ls === 'object')) { | |
return; | |
} | |
Object.keys(ls).forEach(function (eventName) { | |
// @ts-ignore | |
let targetListener = ls[eventName]; | |
if (!targetListener) { | |
return; | |
} | |
// Validator. | |
if (typeof validator === 'function') { | |
if (Array.isArray(targetListener)) { | |
targetListener = targetListener.filter(function (func) { | |
return validator(eventName, func); | |
}); | |
} else { | |
// If failed to validate. OK. Remove this listener. | |
if (!validator(eventName, targetListener)) { | |
return; | |
} | |
} | |
} | |
// @ts-ignore | |
const prevListener = finalListeners[eventName]; | |
if (!prevListener) { | |
// @ts-ignore | |
const wrappedTargetLister = Array.isArray(targetListener) | |
? useFnArrayFormatMode | |
? targetListener | |
: aggregateVueDuplicateListeners(targetListener) | |
: targetListener; | |
finalListeners[eventName] = useFnArrayFormatMode | |
? ([] as Function[]).concat(wrappedTargetLister!) | |
: wrappedTargetLister!; | |
return; | |
} | |
// @ts-ignore | |
const wrappedTargetLister = useFnArrayFormatMode | |
? usePreviousFnsTakePriorityMode | |
? ([] as Function[]).concat( | |
prevListener, // Invoking with higher priority. | |
targetListener | |
) | |
: ([] as Function[]).concat( | |
targetListener, // Invoking with higher priority. | |
prevListener | |
) | |
: aggregateVueDuplicateListeners( | |
([] as Function[]).concat( | |
usePreviousFnsTakePriorityMode | |
? ([] as Function[]).concat( | |
prevListener, // Invoking with higher priority. | |
Array.isArray(targetListener) | |
? useFnArrayFormatMode | |
? targetListener | |
: aggregateVueDuplicateListeners(targetListener)! | |
: targetListener | |
) | |
: ([] as Function[]).concat( | |
Array.isArray(targetListener) // Invoking with higher priority. | |
? useFnArrayFormatMode | |
? targetListener | |
: aggregateVueDuplicateListeners(targetListener)! | |
: targetListener, | |
prevListener | |
) | |
) | |
); | |
finalListeners[eventName] = useFnArrayFormatMode | |
? ([] as Function[]).concat(wrappedTargetLister!) | |
: wrappedTargetLister!; | |
}); | |
}); | |
return finalListeners; | |
} | |
/** | |
* Calc duplicate listeners | |
* @param callbacks {Function[]} | |
* @return {undefined|Function[]} | |
*/ | |
export function aggregateVueDuplicateListeners (callbacks: Function[]): Function | undefined { | |
if (!(Array.isArray(callbacks) && callbacks.length)) { | |
return; | |
} | |
const cbs = callbacks.slice(); | |
// Need to invoke them in parallel. | |
return function modifiedListener () { | |
const lastCb = cbs.slice(-1)[0]; | |
// @ts-ignore | |
// eslint-disable-next-line | |
cbs.slice(0, -1).forEach(cb => cb.apply(this, arguments)); | |
if (!lastCb) { | |
return; | |
} | |
// We may need the return value. | |
// @ts-ignore | |
// eslint-disable-next-line | |
return lastCb.apply(this, arguments); | |
}; | |
} | |
export const vueAsyncLoadingComponentNormalConfig: Parameters<typeof vueAsyncLoadingComponentWrapper>[1] = { | |
loadingCompOptions: { | |
created () { | |
// Global loading enabled. | |
}, | |
destroyed () { | |
// Global loading disabled. | |
} | |
}, | |
errorCompOptions: { | |
mounted (this: Vue) { | |
// Destroy error component instance. | |
this.$destroy(); | |
// Remove it's dom. | |
$(this.$el).remove(); | |
} | |
} | |
}; | |
/** | |
* Calc vue async loading component normal config. | |
* @param normalVueCompRenderingEvent {NormalVueCompRenderingEvent} | |
* @return {*} | |
*/ | |
export const calcVueAsyncLoadingComponentNormalConfig = function ( | |
normalVueCompRenderingEvent?: NormalVueCompRenderingEvent | |
): Parameters<typeof vueAsyncLoadingComponentWrapper>[1] { | |
if (normalVueCompRenderingEvent instanceof NormalVueCompRenderingEvent) { | |
return _.merge( | |
vueAsyncLoadingComponentNormalConfig, | |
{ | |
errorCompOptions: { | |
created () { | |
// We need to 'reset' all the related things. | |
// So that we can reload the 'normalVueCompRenderingEvent.View'. | |
// E.g. | |
// After setting 'offline', we try to render an 'async component. | |
// It will render this error component. And It will 'remember' the 'offline' result. | |
// No matter we set 'online' again or not, it will always render the error component after we re-execute the render events. | |
// So we need to reset the related things to let Vue re-fetch the async component when it failed. | |
// So that we can re-render what we want not just the error component. | |
normalVueCompRenderingEvent.reset({ removeDom: true }); | |
} | |
} | |
} | |
); | |
} | |
return vueAsyncLoadingComponentNormalConfig; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Usage(demo):