feat: renderTriggered for forced updates

This commit is contained in:
Evan You 2018-11-08 14:09:52 -05:00
parent 2ee60e0a03
commit 6027d480f3
4 changed files with 108 additions and 65 deletions

View File

@ -24,7 +24,7 @@ import {
DebuggerEvent
} from './autorun'
export { Autorun, DebuggerEvent }
export { Autorun, AutorunOptions, DebuggerEvent }
export { OperationTypes } from './operations'
export { computed, ComputedGetter } from './computed'
export { lock, unlock } from './lock'

View File

@ -21,7 +21,7 @@ import { createRenderProxy } from './componentProxy'
import {
handleError,
ErrorTypes,
callLifecycleHookWithHandle
callLifecycleHookWithHandler
} from './errorHandling'
import { warn } from './warning'
import { setCurrentInstance, unsetCurrentInstance } from './experimental/hooks'
@ -56,7 +56,7 @@ export function createComponentInstance<T extends Component>(
instance.$slots = currentVNode.slots || EMPTY_OBJ
if (created) {
callLifecycleHookWithHandle(created, $proxy, ErrorTypes.CREATED)
callLifecycleHookWithHandler(created, $proxy, ErrorTypes.CREATED)
}
currentVNode = currentContextVNode = null
@ -100,7 +100,7 @@ export function initializeComponentInstance(instance: ComponentInstance) {
// beforeCreate hook is called right in the constructor
const { beforeCreate, props } = instance.$options
if (beforeCreate) {
callLifecycleHookWithHandle(beforeCreate, proxy, ErrorTypes.BEFORE_CREATE)
callLifecycleHookWithHandler(beforeCreate, proxy, ErrorTypes.BEFORE_CREATE)
}
initializeProps(instance, props, (currentVNode as VNode).data)
}
@ -222,18 +222,24 @@ export function shouldUpdateComponent(
if (nextProps === null) {
return prevProps !== null
}
let shouldUpdate = true
const nextKeys = Object.keys(nextProps)
if (nextKeys.length === Object.keys(prevProps).length) {
shouldUpdate = false
for (let i = 0; i < nextKeys.length; i++) {
const key = nextKeys[i]
if (nextProps[key] !== prevProps[key]) {
shouldUpdate = true
}
if (nextKeys.length !== Object.keys(prevProps).length) {
return true
}
for (let i = 0; i < nextKeys.length; i++) {
const key = nextKeys[i]
if (nextProps[key] !== prevProps[key]) {
return true
}
}
return shouldUpdate
return false
}
export function getReasonForComponentUpdate(
prevVNode: VNode,
nextVNode: VNode
): any {
// TODO: extract more detailed information on why the component is updating
}
export function createComponentClassFromOptions(

View File

@ -1,4 +1,10 @@
import { autorun, stop, Autorun, immutable } from '@vue/observer'
import {
autorun,
stop,
Autorun,
immutable,
AutorunOptions
} from '@vue/observer'
import { queueJob, handleSchedulerError, nextTick } from '@vue/scheduler'
import { VNodeFlags, ChildrenFlags } from './flags'
import { EMPTY_OBJ, reservedPropRE, isString } from '@vue/shared'
@ -15,7 +21,8 @@ import {
renderFunctionalRoot,
createComponentInstance,
teardownComponentInstance,
shouldUpdateComponent
shouldUpdateComponent,
getReasonForComponentUpdate
} from './componentUtils'
import { KeepAliveSymbol } from './optional/keepAlive'
import { pushWarningContext, popWarningContext, warn } from './warning'
@ -23,7 +30,7 @@ import { resolveProps } from './componentProps'
import {
handleError,
ErrorTypes,
callLifecycleHookWithHandle
callLifecycleHookWithHandler
} from './errorHandling'
export interface NodeOps {
@ -561,6 +568,14 @@ export function createRenderer(options: RendererOptions) {
instance.$parentVNode = nextVNode as MountedVNode
if (shouldUpdateComponent(prevVNode, nextVNode)) {
if (__DEV__ && instance.$options.renderTriggered) {
callLifecycleHookWithHandler(
instance.$options.renderTriggered,
instance.$proxy,
ErrorTypes.RENDER_TRIGGERED,
getReasonForComponentUpdate(prevVNode, nextVNode)
)
}
instance.$forceUpdate()
} else if (instance.$vnode.flags & VNodeFlags.COMPONENT) {
instance.$vnode.contextVNode = nextVNode
@ -1194,58 +1209,72 @@ export function createRenderer(options: RendererOptions) {
} = instance
if (beforeMount) {
callLifecycleHookWithHandle(beforeMount, $proxy, ErrorTypes.BEFORE_MOUNT)
callLifecycleHookWithHandler(beforeMount, $proxy, ErrorTypes.BEFORE_MOUNT)
}
const queueUpdate = (instance.$forceUpdate = () => {
queueJob(instance._updateHandle, flushHooks)
})
instance._updateHandle = autorun(
() => {
if (instance._unmounted) {
return
}
if (instance._mounted) {
updateComponentInstance(instance, isSVG)
} else {
// this will be executed synchronously right here
instance.$vnode = renderInstanceRoot(instance) as MountedVNode
const autorunOptions: AutorunOptions = {
scheduler: queueUpdate
}
queuePostCommitHook(() => {
vnode.el = instance.$vnode.el
if (__COMPAT__) {
// expose __vue__ for devtools
;(vnode.el as any).__vue__ = instance
}
if (vnode.ref) {
vnode.ref($proxy)
}
// retrieve mounted value after initial render so that we get
// to inject effects in hooks
const { mounted } = instance.$options
if (mounted) {
callLifecycleHookWithHandle(mounted, $proxy, ErrorTypes.MOUNTED)
}
})
mount(
instance.$vnode,
container,
vnode as MountedVNode,
isSVG,
endNode
if (__DEV__) {
if (renderTracked) {
autorunOptions.onTrack = event => {
callLifecycleHookWithHandler(
renderTracked,
$proxy,
ErrorTypes.RENDER_TRACKED,
event
)
instance._mounted = true
}
},
{
scheduler: queueUpdate,
onTrack: renderTracked,
onTrigger: renderTriggered
}
)
if (renderTriggered) {
autorunOptions.onTrigger = event => {
callLifecycleHookWithHandler(
renderTriggered,
$proxy,
ErrorTypes.RENDER_TRIGGERED,
event
)
}
}
}
instance._updateHandle = autorun(() => {
if (instance._unmounted) {
return
}
if (instance._mounted) {
updateComponentInstance(instance, isSVG)
} else {
// this will be executed synchronously right here
instance.$vnode = renderInstanceRoot(instance) as MountedVNode
queuePostCommitHook(() => {
vnode.el = instance.$vnode.el
if (__COMPAT__) {
// expose __vue__ for devtools
;(vnode.el as any).__vue__ = instance
}
if (vnode.ref) {
vnode.ref($proxy)
}
// retrieve mounted value after initial render so that we get
// to inject effects in hooks
const { mounted } = instance.$options
if (mounted) {
callLifecycleHookWithHandler(mounted, $proxy, ErrorTypes.MOUNTED)
}
})
mount(instance.$vnode, container, vnode as MountedVNode, isSVG, endNode)
instance._mounted = true
}
}, autorunOptions)
if (__DEV__) {
popWarningContext()
@ -1267,7 +1296,7 @@ export function createRenderer(options: RendererOptions) {
$options: { beforeUpdate }
} = instance
if (beforeUpdate) {
callLifecycleHookWithHandle(
callLifecycleHookWithHandler(
beforeUpdate,
$proxy,
ErrorTypes.BEFORE_UPDATE,
@ -1297,7 +1326,7 @@ export function createRenderer(options: RendererOptions) {
}
const { updated } = instance.$options
if (updated) {
callLifecycleHookWithHandle(
callLifecycleHookWithHandler(
updated,
$proxy,
ErrorTypes.UPDATED,
@ -1334,7 +1363,7 @@ export function createRenderer(options: RendererOptions) {
$options: { beforeUnmount, unmounted }
} = instance
if (beforeUnmount) {
callLifecycleHookWithHandle(
callLifecycleHookWithHandler(
beforeUnmount,
$proxy,
ErrorTypes.BEFORE_UNMOUNT
@ -1347,7 +1376,7 @@ export function createRenderer(options: RendererOptions) {
teardownComponentInstance(instance)
instance._unmounted = true
if (unmounted) {
callLifecycleHookWithHandle(unmounted, $proxy, ErrorTypes.UNMOUNTED)
callLifecycleHookWithHandler(unmounted, $proxy, ErrorTypes.UNMOUNTED)
}
}
@ -1391,7 +1420,7 @@ export function createRenderer(options: RendererOptions) {
callActivatedHook($children[i], false)
}
if (activated) {
callLifecycleHookWithHandle(activated, $proxy, ErrorTypes.ACTIVATED)
callLifecycleHookWithHandler(activated, $proxy, ErrorTypes.ACTIVATED)
}
}
}
@ -1416,7 +1445,11 @@ export function createRenderer(options: RendererOptions) {
callDeactivateHook($children[i], false)
}
if (deactivated) {
callLifecycleHookWithHandle(deactivated, $proxy, ErrorTypes.DEACTIVATED)
callLifecycleHookWithHandler(
deactivated,
$proxy,
ErrorTypes.DEACTIVATED
)
}
}
}

View File

@ -16,6 +16,8 @@ export const enum ErrorTypes {
DEACTIVATED,
ERROR_CAPTURED,
RENDER,
RENDER_TRACKED,
RENDER_TRIGGERED,
WATCH_CALLBACK,
NATIVE_EVENT_HANDLER,
COMPONENT_EVENT_HANDLER,
@ -35,6 +37,8 @@ const ErrorTypeStrings: Record<number, string> = {
[ErrorTypes.DEACTIVATED]: 'in deactivated lifecycle hook',
[ErrorTypes.ERROR_CAPTURED]: 'in errorCaptured lifecycle hook',
[ErrorTypes.RENDER]: 'in render function',
[ErrorTypes.RENDER_TRACKED]: 'in renderTracked debug hook',
[ErrorTypes.RENDER_TRIGGERED]: 'in renderTriggered debug hook',
[ErrorTypes.WATCH_CALLBACK]: 'in watcher callback',
[ErrorTypes.NATIVE_EVENT_HANDLER]: 'in native event handler',
[ErrorTypes.COMPONENT_EVENT_HANDLER]: 'in component event handler',
@ -42,7 +46,7 @@ const ErrorTypeStrings: Record<number, string> = {
'when flushing updates. This may be a Vue internals bug.'
}
export function callLifecycleHookWithHandle(
export function callLifecycleHookWithHandler(
hook: Function,
instanceProxy: ComponentInstance,
type: ErrorTypes,