feat: 2.x options support
This commit is contained in:
parent
c833db9c97
commit
a6616e4210
@ -2,8 +2,9 @@ module.exports = {
|
|||||||
preset: 'ts-jest',
|
preset: 'ts-jest',
|
||||||
globals: {
|
globals: {
|
||||||
__DEV__: true,
|
__DEV__: true,
|
||||||
__COMPAT__: false,
|
__JSDOM__: true,
|
||||||
__JSDOM__: true
|
__FEATURE_OPTIONS__: true,
|
||||||
|
__FEATURE_PRODUCTION_TIP__: false
|
||||||
},
|
},
|
||||||
coverageDirectory: 'coverage',
|
coverageDirectory: 'coverage',
|
||||||
coverageReporters: ['html', 'lcov', 'text'],
|
coverageReporters: ['html', 'lcov', 'text'],
|
||||||
|
@ -7,7 +7,7 @@ export interface ComputedRef<T> {
|
|||||||
readonly effect: ReactiveEffect
|
readonly effect: ReactiveEffect
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ComputedOptions<T> {
|
export interface ComputedOptions<T = any> {
|
||||||
get: () => T
|
get: () => T
|
||||||
set: (v: T) => void
|
set: (v: T) => void
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ import { capitalize } from '@vue/shared'
|
|||||||
function injectHook(
|
function injectHook(
|
||||||
type: LifecycleHooks,
|
type: LifecycleHooks,
|
||||||
hook: Function,
|
hook: Function,
|
||||||
target: ComponentInstance | null = currentInstance
|
target: ComponentInstance | null
|
||||||
) {
|
) {
|
||||||
if (target) {
|
if (target) {
|
||||||
;(target[type] || (target[type] = [])).push((...args: any[]) => {
|
;(target[type] || (target[type] = [])).push((...args: any[]) => {
|
||||||
@ -26,7 +26,7 @@ function injectHook(
|
|||||||
})
|
})
|
||||||
} else if (__DEV__) {
|
} else if (__DEV__) {
|
||||||
const apiName = `on${capitalize(
|
const apiName = `on${capitalize(
|
||||||
ErrorTypeStrings[name].replace(/ hook$/, '')
|
ErrorTypeStrings[type].replace(/ hook$/, '')
|
||||||
)}`
|
)}`
|
||||||
warn(
|
warn(
|
||||||
`${apiName} is called when there is no active component instance to be ` +
|
`${apiName} is called when there is no active component instance to be ` +
|
||||||
|
203
packages/runtime-core/src/apiOptions.ts
Normal file
203
packages/runtime-core/src/apiOptions.ts
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
import {
|
||||||
|
ComponentInstance,
|
||||||
|
Data,
|
||||||
|
ComponentOptions,
|
||||||
|
ComponentRenderProxy
|
||||||
|
} from './component'
|
||||||
|
import {
|
||||||
|
isFunction,
|
||||||
|
extend,
|
||||||
|
isString,
|
||||||
|
isObject,
|
||||||
|
isArray,
|
||||||
|
EMPTY_OBJ
|
||||||
|
} from '@vue/shared'
|
||||||
|
import { computed, ComputedOptions } from './apiReactivity'
|
||||||
|
import { watch } from './apiWatch'
|
||||||
|
import { provide, inject } from './apiInject'
|
||||||
|
import {
|
||||||
|
onBeforeMount,
|
||||||
|
onMounted,
|
||||||
|
onBeforeUpdate,
|
||||||
|
onUpdated,
|
||||||
|
onErrorCaptured,
|
||||||
|
onRenderTracked,
|
||||||
|
onBeforeUnmount,
|
||||||
|
onUnmounted
|
||||||
|
} from './apiLifecycle'
|
||||||
|
import { DebuggerEvent } from '@vue/reactivity'
|
||||||
|
|
||||||
|
type LegacyComponent =
|
||||||
|
| ComponentOptions
|
||||||
|
| {
|
||||||
|
new (): ComponentRenderProxy
|
||||||
|
options: ComponentOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO type inference for these options
|
||||||
|
export interface LegacyOptions {
|
||||||
|
el?: any
|
||||||
|
|
||||||
|
// state
|
||||||
|
data?: Data | (() => Data)
|
||||||
|
computed?: Record<string, (() => any) | ComputedOptions>
|
||||||
|
methods?: Record<string, Function>
|
||||||
|
// TODO watch array
|
||||||
|
watch?: Record<
|
||||||
|
string,
|
||||||
|
| string
|
||||||
|
| Function
|
||||||
|
| { handler: Function; deep?: boolean; immediate: boolean }
|
||||||
|
>
|
||||||
|
provide?: Data | (() => Data)
|
||||||
|
inject?:
|
||||||
|
| string[]
|
||||||
|
| Record<
|
||||||
|
string | symbol,
|
||||||
|
string | symbol | { from: string | symbol; default: any }
|
||||||
|
>
|
||||||
|
|
||||||
|
// composition
|
||||||
|
mixins?: LegacyComponent[]
|
||||||
|
extends?: LegacyComponent
|
||||||
|
|
||||||
|
// lifecycle
|
||||||
|
beforeCreate?(): void
|
||||||
|
created?(): void
|
||||||
|
beforeMount?(): void
|
||||||
|
mounted?(): void
|
||||||
|
beforeUpdate?(): void
|
||||||
|
updated?(): void
|
||||||
|
activated?(): void
|
||||||
|
decativated?(): void
|
||||||
|
beforeDestroy?(): void
|
||||||
|
destroyed?(): void
|
||||||
|
renderTracked?(e: DebuggerEvent): void
|
||||||
|
renderTriggered?(e: DebuggerEvent): void
|
||||||
|
errorCaptured?(): boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export function processOptions(instance: ComponentInstance) {
|
||||||
|
const data =
|
||||||
|
instance.data === EMPTY_OBJ ? (instance.data = {}) : instance.data
|
||||||
|
const ctx = instance.renderProxy as any
|
||||||
|
const {
|
||||||
|
data: dataOptions,
|
||||||
|
computed: computedOptions,
|
||||||
|
methods,
|
||||||
|
watch: watchOptions,
|
||||||
|
provide: provideOptions,
|
||||||
|
inject: injectOptions,
|
||||||
|
// beforeCreate is handled separately
|
||||||
|
created,
|
||||||
|
beforeMount,
|
||||||
|
mounted,
|
||||||
|
beforeUpdate,
|
||||||
|
updated,
|
||||||
|
// TODO activated
|
||||||
|
// TODO decativated
|
||||||
|
beforeDestroy,
|
||||||
|
destroyed,
|
||||||
|
renderTracked,
|
||||||
|
renderTriggered,
|
||||||
|
errorCaptured
|
||||||
|
} = instance.type as ComponentOptions
|
||||||
|
|
||||||
|
if (dataOptions) {
|
||||||
|
extend(data, isFunction(dataOptions) ? dataOptions.call(ctx) : dataOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (computedOptions) {
|
||||||
|
for (const key in computedOptions) {
|
||||||
|
data[key] = computed(computedOptions[key] as any)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (methods) {
|
||||||
|
for (const key in methods) {
|
||||||
|
data[key] = methods[key].bind(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (watchOptions) {
|
||||||
|
for (const key in watchOptions) {
|
||||||
|
const raw = watchOptions[key]
|
||||||
|
const getter = () => ctx[key]
|
||||||
|
if (isString(raw)) {
|
||||||
|
const handler = data[key]
|
||||||
|
if (isFunction(handler)) {
|
||||||
|
watch(getter, handler.bind(ctx))
|
||||||
|
} else if (__DEV__) {
|
||||||
|
// TODO warn invalid watch handler path
|
||||||
|
}
|
||||||
|
} else if (isFunction(raw)) {
|
||||||
|
watch(getter, raw.bind(ctx))
|
||||||
|
} else if (isObject(raw)) {
|
||||||
|
watch(getter, raw.handler.bind(ctx), {
|
||||||
|
deep: !!raw.deep,
|
||||||
|
lazy: !raw.immediate
|
||||||
|
})
|
||||||
|
} else if (__DEV__) {
|
||||||
|
// TODO warn invalid watch options
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (provideOptions) {
|
||||||
|
const provides = isFunction(provideOptions)
|
||||||
|
? provideOptions.call(ctx)
|
||||||
|
: provideOptions
|
||||||
|
for (const key in provides) {
|
||||||
|
provide(key, provides[key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (injectOptions) {
|
||||||
|
if (isArray(injectOptions)) {
|
||||||
|
for (let i = 0; i < injectOptions.length; i++) {
|
||||||
|
const key = injectOptions[i]
|
||||||
|
data[key] = inject(key)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (const key in injectOptions) {
|
||||||
|
const opt = injectOptions[key]
|
||||||
|
if (isObject(opt)) {
|
||||||
|
data[key] = inject(opt.from, opt.default)
|
||||||
|
} else {
|
||||||
|
data[key] = inject(opt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (created) {
|
||||||
|
created.call(ctx)
|
||||||
|
}
|
||||||
|
if (beforeMount) {
|
||||||
|
onBeforeMount(beforeMount.bind(ctx))
|
||||||
|
}
|
||||||
|
if (mounted) {
|
||||||
|
onMounted(mounted.bind(ctx))
|
||||||
|
}
|
||||||
|
if (beforeUpdate) {
|
||||||
|
onBeforeUpdate(beforeUpdate.bind(ctx))
|
||||||
|
}
|
||||||
|
if (updated) {
|
||||||
|
onUpdated(updated.bind(ctx))
|
||||||
|
}
|
||||||
|
if (errorCaptured) {
|
||||||
|
onErrorCaptured(errorCaptured.bind(ctx))
|
||||||
|
}
|
||||||
|
if (renderTracked) {
|
||||||
|
onRenderTracked(renderTracked.bind(ctx))
|
||||||
|
}
|
||||||
|
if (renderTriggered) {
|
||||||
|
onRenderTracked(renderTriggered.bind(ctx))
|
||||||
|
}
|
||||||
|
if (beforeDestroy) {
|
||||||
|
onBeforeUnmount(beforeDestroy.bind(ctx))
|
||||||
|
}
|
||||||
|
if (destroyed) {
|
||||||
|
onUnmounted(destroyed.bind(ctx))
|
||||||
|
}
|
||||||
|
}
|
@ -17,7 +17,8 @@ export {
|
|||||||
OperationTypes,
|
OperationTypes,
|
||||||
Ref,
|
Ref,
|
||||||
ComputedRef,
|
ComputedRef,
|
||||||
UnwrapRef
|
UnwrapRef,
|
||||||
|
ComputedOptions
|
||||||
} from '@vue/reactivity'
|
} from '@vue/reactivity'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -1,6 +1,13 @@
|
|||||||
import { VNode, normalizeVNode, VNodeChild, createVNode, Empty } from './vnode'
|
import { VNode, normalizeVNode, VNodeChild, createVNode, Empty } from './vnode'
|
||||||
import { ReactiveEffect, UnwrapRef, reactive, readonly } from '@vue/reactivity'
|
import { ReactiveEffect, UnwrapRef, reactive, readonly } from '@vue/reactivity'
|
||||||
import { EMPTY_OBJ, isFunction, capitalize, NOOP, isArray } from '@vue/shared'
|
import {
|
||||||
|
EMPTY_OBJ,
|
||||||
|
isFunction,
|
||||||
|
capitalize,
|
||||||
|
NOOP,
|
||||||
|
isArray,
|
||||||
|
isObject
|
||||||
|
} from '@vue/shared'
|
||||||
import { RenderProxyHandlers } from './componentProxy'
|
import { RenderProxyHandlers } from './componentProxy'
|
||||||
import { ComponentPropsOptions, ExtractPropTypes } from './componentProps'
|
import { ComponentPropsOptions, ExtractPropTypes } from './componentProps'
|
||||||
import { Slots } from './componentSlots'
|
import { Slots } from './componentSlots'
|
||||||
@ -15,6 +22,7 @@ import {
|
|||||||
} from './errorHandling'
|
} from './errorHandling'
|
||||||
import { AppContext, createAppContext, resolveAsset } from './apiApp'
|
import { AppContext, createAppContext, resolveAsset } from './apiApp'
|
||||||
import { Directive } from './directives'
|
import { Directive } from './directives'
|
||||||
|
import { processOptions, LegacyOptions } from './apiOptions'
|
||||||
|
|
||||||
export type Data = { [key: string]: unknown }
|
export type Data = { [key: string]: unknown }
|
||||||
|
|
||||||
@ -38,15 +46,16 @@ type RenderFunction<Props = {}, RawBindings = {}> = <
|
|||||||
this: ComponentRenderProxy<Props, Bindings>
|
this: ComponentRenderProxy<Props, Bindings>
|
||||||
) => VNodeChild
|
) => VNodeChild
|
||||||
|
|
||||||
interface ComponentOptionsBase<Props, RawBindings> {
|
interface ComponentOptionsBase<Props, RawBindings> extends LegacyOptions {
|
||||||
setup?: (
|
setup?: (
|
||||||
props: Props,
|
props: Props,
|
||||||
ctx: SetupContext
|
ctx: SetupContext
|
||||||
) => RawBindings | (() => VNodeChild) | void
|
) => RawBindings | (() => VNodeChild) | void
|
||||||
|
name?: string
|
||||||
|
template?: string
|
||||||
render?: RenderFunction<Props, RawBindings>
|
render?: RenderFunction<Props, RawBindings>
|
||||||
components?: Record<string, Component>
|
components?: Record<string, Component>
|
||||||
directives?: Record<string, Directive>
|
directives?: Record<string, Directive>
|
||||||
// TODO full 2.x options compat
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ComponentOptionsWithoutProps<Props = {}, RawBindings = {}>
|
interface ComponentOptionsWithoutProps<Props = {}, RawBindings = {}>
|
||||||
@ -279,6 +288,7 @@ export const setCurrentInstance = (instance: ComponentInstance | null) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function setupStatefulComponent(instance: ComponentInstance) {
|
export function setupStatefulComponent(instance: ComponentInstance) {
|
||||||
|
currentInstance = instance
|
||||||
const Component = instance.type as ComponentOptions
|
const Component = instance.type as ComponentOptions
|
||||||
// 1. create render proxy
|
// 1. create render proxy
|
||||||
instance.renderProxy = new Proxy(instance, RenderProxyHandlers) as any
|
instance.renderProxy = new Proxy(instance, RenderProxyHandlers) as any
|
||||||
@ -291,15 +301,12 @@ export function setupStatefulComponent(instance: ComponentInstance) {
|
|||||||
if (setup) {
|
if (setup) {
|
||||||
const setupContext = (instance.setupContext =
|
const setupContext = (instance.setupContext =
|
||||||
setup.length > 1 ? createSetupContext(instance) : null)
|
setup.length > 1 ? createSetupContext(instance) : null)
|
||||||
|
|
||||||
currentInstance = instance
|
|
||||||
const setupResult = callWithErrorHandling(
|
const setupResult = callWithErrorHandling(
|
||||||
setup,
|
setup,
|
||||||
instance,
|
instance,
|
||||||
ErrorTypes.SETUP_FUNCTION,
|
ErrorTypes.SETUP_FUNCTION,
|
||||||
[propsProxy, setupContext]
|
[propsProxy, setupContext]
|
||||||
)
|
)
|
||||||
currentInstance = null
|
|
||||||
|
|
||||||
if (isFunction(setupResult)) {
|
if (isFunction(setupResult)) {
|
||||||
// setup returned an inline render function
|
// setup returned an inline render function
|
||||||
@ -322,15 +329,32 @@ export function setupStatefulComponent(instance: ComponentInstance) {
|
|||||||
}
|
}
|
||||||
// setup returned bindings.
|
// setup returned bindings.
|
||||||
// assuming a render function compiled from template is present.
|
// assuming a render function compiled from template is present.
|
||||||
instance.data = reactive(setupResult || {})
|
if (isObject(setupResult)) {
|
||||||
|
instance.data = setupResult
|
||||||
|
} else if (__DEV__ && setupResult !== undefined) {
|
||||||
|
warn(
|
||||||
|
`setup() should return an object. Received: ${
|
||||||
|
setupResult === null ? 'null' : typeof setupResult
|
||||||
|
}`
|
||||||
|
)
|
||||||
|
}
|
||||||
instance.render = (Component.render || NOOP) as RenderFunction
|
instance.render = (Component.render || NOOP) as RenderFunction
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (__DEV__ && !Component.render) {
|
if (__DEV__ && !Component.render) {
|
||||||
// TODO warn missing render fn
|
warn(
|
||||||
|
`Component is missing render function. Either provide a template or ` +
|
||||||
|
`return a render function from setup().`
|
||||||
|
)
|
||||||
}
|
}
|
||||||
instance.render = Component.render as RenderFunction
|
instance.render = Component.render as RenderFunction
|
||||||
}
|
}
|
||||||
|
// support for 2.x options
|
||||||
|
if (__FEATURE_OPTIONS__) {
|
||||||
|
processOptions(instance)
|
||||||
|
}
|
||||||
|
instance.data = reactive(instance.data === EMPTY_OBJ ? {} : instance.data)
|
||||||
|
currentInstance = null
|
||||||
}
|
}
|
||||||
|
|
||||||
// used to identify a setup context proxy
|
// used to identify a setup context proxy
|
||||||
|
Loading…
Reference in New Issue
Block a user