diff --git a/packages/runtime-core/__tests__/hooks.spec.ts b/packages/runtime-core/__tests__/hooks.spec.ts
new file mode 100644
index 00000000..51ccd39f
--- /dev/null
+++ b/packages/runtime-core/__tests__/hooks.spec.ts
@@ -0,0 +1,60 @@
+import { withHooks, useState, h, nextTick, useEffect } from '../src'
+import { renderIntsance, serialize, triggerEvent } from '@vue/runtime-test'
+
+describe('hooks', () => {
+ it('useState', async () => {
+ const Counter = withHooks(() => {
+ const [count, setCount] = useState(0)
+ return h(
+ 'div',
+ {
+ onClick: () => {
+ setCount(count + 1)
+ }
+ },
+ count
+ )
+ })
+
+ const counter = renderIntsance(Counter)
+ expect(serialize(counter.$el)).toBe(`
0
`)
+
+ triggerEvent(counter.$el, 'click')
+ await nextTick()
+ expect(serialize(counter.$el)).toBe(`1
`)
+ })
+
+ it('useEffect', async () => {
+ let effect = -1
+
+ const Counter = withHooks(() => {
+ const [count, setCount] = useState(0)
+ useEffect(() => {
+ effect = count
+ })
+ return h(
+ 'div',
+ {
+ onClick: () => {
+ setCount(count + 1)
+ }
+ },
+ count
+ )
+ })
+
+ const counter = renderIntsance(Counter)
+ expect(effect).toBe(0)
+ triggerEvent(counter.$el, 'click')
+ await nextTick()
+ expect(effect).toBe(1)
+ })
+
+ it('useEffect with empty keys', async () => {
+ // TODO
+ })
+
+ it('useEffect with keys', async () => {
+ // TODO
+ })
+})
diff --git a/packages/runtime-core/src/optional/hooks.ts b/packages/runtime-core/src/optional/hooks.ts
index 9303613d..c195c2c7 100644
--- a/packages/runtime-core/src/optional/hooks.ts
+++ b/packages/runtime-core/src/optional/hooks.ts
@@ -1,4 +1,4 @@
-import { ComponentInstance, FunctionalComponent } from '../component'
+import { ComponentInstance, FunctionalComponent, Component } from '../component'
import { mergeLifecycleHooks, Data } from '../componentOptions'
import { VNode, Slots } from '../vdom'
import { observable } from '@vue/observer'
@@ -36,7 +36,7 @@ export function unsetCurrentInstance() {
currentInstance = null
}
-export function useState(initial: any) {
+export function useState(initial: T): [T, (newValue: T) => void] {
if (!currentInstance) {
throw new Error(
`useState must be called in a function passed to withHooks.`
@@ -107,20 +107,20 @@ function injectEffect(
: effect
}
-export function withHooks(render: T): T {
- return {
- displayName: render.name,
+export function withHooks(render: FunctionalComponent): new () => Component {
+ return class ComponentWithHooks extends Component {
+ static displayName = render.name
created() {
- hooksState.set(this._self, {
+ hooksState.set((this as any)._self, {
state: observable({}),
effects: []
})
- },
+ }
render(props: Data, slots: Slots, attrs: Data, parentVNode: VNode) {
- setCurrentInstance(this._self)
+ setCurrentInstance((this as any)._self)
const ret = render(props, slots, attrs, parentVNode)
unsetCurrentInstance()
return ret
}
- } as any
+ }
}