} from './componentUtils'
import { KeepAliveSymbol } from './optional/keepAlive'
import { pushWarningContext, popWarningContext } from './warning'
+import { handleError, ErrorTypes } from './errorHandling'
interface NodeOps {
createElement: (tag: string, isSVG?: boolean) => any
beforeMount.call($proxy)
}
+ const errorSchedulerHandler = (err: Error) => {
+ handleError(err, instance, ErrorTypes.SCHEDULER)
+ }
+
const queueUpdate = (instance.$forceUpdate = () => {
- queueJob(instance._updateHandle, flushHooks)
+ queueJob(instance._updateHandle, flushHooks, errorSchedulerHandler)
})
instance._updateHandle = autorun(
$vnode: prevVNode,
$parentVNode,
$proxy,
- $options: { beforeUpdate, updated }
+ $options: { beforeUpdate }
} = instance
if (beforeUpdate) {
beforeUpdate.call($proxy, prevVNode)
}
}
+ const { updated } = instance.$options
if (updated) {
// Because the child's update is executed by the scheduler and not
// synchronously within the parent's update call, the child's updated hook
-import { ComponentInstance, APIMethods } from '../component'
+import { ComponentInstance, FunctionalComponent } from '../component'
import { mergeLifecycleHooks, Data } from '../componentOptions'
import { VNode, Slots } from '../vdom'
import { observable } from '@vue/observer'
type EffectRecord = {
effect: Effect
+ cleanup: Effect
deps: any[] | void
}
-type ComponentInstanceWithHook = ComponentInstance & {
- _state: Record<number, any>
- _effects: EffectRecord[]
+type HookState = {
+ state: any
+ effects: EffectRecord[]
}
-let currentInstance: ComponentInstanceWithHook | null = null
+let currentInstance: ComponentInstance | null = null
let isMounting: boolean = false
let callIndex: number = 0
+const hooksState = new WeakMap<ComponentInstance, HookState>()
+
+export function setCurrentInstance(instance: ComponentInstance) {
+ currentInstance = instance
+ isMounting = !currentInstance._mounted
+ callIndex = 0
+}
+
+export function unsetCurrentInstance() {
+ currentInstance = null
+}
+
export function useState(initial: any) {
if (!currentInstance) {
throw new Error(
)
}
const id = ++callIndex
- const state = currentInstance._state
+ const { state } = hooksState.get(currentInstance) as HookState
const set = (newValue: any) => {
state[id] = newValue
}
}
}
const effect: Effect = () => {
- cleanup()
const { current } = effect
if (current) {
- effect.current = current()
+ cleanup.current = current()
+ effect.current = null
}
}
effect.current = rawEffect
-
- currentInstance._effects[id] = {
+ ;(hooksState.get(currentInstance) as HookState).effects[id] = {
effect,
+ cleanup,
deps
}
injectEffect(currentInstance, 'mounted', effect)
injectEffect(currentInstance, 'unmounted', cleanup)
- if (!deps) {
- injectEffect(currentInstance, 'updated', effect)
- }
+ injectEffect(currentInstance, 'updated', effect)
} else {
- const { effect, deps: prevDeps = [] } = currentInstance._effects[id]
+ const record = (hooksState.get(currentInstance) as HookState).effects[id]
+ const { effect, cleanup, deps: prevDeps = [] } = record
+ record.deps = deps
if (!deps || deps.some((d, i) => d !== prevDeps[i])) {
+ cleanup()
effect.current = rawEffect
- } else {
- effect.current = null
}
}
}
function injectEffect(
- instance: ComponentInstanceWithHook,
+ instance: ComponentInstance,
key: string,
effect: Effect
) {
: effect
}
-export function withHooks<T extends APIMethods['render']>(render: T): T {
+export function withHooks<T extends FunctionalComponent>(render: T): T {
return {
displayName: render.name,
created() {
- const { _self } = this
- _self._state = observable({})
- _self._effects = []
+ hooksState.set(this._self, {
+ state: observable({}),
+ effects: []
+ })
},
render(props: Data, slots: Slots, attrs: Data, parentVNode: VNode) {
- const { _self } = this
- callIndex = 0
- currentInstance = _self
- isMounting = !_self._mounted
+ setCurrentInstance(this._self)
const ret = render(props, slots, attrs, parentVNode)
- currentInstance = null
+ unsetCurrentInstance()
return ret
}
} as any