fix(hooks): fix effect update & cleanup
This commit is contained in:
parent
a7bcb7898f
commit
98e79943d2
@ -22,6 +22,7 @@ import {
|
|||||||
} from './componentUtils'
|
} from './componentUtils'
|
||||||
import { KeepAliveSymbol } from './optional/keepAlive'
|
import { KeepAliveSymbol } from './optional/keepAlive'
|
||||||
import { pushWarningContext, popWarningContext } from './warning'
|
import { pushWarningContext, popWarningContext } from './warning'
|
||||||
|
import { handleError, ErrorTypes } from './errorHandling'
|
||||||
|
|
||||||
interface NodeOps {
|
interface NodeOps {
|
||||||
createElement: (tag: string, isSVG?: boolean) => any
|
createElement: (tag: string, isSVG?: boolean) => any
|
||||||
@ -1162,8 +1163,12 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
beforeMount.call($proxy)
|
beforeMount.call($proxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const errorSchedulerHandler = (err: Error) => {
|
||||||
|
handleError(err, instance, ErrorTypes.SCHEDULER)
|
||||||
|
}
|
||||||
|
|
||||||
const queueUpdate = (instance.$forceUpdate = () => {
|
const queueUpdate = (instance.$forceUpdate = () => {
|
||||||
queueJob(instance._updateHandle, flushHooks)
|
queueJob(instance._updateHandle, flushHooks, errorSchedulerHandler)
|
||||||
})
|
})
|
||||||
|
|
||||||
instance._updateHandle = autorun(
|
instance._updateHandle = autorun(
|
||||||
@ -1227,7 +1232,7 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
$vnode: prevVNode,
|
$vnode: prevVNode,
|
||||||
$parentVNode,
|
$parentVNode,
|
||||||
$proxy,
|
$proxy,
|
||||||
$options: { beforeUpdate, updated }
|
$options: { beforeUpdate }
|
||||||
} = instance
|
} = instance
|
||||||
if (beforeUpdate) {
|
if (beforeUpdate) {
|
||||||
beforeUpdate.call($proxy, prevVNode)
|
beforeUpdate.call($proxy, prevVNode)
|
||||||
@ -1256,6 +1261,7 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { updated } = instance.$options
|
||||||
if (updated) {
|
if (updated) {
|
||||||
// Because the child's update is executed by the scheduler and not
|
// Because the child's update is executed by the scheduler and not
|
||||||
// synchronously within the parent's update call, the child's updated hook
|
// synchronously within the parent's update call, the child's updated hook
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { ComponentInstance, APIMethods } from '../component'
|
import { ComponentInstance, FunctionalComponent } from '../component'
|
||||||
import { mergeLifecycleHooks, Data } from '../componentOptions'
|
import { mergeLifecycleHooks, Data } from '../componentOptions'
|
||||||
import { VNode, Slots } from '../vdom'
|
import { VNode, Slots } from '../vdom'
|
||||||
import { observable } from '@vue/observer'
|
import { observable } from '@vue/observer'
|
||||||
@ -11,18 +11,31 @@ type Effect = RawEffect & {
|
|||||||
|
|
||||||
type EffectRecord = {
|
type EffectRecord = {
|
||||||
effect: Effect
|
effect: Effect
|
||||||
|
cleanup: Effect
|
||||||
deps: any[] | void
|
deps: any[] | void
|
||||||
}
|
}
|
||||||
|
|
||||||
type ComponentInstanceWithHook = ComponentInstance & {
|
type HookState = {
|
||||||
_state: Record<number, any>
|
state: any
|
||||||
_effects: EffectRecord[]
|
effects: EffectRecord[]
|
||||||
}
|
}
|
||||||
|
|
||||||
let currentInstance: ComponentInstanceWithHook | null = null
|
let currentInstance: ComponentInstance | null = null
|
||||||
let isMounting: boolean = false
|
let isMounting: boolean = false
|
||||||
let callIndex: number = 0
|
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) {
|
export function useState(initial: any) {
|
||||||
if (!currentInstance) {
|
if (!currentInstance) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@ -30,7 +43,7 @@ export function useState(initial: any) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
const id = ++callIndex
|
const id = ++callIndex
|
||||||
const state = currentInstance._state
|
const { state } = hooksState.get(currentInstance) as HookState
|
||||||
const set = (newValue: any) => {
|
const set = (newValue: any) => {
|
||||||
state[id] = newValue
|
state[id] = newValue
|
||||||
}
|
}
|
||||||
@ -56,36 +69,35 @@ export function useEffect(rawEffect: Effect, deps?: any[]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const effect: Effect = () => {
|
const effect: Effect = () => {
|
||||||
cleanup()
|
|
||||||
const { current } = effect
|
const { current } = effect
|
||||||
if (current) {
|
if (current) {
|
||||||
effect.current = current()
|
cleanup.current = current()
|
||||||
|
effect.current = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
effect.current = rawEffect
|
effect.current = rawEffect
|
||||||
|
;(hooksState.get(currentInstance) as HookState).effects[id] = {
|
||||||
currentInstance._effects[id] = {
|
|
||||||
effect,
|
effect,
|
||||||
|
cleanup,
|
||||||
deps
|
deps
|
||||||
}
|
}
|
||||||
|
|
||||||
injectEffect(currentInstance, 'mounted', effect)
|
injectEffect(currentInstance, 'mounted', effect)
|
||||||
injectEffect(currentInstance, 'unmounted', cleanup)
|
injectEffect(currentInstance, 'unmounted', cleanup)
|
||||||
if (!deps) {
|
injectEffect(currentInstance, 'updated', effect)
|
||||||
injectEffect(currentInstance, 'updated', effect)
|
|
||||||
}
|
|
||||||
} else {
|
} 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])) {
|
if (!deps || deps.some((d, i) => d !== prevDeps[i])) {
|
||||||
|
cleanup()
|
||||||
effect.current = rawEffect
|
effect.current = rawEffect
|
||||||
} else {
|
|
||||||
effect.current = null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function injectEffect(
|
function injectEffect(
|
||||||
instance: ComponentInstanceWithHook,
|
instance: ComponentInstance,
|
||||||
key: string,
|
key: string,
|
||||||
effect: Effect
|
effect: Effect
|
||||||
) {
|
) {
|
||||||
@ -95,21 +107,19 @@ function injectEffect(
|
|||||||
: effect
|
: effect
|
||||||
}
|
}
|
||||||
|
|
||||||
export function withHooks<T extends APIMethods['render']>(render: T): T {
|
export function withHooks<T extends FunctionalComponent>(render: T): T {
|
||||||
return {
|
return {
|
||||||
displayName: render.name,
|
displayName: render.name,
|
||||||
created() {
|
created() {
|
||||||
const { _self } = this
|
hooksState.set(this._self, {
|
||||||
_self._state = observable({})
|
state: observable({}),
|
||||||
_self._effects = []
|
effects: []
|
||||||
|
})
|
||||||
},
|
},
|
||||||
render(props: Data, slots: Slots, attrs: Data, parentVNode: VNode) {
|
render(props: Data, slots: Slots, attrs: Data, parentVNode: VNode) {
|
||||||
const { _self } = this
|
setCurrentInstance(this._self)
|
||||||
callIndex = 0
|
|
||||||
currentInstance = _self
|
|
||||||
isMounting = !_self._mounted
|
|
||||||
const ret = render(props, slots, attrs, parentVNode)
|
const ret = render(props, slots, attrs, parentVNode)
|
||||||
currentInstance = null
|
unsetCurrentInstance()
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
} as any
|
} as any
|
||||||
|
Loading…
Reference in New Issue
Block a user