feat: make functional components time-slicable
This commit is contained in:
parent
6ba02827b1
commit
d5862d8c51
@ -77,7 +77,6 @@ export interface ComponentClass extends ComponentClassOptions {
|
|||||||
|
|
||||||
export interface FunctionalComponent<P = {}> {
|
export interface FunctionalComponent<P = {}> {
|
||||||
(props: P, slots: Slots, attrs: Data, parentVNode: VNode): any
|
(props: P, slots: Slots, attrs: Data, parentVNode: VNode): any
|
||||||
pure?: boolean
|
|
||||||
props?: ComponentPropsOptions<P>
|
props?: ComponentPropsOptions<P>
|
||||||
displayName?: string
|
displayName?: string
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { immutable, unwrap, lock, unlock } from '@vue/observer'
|
import { immutable, unwrap } from '@vue/observer'
|
||||||
import { ComponentInstance } from './component'
|
import { ComponentInstance } from './component'
|
||||||
import {
|
import {
|
||||||
Data,
|
Data,
|
||||||
@ -36,10 +36,12 @@ export function initializeProps(
|
|||||||
options: NormalizedPropsOptions | undefined,
|
options: NormalizedPropsOptions | undefined,
|
||||||
data: Data | null
|
data: Data | null
|
||||||
) {
|
) {
|
||||||
const [props, attrs] = resolveProps(data, options)
|
const { 0: props, 1: attrs } = resolveProps(data, options)
|
||||||
instance.$props = immutable(props === EMPTY_OBJ ? {} : props)
|
instance.$props = __DEV__ ? immutable(props) : props
|
||||||
instance.$attrs = options
|
instance.$attrs = options
|
||||||
? immutable(attrs === EMPTY_OBJ ? {} : attrs)
|
? __DEV__
|
||||||
|
? immutable(attrs)
|
||||||
|
: attrs
|
||||||
: instance.$props
|
: instance.$props
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,47 +117,6 @@ export function resolveProps(
|
|||||||
return [props, attrs]
|
return [props, attrs]
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateProps(
|
|
||||||
instance: ComponentInstance,
|
|
||||||
nextData: Data | null
|
|
||||||
) {
|
|
||||||
// instance.$props and instance.$attrs are observables that should not be
|
|
||||||
// replaced. Instead, we mutate them to match latest props, which will trigger
|
|
||||||
// updates if any value that's been used in child component has changed.
|
|
||||||
const [nextProps, nextAttrs] = resolveProps(nextData, instance.$options.props)
|
|
||||||
// unlock to temporarily allow mutatiing props
|
|
||||||
unlock()
|
|
||||||
const props = instance.$props
|
|
||||||
const rawProps = unwrap(props)
|
|
||||||
const hasEmptyProps = nextProps === EMPTY_OBJ
|
|
||||||
for (const key in rawProps) {
|
|
||||||
if (hasEmptyProps || !nextProps.hasOwnProperty(key)) {
|
|
||||||
delete (props as any)[key]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!hasEmptyProps) {
|
|
||||||
for (const key in nextProps) {
|
|
||||||
;(props as any)[key] = nextProps[key]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const attrs = instance.$attrs
|
|
||||||
if (attrs !== props) {
|
|
||||||
const rawAttrs = unwrap(attrs)
|
|
||||||
const hasEmptyAttrs = nextAttrs === EMPTY_OBJ
|
|
||||||
for (const key in rawAttrs) {
|
|
||||||
if (hasEmptyAttrs || !nextAttrs.hasOwnProperty(key)) {
|
|
||||||
delete attrs[key]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!hasEmptyAttrs) {
|
|
||||||
for (const key in nextAttrs) {
|
|
||||||
attrs[key] = nextAttrs[key]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lock()
|
|
||||||
}
|
|
||||||
|
|
||||||
export function normalizePropsOptions(
|
export function normalizePropsOptions(
|
||||||
raw: ComponentPropsOptions | void
|
raw: ComponentPropsOptions | void
|
||||||
): NormalizedPropsOptions | void {
|
): NormalizedPropsOptions | void {
|
||||||
|
@ -30,7 +30,6 @@ const renderProxyHandlers = {
|
|||||||
return target.$data[key]
|
return target.$data[key]
|
||||||
} else if ((i = target.$options.props) != null && i.hasOwnProperty(key)) {
|
} else if ((i = target.$options.props) != null && i.hasOwnProperty(key)) {
|
||||||
// props are only proxied if declared
|
// props are only proxied if declared
|
||||||
// make sure to return from $props to register dependency
|
|
||||||
return target.$props[key]
|
return target.$props[key]
|
||||||
} else if (
|
} else if (
|
||||||
(i = target._computedGetters) !== null &&
|
(i = target._computedGetters) !== null &&
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { VNodeFlags } from './flags'
|
import { VNodeFlags, ChildrenFlags } from './flags'
|
||||||
import { EMPTY_OBJ, isArray, isObject } from '@vue/shared'
|
import { EMPTY_OBJ, isArray, isObject } from '@vue/shared'
|
||||||
import { h } from './h'
|
import { h } from './h'
|
||||||
import { VNode, MountedVNode, createFragment } from './vdom'
|
import { VNode, MountedVNode, createFragment } from './vdom'
|
||||||
@ -193,10 +193,22 @@ function normalizeComponentRoot(
|
|||||||
return vnode
|
return vnode
|
||||||
}
|
}
|
||||||
|
|
||||||
export function shouldUpdateFunctionalComponent(
|
export function shouldUpdateComponent(
|
||||||
prevProps: Record<string, any> | null,
|
prevVNode: VNode,
|
||||||
nextProps: Record<string, any> | null
|
nextVNode: VNode
|
||||||
): boolean {
|
): boolean {
|
||||||
|
const { data: prevProps, childFlags: prevChildFlags } = prevVNode
|
||||||
|
const { data: nextProps, childFlags: nextChildFlags } = nextVNode
|
||||||
|
// If has different slots content, or has non-compiled slots,
|
||||||
|
// the child needs to be force updated. It's ok to call $forceUpdate
|
||||||
|
// again even if props update has already queued an update, as the
|
||||||
|
// scheduler will not queue the same update twice.
|
||||||
|
if (
|
||||||
|
prevChildFlags !== nextChildFlags ||
|
||||||
|
(nextChildFlags & ChildrenFlags.DYNAMIC_SLOTS) > 0
|
||||||
|
) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
if (prevProps === nextProps) {
|
if (prevProps === nextProps) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { autorun, stop } from '@vue/observer'
|
import { autorun, stop, Autorun, immutable } from '@vue/observer'
|
||||||
import { queueJob } from '@vue/scheduler'
|
import { queueJob } from '@vue/scheduler'
|
||||||
import { VNodeFlags, ChildrenFlags } from './flags'
|
import { VNodeFlags, ChildrenFlags } from './flags'
|
||||||
import { EMPTY_OBJ, reservedPropRE, isString } from '@vue/shared'
|
import { EMPTY_OBJ, reservedPropRE, isString } from '@vue/shared'
|
||||||
@ -10,18 +10,18 @@ import {
|
|||||||
Ref,
|
Ref,
|
||||||
VNodeChildren
|
VNodeChildren
|
||||||
} from './vdom'
|
} from './vdom'
|
||||||
import { ComponentInstance, FunctionalComponent } from './component'
|
import { ComponentInstance } from './component'
|
||||||
import { updateProps } from './componentProps'
|
|
||||||
import {
|
import {
|
||||||
renderInstanceRoot,
|
renderInstanceRoot,
|
||||||
renderFunctionalRoot,
|
renderFunctionalRoot,
|
||||||
createComponentInstance,
|
createComponentInstance,
|
||||||
teardownComponentInstance,
|
teardownComponentInstance,
|
||||||
shouldUpdateFunctionalComponent
|
shouldUpdateComponent
|
||||||
} from './componentUtils'
|
} from './componentUtils'
|
||||||
import { KeepAliveSymbol } from './optional/keepAlive'
|
import { KeepAliveSymbol } from './optional/keepAlive'
|
||||||
import { pushWarningContext, popWarningContext } from './warning'
|
import { pushWarningContext, popWarningContext, warn } from './warning'
|
||||||
import { handleError, ErrorTypes } from './errorHandling'
|
import { handleError, ErrorTypes } from './errorHandling'
|
||||||
|
import { resolveProps } from './componentProps'
|
||||||
|
|
||||||
export interface NodeOps {
|
export interface NodeOps {
|
||||||
createElement: (tag: string, isSVG?: boolean) => any
|
createElement: (tag: string, isSVG?: boolean) => any
|
||||||
@ -57,6 +57,13 @@ export interface RendererOptions {
|
|||||||
teardownVNode?: (vnode: VNode) => void
|
teardownVNode?: (vnode: VNode) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface FunctionalHandle {
|
||||||
|
current: VNode
|
||||||
|
prevTree: VNode
|
||||||
|
runner: Autorun
|
||||||
|
forceUpdate: () => void
|
||||||
|
}
|
||||||
|
|
||||||
// The whole mounting / patching / unmouting logic is placed inside this
|
// The whole mounting / patching / unmouting logic is placed inside this
|
||||||
// single function so that we can create multiple renderes with different
|
// single function so that we can create multiple renderes with different
|
||||||
// platform definitions. This allows for use cases like creating a test
|
// platform definitions. This allows for use cases like creating a test
|
||||||
@ -239,9 +246,64 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
isSVG: boolean,
|
isSVG: boolean,
|
||||||
endNode: RenderNode | null
|
endNode: RenderNode | null
|
||||||
) {
|
) {
|
||||||
const subTree = (vnode.children = renderFunctionalRoot(vnode))
|
if (__DEV__ && vnode.ref) {
|
||||||
mount(subTree, container, vnode as MountedVNode, isSVG, endNode)
|
warn(
|
||||||
vnode.el = subTree.el as RenderNode
|
`cannot use ref on a functional component because there is no ` +
|
||||||
|
`instance to reference to.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handle: FunctionalHandle = (vnode.handle = {
|
||||||
|
current: vnode,
|
||||||
|
prevTree: null as any,
|
||||||
|
runner: null as any,
|
||||||
|
forceUpdate: null as any
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleSchedulerError = (err: Error) => {
|
||||||
|
handleError(err, handle.current as VNode, ErrorTypes.SCHEDULER)
|
||||||
|
}
|
||||||
|
|
||||||
|
const queueUpdate = (handle.forceUpdate = () => {
|
||||||
|
queueJob(handle.runner, null, handleSchedulerError)
|
||||||
|
})
|
||||||
|
|
||||||
|
// we are using vnode.ref to store the functional component's update job
|
||||||
|
queueJob(
|
||||||
|
() => {
|
||||||
|
handle.runner = autorun(
|
||||||
|
() => {
|
||||||
|
if (handle.prevTree) {
|
||||||
|
// mounted
|
||||||
|
const { prevTree, current } = handle
|
||||||
|
const nextTree = (handle.prevTree = current.children = renderFunctionalRoot(
|
||||||
|
current
|
||||||
|
))
|
||||||
|
patch(
|
||||||
|
prevTree as MountedVNode,
|
||||||
|
nextTree,
|
||||||
|
platformParentNode(current.el),
|
||||||
|
current as MountedVNode,
|
||||||
|
isSVG
|
||||||
|
)
|
||||||
|
current.el = nextTree.el
|
||||||
|
} else {
|
||||||
|
// initial mount
|
||||||
|
const subTree = (handle.prevTree = vnode.children = renderFunctionalRoot(
|
||||||
|
vnode
|
||||||
|
))
|
||||||
|
mount(subTree, container, vnode as MountedVNode, isSVG, endNode)
|
||||||
|
vnode.el = subTree.el as RenderNode
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scheduler: queueUpdate
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
handleSchedulerError
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function mountText(
|
function mountText(
|
||||||
@ -462,13 +524,7 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
} else if (flags & VNodeFlags.COMPONENT_STATEFUL) {
|
} else if (flags & VNodeFlags.COMPONENT_STATEFUL) {
|
||||||
patchStatefulComponent(prevVNode, nextVNode)
|
patchStatefulComponent(prevVNode, nextVNode)
|
||||||
} else {
|
} else {
|
||||||
patchFunctionalComponent(
|
patchFunctionalComponent(prevVNode, nextVNode)
|
||||||
prevVNode,
|
|
||||||
nextVNode,
|
|
||||||
container,
|
|
||||||
contextVNode,
|
|
||||||
isSVG
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
popWarningContext()
|
popWarningContext()
|
||||||
@ -476,31 +532,24 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function patchStatefulComponent(prevVNode: MountedVNode, nextVNode: VNode) {
|
function patchStatefulComponent(prevVNode: MountedVNode, nextVNode: VNode) {
|
||||||
const { data: prevData, childFlags: prevChildFlags } = prevVNode
|
const { data: prevData } = prevVNode
|
||||||
const {
|
const { data: nextData, slots: nextSlots } = nextVNode
|
||||||
data: nextData,
|
|
||||||
slots: nextSlots,
|
|
||||||
childFlags: nextChildFlags
|
|
||||||
} = nextVNode
|
|
||||||
|
|
||||||
const instance = (nextVNode.children =
|
const instance = (nextVNode.children =
|
||||||
prevVNode.children) as ComponentInstance
|
prevVNode.children) as ComponentInstance
|
||||||
|
|
||||||
|
if (nextData !== prevData) {
|
||||||
|
const { 0: props, 1: attrs } = resolveProps(
|
||||||
|
nextData,
|
||||||
|
instance.$options.props
|
||||||
|
)
|
||||||
|
instance.$props = __DEV__ ? immutable(props) : props
|
||||||
|
instance.$attrs = __DEV__ ? immutable(attrs) : attrs
|
||||||
|
}
|
||||||
instance.$slots = nextSlots || EMPTY_OBJ
|
instance.$slots = nextSlots || EMPTY_OBJ
|
||||||
instance.$parentVNode = nextVNode as MountedVNode
|
instance.$parentVNode = nextVNode as MountedVNode
|
||||||
|
|
||||||
// Update props. This will trigger child update if necessary.
|
if (shouldUpdateComponent(prevVNode, nextVNode)) {
|
||||||
if (nextData !== prevData) {
|
|
||||||
updateProps(instance, nextData)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If has different slots content, or has non-compiled slots,
|
|
||||||
// the child needs to be force updated. It's ok to call $forceUpdate
|
|
||||||
// again even if props update has already queued an update, as the
|
|
||||||
// scheduler will not queue the same update twice.
|
|
||||||
const shouldForceUpdate =
|
|
||||||
prevChildFlags !== nextChildFlags ||
|
|
||||||
(nextChildFlags & ChildrenFlags.DYNAMIC_SLOTS) > 0
|
|
||||||
if (shouldForceUpdate) {
|
|
||||||
instance.$forceUpdate()
|
instance.$forceUpdate()
|
||||||
} else if (instance.$vnode.flags & VNodeFlags.COMPONENT) {
|
} else if (instance.$vnode.flags & VNodeFlags.COMPONENT) {
|
||||||
instance.$vnode.contextVNode = nextVNode
|
instance.$vnode.contextVNode = nextVNode
|
||||||
@ -508,28 +557,13 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
nextVNode.el = instance.$vnode.el
|
nextVNode.el = instance.$vnode.el
|
||||||
}
|
}
|
||||||
|
|
||||||
function patchFunctionalComponent(
|
function patchFunctionalComponent(prevVNode: MountedVNode, nextVNode: VNode) {
|
||||||
prevVNode: MountedVNode,
|
const prevTree = prevVNode.children as VNode
|
||||||
nextVNode: VNode,
|
const handle = (nextVNode.handle = prevVNode.handle as FunctionalHandle)
|
||||||
container: RenderNode,
|
handle.current = nextVNode
|
||||||
contextVNode: MountedVNode | null,
|
|
||||||
isSVG: boolean
|
|
||||||
) {
|
|
||||||
// functional component tree is stored on the vnode as `children`
|
|
||||||
const { data: prevData, slots: prevSlots } = prevVNode
|
|
||||||
const { data: nextData, slots: nextSlots } = nextVNode
|
|
||||||
const render = nextVNode.tag as FunctionalComponent
|
|
||||||
const prevTree = prevVNode.children as MountedVNode
|
|
||||||
|
|
||||||
let shouldUpdate = true
|
if (shouldUpdateComponent(prevVNode, nextVNode)) {
|
||||||
if (render.pure && prevSlots == null && nextSlots == null) {
|
handle.forceUpdate()
|
||||||
shouldUpdate = shouldUpdateFunctionalComponent(prevData, nextData)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shouldUpdate) {
|
|
||||||
const nextTree = (nextVNode.children = renderFunctionalRoot(nextVNode))
|
|
||||||
patch(prevTree, nextTree, container, nextVNode as MountedVNode, isSVG)
|
|
||||||
nextVNode.el = nextTree.el
|
|
||||||
} else if (prevTree.flags & VNodeFlags.COMPONENT) {
|
} else if (prevTree.flags & VNodeFlags.COMPONENT) {
|
||||||
// functional component returned another component
|
// functional component returned another component
|
||||||
prevTree.contextVNode = nextVNode
|
prevTree.contextVNode = nextVNode
|
||||||
@ -1025,7 +1059,7 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
// unmounting ----------------------------------------------------------------
|
// unmounting ----------------------------------------------------------------
|
||||||
|
|
||||||
function unmount(vnode: MountedVNode) {
|
function unmount(vnode: MountedVNode) {
|
||||||
const { flags, data, children, childFlags, ref } = vnode
|
const { flags, data, children, childFlags, ref, handle } = vnode
|
||||||
const isElement = flags & VNodeFlags.ELEMENT
|
const isElement = flags & VNodeFlags.ELEMENT
|
||||||
if (isElement || flags & VNodeFlags.FRAGMENT) {
|
if (isElement || flags & VNodeFlags.FRAGMENT) {
|
||||||
if (isElement && data != null && data.vnodeBeforeUnmount) {
|
if (isElement && data != null && data.vnodeBeforeUnmount) {
|
||||||
@ -1046,6 +1080,8 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
unmountComponentInstance(children as ComponentInstance)
|
unmountComponentInstance(children as ComponentInstance)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// functional
|
||||||
|
stop((handle as FunctionalHandle).runner)
|
||||||
unmount(children as MountedVNode)
|
unmount(children as MountedVNode)
|
||||||
}
|
}
|
||||||
} else if (flags & VNodeFlags.PORTAL) {
|
} else if (flags & VNodeFlags.PORTAL) {
|
||||||
@ -1144,12 +1180,12 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
beforeMount.call($proxy)
|
beforeMount.call($proxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
const errorSchedulerHandler = (err: Error) => {
|
const handleSchedulerError = (err: Error) => {
|
||||||
handleError(err, instance, ErrorTypes.SCHEDULER)
|
handleError(err, instance, ErrorTypes.SCHEDULER)
|
||||||
}
|
}
|
||||||
|
|
||||||
const queueUpdate = (instance.$forceUpdate = () => {
|
const queueUpdate = (instance.$forceUpdate = () => {
|
||||||
queueJob(instance._updateHandle, flushHooks, errorSchedulerHandler)
|
queueJob(instance._updateHandle, flushHooks, handleSchedulerError)
|
||||||
})
|
})
|
||||||
|
|
||||||
instance._updateHandle = autorun(
|
instance._updateHandle = autorun(
|
||||||
@ -1185,7 +1221,7 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
// to inject effects in first render
|
// to inject effects in first render
|
||||||
const { mounted } = instance.$options
|
const { mounted } = instance.$options
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
lifecycleHooks.push(() => {
|
lifecycleHooks.unshift(() => {
|
||||||
mounted.call($proxy)
|
mounted.call($proxy)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import { VNodeFlags, ChildrenFlags } from './flags'
|
|||||||
import { createComponentClassFromOptions } from './componentUtils'
|
import { createComponentClassFromOptions } from './componentUtils'
|
||||||
import { EMPTY_OBJ, isObject, isArray, isFunction, isString } from '@vue/shared'
|
import { EMPTY_OBJ, isObject, isArray, isFunction, isString } from '@vue/shared'
|
||||||
import { RawChildrenType, RawSlots } from './h'
|
import { RawChildrenType, RawSlots } from './h'
|
||||||
|
import { FunctionalHandle } from './createRenderer'
|
||||||
|
|
||||||
const handlersRE = /^on|^vnode/
|
const handlersRE = /^on|^vnode/
|
||||||
|
|
||||||
@ -37,6 +38,10 @@ export interface VNode {
|
|||||||
// only on mounted component nodes
|
// only on mounted component nodes
|
||||||
// points to the parent stateful/functional component's placeholder node
|
// points to the parent stateful/functional component's placeholder node
|
||||||
contextVNode: VNode | null
|
contextVNode: VNode | null
|
||||||
|
// only on mounted functional component nodes
|
||||||
|
// a consistent handle so that a functional component can be identified
|
||||||
|
// by the scheduler
|
||||||
|
handle: FunctionalHandle | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MountedVNode extends VNode {
|
export interface MountedVNode extends VNode {
|
||||||
@ -92,7 +97,8 @@ export function createVNode(
|
|||||||
slots: slots === void 0 ? null : slots,
|
slots: slots === void 0 ? null : slots,
|
||||||
el: null,
|
el: null,
|
||||||
parentVNode: null,
|
parentVNode: null,
|
||||||
contextVNode: null
|
contextVNode: null,
|
||||||
|
handle: null
|
||||||
}
|
}
|
||||||
if (childFlags === ChildrenFlags.UNKNOWN_CHILDREN) {
|
if (childFlags === ChildrenFlags.UNKNOWN_CHILDREN) {
|
||||||
normalizeChildren(vnode, children)
|
normalizeChildren(vnode, children)
|
||||||
|
@ -7,8 +7,11 @@ const enum Priorities {
|
|||||||
|
|
||||||
const frameBudget = 1000 / 60
|
const frameBudget = 1000 / 60
|
||||||
|
|
||||||
|
let start: number = 0
|
||||||
let currentOps: Op[]
|
let currentOps: Op[]
|
||||||
|
|
||||||
|
const getNow = () => window.performance.now()
|
||||||
|
|
||||||
const evaluate = (v: any) => {
|
const evaluate = (v: any) => {
|
||||||
return typeof v === 'function' ? v() : v
|
return typeof v === 'function' ? v() : v
|
||||||
}
|
}
|
||||||
@ -21,11 +24,9 @@ Object.keys(nodeOps).forEach((key: keyof NodeOps) => {
|
|||||||
}
|
}
|
||||||
if (/create/.test(key)) {
|
if (/create/.test(key)) {
|
||||||
nodeOps[key] = (...args: any[]) => {
|
nodeOps[key] = (...args: any[]) => {
|
||||||
|
let res: any
|
||||||
if (currentOps) {
|
if (currentOps) {
|
||||||
let res: any
|
return () => res || (res = original(...args))
|
||||||
return () => {
|
|
||||||
return res || (res = original(...args))
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
return original(...args)
|
return original(...args)
|
||||||
}
|
}
|
||||||
@ -45,7 +46,7 @@ type Op = [Function, ...any[]]
|
|||||||
|
|
||||||
interface Job extends Function {
|
interface Job extends Function {
|
||||||
ops: Op[]
|
ops: Op[]
|
||||||
post: Function
|
post: Function | null
|
||||||
expiration: number
|
expiration: number
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,6 +66,7 @@ window.addEventListener(
|
|||||||
if (event.source !== window || event.data !== key) {
|
if (event.source !== window || event.data !== key) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
start = getNow()
|
||||||
flush()
|
flush()
|
||||||
},
|
},
|
||||||
false
|
false
|
||||||
@ -102,11 +104,11 @@ let hasPendingFlush = false
|
|||||||
|
|
||||||
export function queueJob(
|
export function queueJob(
|
||||||
rawJob: Function,
|
rawJob: Function,
|
||||||
postJob: Function,
|
postJob?: Function | null,
|
||||||
onError?: (reason: any) => void
|
onError?: (reason: any) => void
|
||||||
) {
|
) {
|
||||||
const job = rawJob as Job
|
const job = rawJob as Job
|
||||||
job.post = postJob
|
job.post = postJob || null
|
||||||
job.ops = job.ops || []
|
job.ops = job.ops || []
|
||||||
// 1. let's see if this invalidates any work that
|
// 1. let's see if this invalidates any work that
|
||||||
// has already been done.
|
// has already been done.
|
||||||
@ -126,12 +128,13 @@ export function queueJob(
|
|||||||
}
|
}
|
||||||
} else if (patchQueue.indexOf(job) === -1) {
|
} else if (patchQueue.indexOf(job) === -1) {
|
||||||
// a new job
|
// a new job
|
||||||
job.expiration = performance.now() + Priorities.NORMAL
|
job.expiration = getNow() + Priorities.NORMAL
|
||||||
patchQueue.push(job)
|
patchQueue.push(job)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasPendingFlush) {
|
if (!hasPendingFlush) {
|
||||||
hasPendingFlush = true
|
hasPendingFlush = true
|
||||||
|
start = getNow()
|
||||||
const p = nextTick(flush)
|
const p = nextTick(flush)
|
||||||
if (onError) p.catch(onError)
|
if (onError) p.catch(onError)
|
||||||
}
|
}
|
||||||
@ -139,7 +142,6 @@ export function queueJob(
|
|||||||
|
|
||||||
function flush() {
|
function flush() {
|
||||||
let job
|
let job
|
||||||
let start = window.performance.now()
|
|
||||||
while (true) {
|
while (true) {
|
||||||
job = patchQueue.shift()
|
job = patchQueue.shift()
|
||||||
if (job) {
|
if (job) {
|
||||||
@ -147,7 +149,7 @@ function flush() {
|
|||||||
} else {
|
} else {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
const now = performance.now()
|
const now = getNow()
|
||||||
if (now - start > frameBudget && job.expiration > now) {
|
if (now - start > frameBudget && job.expiration > now) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user