]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(hmr): make hmr working with class components (#2144)
authorKatashin <ktsn55@gmail.com>
Fri, 18 Sep 2020 04:14:59 +0000 (12:14 +0800)
committerGitHub <noreply@github.com>
Fri, 18 Sep 2020 04:14:59 +0000 (00:14 -0400)
packages/runtime-core/__tests__/hmr.spec.ts
packages/runtime-core/src/component.ts
packages/runtime-core/src/hmr.ts
packages/runtime-core/src/vnode.ts

index a53c7a3e258dd2d21554e88c71e616c23b04f773..31c910d17d46e5029913abf4dbe6c81c095660c6 100644 (file)
@@ -148,6 +148,49 @@ describe('hot module replacement', () => {
     expect(mountSpy).toHaveBeenCalledTimes(1)
   })
 
+  test('reload class component', async () => {
+    const root = nodeOps.createElement('div')
+    const childId = 'test4-child'
+    const unmountSpy = jest.fn()
+    const mountSpy = jest.fn()
+
+    class Child {
+      static __vccOpts: ComponentOptions = {
+        __hmrId: childId,
+        data() {
+          return { count: 0 }
+        },
+        unmounted: unmountSpy,
+        render: compileToFunction(`<div @click="count++">{{ count }}</div>`)
+      }
+    }
+    createRecord(childId)
+
+    const Parent: ComponentOptions = {
+      render: () => h(Child)
+    }
+
+    render(h(Parent), root)
+    expect(serializeInner(root)).toBe(`<div>0</div>`)
+
+    class UpdatedChild {
+      static __vccOpts: ComponentOptions = {
+        __hmrId: childId,
+        data() {
+          return { count: 1 }
+        },
+        mounted: mountSpy,
+        render: compileToFunction(`<div @click="count++">{{ count }}</div>`)
+      }
+    }
+
+    reload(childId, UpdatedChild)
+    await nextTick()
+    expect(serializeInner(root)).toBe(`<div>1</div>`)
+    expect(unmountSpy).toHaveBeenCalledTimes(1)
+    expect(mountSpy).toHaveBeenCalledTimes(1)
+  })
+
   // #1156 - static nodes should retain DOM element reference across updates
   // when HMR is active
   test('static el reference', async () => {
index ac482d6707a7b1879eedebca54cce967220e056f..78abad60a8afafef65340d02208d4a900081640c 100644 (file)
@@ -800,3 +800,7 @@ export function formatComponentName(
 
   return name ? classify(name) : isRoot ? `App` : `Anonymous`
 }
+
+export function isClassComponent(value: unknown): value is ClassComponent {
+  return isFunction(value) && '__vccOpts' in value
+}
index b831498baa4dec0b3ba9859d0389f0c4c7de9541..df557313565589c4ac80fdc53fe1a43cc56f7fda 100644 (file)
@@ -3,7 +3,9 @@ import {
   ConcreteComponent,
   ComponentInternalInstance,
   ComponentOptions,
-  InternalRenderFunction
+  InternalRenderFunction,
+  ClassComponent,
+  isClassComponent
 } from './component'
 import { queueJob, queuePostFlushCb } from './scheduler'
 import { extend } from '@vue/shared'
@@ -83,7 +85,7 @@ function rerender(id: string, newRender?: Function) {
   })
 }
 
-function reload(id: string, newComp: ComponentOptions) {
+function reload(id: string, newComp: ComponentOptions | ClassComponent) {
   const record = map.get(id)
   if (!record) return
   // Array.from creates a snapshot which avoids the set being mutated during
@@ -92,6 +94,7 @@ function reload(id: string, newComp: ComponentOptions) {
     const comp = instance.type
     if (!hmrDirtyComponents.has(comp)) {
       // 1. Update existing comp definition to match new one
+      newComp = isClassComponent(newComp) ? newComp.__vccOpts : newComp
       extend(comp, newComp)
       for (const key in comp) {
         if (!(key in newComp)) {
index 9a11090a37cf7d1fba36e55c504a39779c155d40..56ff85f5eea0014a6280fda9c5c4c0f665735234 100644 (file)
@@ -17,7 +17,8 @@ import {
   Data,
   ConcreteComponent,
   ClassComponent,
-  Component
+  Component,
+  isClassComponent
 } from './component'
 import { RawSlots } from './componentSlots'
 import { isProxy, Ref, toRaw, ReactiveFlags } from '@vue/reactivity'
@@ -340,7 +341,7 @@ function _createVNode(
   }
 
   // class component normalization.
-  if (isFunction(type) && '__vccOpts' in type) {
+  if (isClassComponent(type)) {
     type = type.__vccOpts
   }