refactor: remove experimental hooks
This commit is contained in:
parent
77dae71062
commit
2f165c1e87
@ -1,111 +0,0 @@
|
|||||||
import { useState, h, nextTick, useEffect, Component } from '../src'
|
|
||||||
import { renderInstance, serialize, triggerEvent } from '@vue/runtime-test'
|
|
||||||
|
|
||||||
describe('hooks', () => {
|
|
||||||
it('useState', async () => {
|
|
||||||
class Counter extends Component {
|
|
||||||
render() {
|
|
||||||
const [count, setCount] = useState(0)
|
|
||||||
return h(
|
|
||||||
'div',
|
|
||||||
{
|
|
||||||
onClick: () => {
|
|
||||||
setCount(count + 1)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
count
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const counter = await renderInstance(Counter)
|
|
||||||
expect(serialize(counter.$el)).toBe(`<div>0</div>`)
|
|
||||||
|
|
||||||
triggerEvent(counter.$el, 'click')
|
|
||||||
await nextTick()
|
|
||||||
expect(serialize(counter.$el)).toBe(`<div>1</div>`)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should be usable via hooks() method', async () => {
|
|
||||||
class Counter extends Component {
|
|
||||||
hooks() {
|
|
||||||
const [count, setCount] = useState(0)
|
|
||||||
return {
|
|
||||||
count,
|
|
||||||
setCount
|
|
||||||
}
|
|
||||||
}
|
|
||||||
render() {
|
|
||||||
const { count, setCount } = this as any
|
|
||||||
return h(
|
|
||||||
'div',
|
|
||||||
{
|
|
||||||
onClick: () => {
|
|
||||||
setCount(count + 1)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
count
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const counter = await renderInstance(Counter)
|
|
||||||
expect(serialize(counter.$el)).toBe(`<div>0</div>`)
|
|
||||||
|
|
||||||
triggerEvent(counter.$el, 'click')
|
|
||||||
await nextTick()
|
|
||||||
expect(serialize(counter.$el)).toBe(`<div>1</div>`)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('useEffect', async () => {
|
|
||||||
let effect = -1
|
|
||||||
|
|
||||||
class Counter extends Component {
|
|
||||||
render() {
|
|
||||||
const [count, setCount] = useState(0)
|
|
||||||
useEffect(() => {
|
|
||||||
effect = count
|
|
||||||
})
|
|
||||||
return h(
|
|
||||||
'div',
|
|
||||||
{
|
|
||||||
onClick: () => {
|
|
||||||
setCount(count + 1)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
count
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const counter = await renderInstance(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
|
|
||||||
})
|
|
||||||
|
|
||||||
it('useData', () => {
|
|
||||||
// TODO
|
|
||||||
})
|
|
||||||
|
|
||||||
it('useMounted/useUnmounted/useUpdated', () => {
|
|
||||||
// TODO
|
|
||||||
})
|
|
||||||
|
|
||||||
it('useWatch', () => {
|
|
||||||
// TODO
|
|
||||||
})
|
|
||||||
|
|
||||||
it('useComputed', () => {
|
|
||||||
// TODO
|
|
||||||
})
|
|
||||||
})
|
|
@ -45,7 +45,6 @@ interface PublicInstanceMethods {
|
|||||||
|
|
||||||
export interface APIMethods<P = {}, D = {}> {
|
export interface APIMethods<P = {}, D = {}> {
|
||||||
data(): Partial<D>
|
data(): Partial<D>
|
||||||
hooks(): any
|
|
||||||
render(props: Readonly<P>, slots: Slots, attrs: Data, parentVNode: VNode): any
|
render(props: Readonly<P>, slots: Slots, attrs: Data, parentVNode: VNode): any
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,7 +135,6 @@ class InternalComponent implements PublicInstanceMethods {
|
|||||||
_queueJob: ((fn: () => void) => void) | null = null
|
_queueJob: ((fn: () => void) => void) | null = null
|
||||||
_isVue: boolean = true
|
_isVue: boolean = true
|
||||||
_inactiveRoot: boolean = false
|
_inactiveRoot: boolean = false
|
||||||
_hookProps: any = null
|
|
||||||
|
|
||||||
constructor(props?: object) {
|
constructor(props?: object) {
|
||||||
if (props === void 0) {
|
if (props === void 0) {
|
||||||
|
@ -90,7 +90,6 @@ type ReservedKeys = { [K in keyof (APIMethods & LifecycleMethods)]: 1 }
|
|||||||
export const reservedMethods: ReservedKeys = {
|
export const reservedMethods: ReservedKeys = {
|
||||||
data: 1,
|
data: 1,
|
||||||
render: 1,
|
render: 1,
|
||||||
hooks: 1,
|
|
||||||
beforeCreate: 1,
|
beforeCreate: 1,
|
||||||
created: 1,
|
created: 1,
|
||||||
beforeMount: 1,
|
beforeMount: 1,
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import { ComponentInstance } from './component'
|
import { ComponentInstance } from './component'
|
||||||
import { isFunction, isReservedKey } from '@vue/shared'
|
import { isFunction, isReservedKey } from '@vue/shared'
|
||||||
import { warn } from './warning'
|
|
||||||
import { isRendering } from './componentRenderUtils'
|
import { isRendering } from './componentRenderUtils'
|
||||||
import { isObservable } from '@vue/observer'
|
|
||||||
import { reservedMethods } from './componentOptions'
|
import { reservedMethods } from './componentOptions'
|
||||||
|
import { warn } from './warning'
|
||||||
|
|
||||||
const bindCache = new WeakMap()
|
const bindCache = new WeakMap()
|
||||||
|
|
||||||
@ -37,9 +36,6 @@ const renderProxyHandlers = {
|
|||||||
) {
|
) {
|
||||||
// computed
|
// computed
|
||||||
return i[key]()
|
return i[key]()
|
||||||
} else if ((i = target._hookProps) !== null && i.hasOwnProperty(key)) {
|
|
||||||
// hooks injections
|
|
||||||
return i[key]
|
|
||||||
} else if (key[0] !== '_') {
|
} else if (key[0] !== '_') {
|
||||||
if (
|
if (
|
||||||
__DEV__ &&
|
__DEV__ &&
|
||||||
@ -81,16 +77,6 @@ const renderProxyHandlers = {
|
|||||||
if ((i = target._rawData) !== null && i.hasOwnProperty(key)) {
|
if ((i = target._rawData) !== null && i.hasOwnProperty(key)) {
|
||||||
target.$data[key] = value
|
target.$data[key] = value
|
||||||
return true
|
return true
|
||||||
} else if ((i = target._hookProps) !== null && i.hasOwnProperty(key)) {
|
|
||||||
if (__DEV__ && !isObservable(i)) {
|
|
||||||
warn(
|
|
||||||
`attempting to mutate a property returned from hooks(), but the ` +
|
|
||||||
`value is not observable.`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
// this enables returning observable objects from hooks()
|
|
||||||
i[key] = value
|
|
||||||
return true
|
|
||||||
} else {
|
} else {
|
||||||
return Reflect.set(target, key, value, receiver)
|
return Reflect.set(target, key, value, receiver)
|
||||||
}
|
}
|
||||||
|
@ -4,26 +4,13 @@ import { resolveProps } from './componentProps'
|
|||||||
import { handleError, ErrorTypes } from './errorHandling'
|
import { handleError, ErrorTypes } from './errorHandling'
|
||||||
import { VNodeFlags, ChildrenFlags } from './flags'
|
import { VNodeFlags, ChildrenFlags } from './flags'
|
||||||
import { EMPTY_OBJ, isArray, isObject } from '@vue/shared'
|
import { EMPTY_OBJ, isArray, isObject } from '@vue/shared'
|
||||||
import { setCurrentInstance, unsetCurrentInstance } from './experimental/hooks'
|
|
||||||
|
|
||||||
export let isRendering = false
|
export let isRendering = false
|
||||||
|
|
||||||
export function renderInstanceRoot(instance: ComponentInstance): VNode {
|
export function renderInstanceRoot(instance: ComponentInstance): VNode {
|
||||||
let vnode
|
let vnode
|
||||||
const {
|
const { render, $proxy, $props, $slots, $attrs, $parentVNode } = instance
|
||||||
$options: { hooks },
|
|
||||||
render,
|
|
||||||
$proxy,
|
|
||||||
$props,
|
|
||||||
$slots,
|
|
||||||
$attrs,
|
|
||||||
$parentVNode
|
|
||||||
} = instance
|
|
||||||
try {
|
try {
|
||||||
setCurrentInstance(instance)
|
|
||||||
if (hooks) {
|
|
||||||
instance._hookProps = hooks.call($proxy, $props) || null
|
|
||||||
}
|
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
isRendering = true
|
isRendering = true
|
||||||
}
|
}
|
||||||
@ -31,7 +18,6 @@ export function renderInstanceRoot(instance: ComponentInstance): VNode {
|
|||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
isRendering = false
|
isRendering = false
|
||||||
}
|
}
|
||||||
unsetCurrentInstance()
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
handleError(err, instance, ErrorTypes.RENDER)
|
handleError(err, instance, ErrorTypes.RENDER)
|
||||||
}
|
}
|
||||||
|
@ -1258,7 +1258,7 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
$proxy,
|
$proxy,
|
||||||
$options: { beforeMount, renderTracked, renderTriggered }
|
$options: { beforeMount, mounted, renderTracked, renderTriggered }
|
||||||
} = instance
|
} = instance
|
||||||
|
|
||||||
instance.$forceUpdate = () => {
|
instance.$forceUpdate = () => {
|
||||||
@ -1318,9 +1318,6 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
if (vnode.ref) {
|
if (vnode.ref) {
|
||||||
vnode.ref($proxy)
|
vnode.ref($proxy)
|
||||||
}
|
}
|
||||||
// retrieve mounted value after initial render so that we get
|
|
||||||
// to inject effects in hooks
|
|
||||||
const { mounted } = instance.$options
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
callLifecycleHookWithHandler(mounted, $proxy, ErrorTypes.MOUNTED)
|
callLifecycleHookWithHandler(mounted, $proxy, ErrorTypes.MOUNTED)
|
||||||
}
|
}
|
||||||
|
@ -1,201 +0,0 @@
|
|||||||
import { ComponentInstance } from '../component'
|
|
||||||
import { mergeLifecycleHooks, WatchOptions } from '../componentOptions'
|
|
||||||
import { observable, computed } from '@vue/observer'
|
|
||||||
import { setupWatcher } from '../componentWatch'
|
|
||||||
|
|
||||||
type RawEffect = () => (() => void) | void
|
|
||||||
|
|
||||||
type Effect = RawEffect & {
|
|
||||||
current?: RawEffect | null | void
|
|
||||||
}
|
|
||||||
|
|
||||||
type EffectRecord = {
|
|
||||||
effect: Effect
|
|
||||||
cleanup: Effect
|
|
||||||
deps: any[] | void
|
|
||||||
}
|
|
||||||
|
|
||||||
type Ref<T> = { current: T }
|
|
||||||
|
|
||||||
type HookState = {
|
|
||||||
state: any
|
|
||||||
effects: Record<number, EffectRecord>
|
|
||||||
refs: Record<number, Ref<any>>
|
|
||||||
}
|
|
||||||
|
|
||||||
let currentInstance: ComponentInstance | null = null
|
|
||||||
let isMounting: boolean = false
|
|
||||||
let callIndex: number = 0
|
|
||||||
|
|
||||||
const hooksStateMap = new WeakMap<ComponentInstance, HookState>()
|
|
||||||
|
|
||||||
export function setCurrentInstance(instance: ComponentInstance) {
|
|
||||||
currentInstance = instance
|
|
||||||
isMounting = !currentInstance._mounted
|
|
||||||
callIndex = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
export function unsetCurrentInstance() {
|
|
||||||
currentInstance = null
|
|
||||||
}
|
|
||||||
|
|
||||||
function ensureCurrentInstance() {
|
|
||||||
if (!currentInstance) {
|
|
||||||
throw new Error(
|
|
||||||
`invalid hooks call` +
|
|
||||||
(__DEV__
|
|
||||||
? `. Hooks can only be called in one of the following: ` +
|
|
||||||
`render(), hooks(), or withHooks().`
|
|
||||||
: ``)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCurrentHookState(): HookState {
|
|
||||||
ensureCurrentInstance()
|
|
||||||
let hookState = hooksStateMap.get(currentInstance as ComponentInstance)
|
|
||||||
if (!hookState) {
|
|
||||||
hookState = {
|
|
||||||
state: observable({}),
|
|
||||||
effects: {},
|
|
||||||
refs: {}
|
|
||||||
}
|
|
||||||
hooksStateMap.set(currentInstance as ComponentInstance, hookState)
|
|
||||||
}
|
|
||||||
return hookState
|
|
||||||
}
|
|
||||||
|
|
||||||
// React compatible hooks ------------------------------------------------------
|
|
||||||
|
|
||||||
export function useState<T>(initial: T): [T, (newValue: T) => void] {
|
|
||||||
const id = ++callIndex
|
|
||||||
const { state } = getCurrentHookState()
|
|
||||||
const set = (newValue: any) => {
|
|
||||||
state[id] = newValue
|
|
||||||
}
|
|
||||||
if (isMounting) {
|
|
||||||
set(initial)
|
|
||||||
}
|
|
||||||
return [state[id], set]
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useEffect(rawEffect: Effect, deps?: any[]) {
|
|
||||||
const id = ++callIndex
|
|
||||||
if (isMounting) {
|
|
||||||
const cleanup: Effect = () => {
|
|
||||||
const { current } = cleanup
|
|
||||||
if (current) {
|
|
||||||
current()
|
|
||||||
cleanup.current = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const effect: Effect = () => {
|
|
||||||
const { current } = effect
|
|
||||||
if (current) {
|
|
||||||
cleanup.current = current()
|
|
||||||
effect.current = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
effect.current = rawEffect
|
|
||||||
getCurrentHookState().effects[id] = {
|
|
||||||
effect,
|
|
||||||
cleanup,
|
|
||||||
deps
|
|
||||||
}
|
|
||||||
|
|
||||||
injectEffect(currentInstance as ComponentInstance, 'mounted', effect)
|
|
||||||
injectEffect(currentInstance as ComponentInstance, 'unmounted', cleanup)
|
|
||||||
if (!deps || deps.length !== 0) {
|
|
||||||
injectEffect(currentInstance as ComponentInstance, 'updated', effect)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const record = getCurrentHookState().effects[id]
|
|
||||||
const { effect, cleanup, deps: prevDeps = [] } = record
|
|
||||||
record.deps = deps
|
|
||||||
if (!deps || hasDepsChanged(deps, prevDeps)) {
|
|
||||||
cleanup()
|
|
||||||
effect.current = rawEffect
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function hasDepsChanged(deps: any[], prevDeps: any[]): boolean {
|
|
||||||
if (deps.length !== prevDeps.length) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
for (let i = 0; i < deps.length; i++) {
|
|
||||||
if (deps[i] !== prevDeps[i]) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
function injectEffect(
|
|
||||||
instance: ComponentInstance,
|
|
||||||
key: string,
|
|
||||||
effect: Effect
|
|
||||||
) {
|
|
||||||
const existing = instance.$options[key]
|
|
||||||
;(instance.$options as any)[key] = existing
|
|
||||||
? mergeLifecycleHooks(existing, effect)
|
|
||||||
: effect
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useRef<T>(initial?: T): Ref<T> {
|
|
||||||
const id = ++callIndex
|
|
||||||
const { refs } = getCurrentHookState()
|
|
||||||
return isMounting ? (refs[id] = { current: initial }) : refs[id]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Vue API hooks ---------------------------------------------------------------
|
|
||||||
|
|
||||||
export function useData<T>(initial: T): T {
|
|
||||||
const id = ++callIndex
|
|
||||||
const { state } = getCurrentHookState()
|
|
||||||
if (isMounting) {
|
|
||||||
state[id] = initial
|
|
||||||
}
|
|
||||||
return state[id]
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useMounted(fn: () => void) {
|
|
||||||
useEffect(fn, [])
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useUnmounted(fn: () => void) {
|
|
||||||
useEffect(() => fn, [])
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useUpdated(fn: () => void, deps?: any[]) {
|
|
||||||
const isMount = useRef(true)
|
|
||||||
useEffect(() => {
|
|
||||||
if (isMount.current) {
|
|
||||||
isMount.current = false
|
|
||||||
} else {
|
|
||||||
return fn()
|
|
||||||
}
|
|
||||||
}, deps)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useWatch<T>(
|
|
||||||
getter: () => T,
|
|
||||||
cb: (val: T, oldVal: T) => void,
|
|
||||||
options?: WatchOptions
|
|
||||||
) {
|
|
||||||
ensureCurrentInstance()
|
|
||||||
if (isMounting) {
|
|
||||||
setupWatcher(currentInstance as ComponentInstance, getter, cb, options)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useComputed<T>(getter: () => T): T {
|
|
||||||
ensureCurrentInstance()
|
|
||||||
const id = `__hooksComputed${++callIndex}`
|
|
||||||
const instance = currentInstance as ComponentInstance
|
|
||||||
const handles = instance._computedGetters || (instance._computedGetters = {})
|
|
||||||
if (isMounting) {
|
|
||||||
handles[id] = computed(getter)
|
|
||||||
}
|
|
||||||
return handles[id]()
|
|
||||||
}
|
|
@ -32,19 +32,6 @@ export { mixins } from './optional/mixins'
|
|||||||
export { EventEmitter } from './optional/eventEmitter'
|
export { EventEmitter } from './optional/eventEmitter'
|
||||||
export { memoize } from './optional/memoize'
|
export { memoize } from './optional/memoize'
|
||||||
|
|
||||||
// Experimental APIs
|
|
||||||
export {
|
|
||||||
useState,
|
|
||||||
useEffect,
|
|
||||||
useRef,
|
|
||||||
useData,
|
|
||||||
useWatch,
|
|
||||||
useComputed,
|
|
||||||
useMounted,
|
|
||||||
useUnmounted,
|
|
||||||
useUpdated
|
|
||||||
} from './experimental/hooks'
|
|
||||||
|
|
||||||
// flags & types
|
// flags & types
|
||||||
export { ComponentType, ComponentClass, FunctionalComponent } from './component'
|
export { ComponentType, ComponentClass, FunctionalComponent } from './component'
|
||||||
export { VNodeFlags, ChildrenFlags } from './flags'
|
export { VNodeFlags, ChildrenFlags } from './flags'
|
||||||
|
Loading…
Reference in New Issue
Block a user