fix(hooks): fix effect update & cleanup

This commit is contained in:
Evan You 2018-10-28 12:10:29 -04:00
parent a7bcb7898f
commit 98e79943d2
2 changed files with 44 additions and 28 deletions

View File

@ -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

View File

@ -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