feat: 2.x options support
This commit is contained in:
parent
c833db9c97
commit
a6616e4210
@ -2,8 +2,9 @@ module.exports = {
|
||||
preset: 'ts-jest',
|
||||
globals: {
|
||||
__DEV__: true,
|
||||
__COMPAT__: false,
|
||||
__JSDOM__: true
|
||||
__JSDOM__: true,
|
||||
__FEATURE_OPTIONS__: true,
|
||||
__FEATURE_PRODUCTION_TIP__: false
|
||||
},
|
||||
coverageDirectory: 'coverage',
|
||||
coverageReporters: ['html', 'lcov', 'text'],
|
||||
|
@ -7,7 +7,7 @@ export interface ComputedRef<T> {
|
||||
readonly effect: ReactiveEffect
|
||||
}
|
||||
|
||||
export interface ComputedOptions<T> {
|
||||
export interface ComputedOptions<T = any> {
|
||||
get: () => T
|
||||
set: (v: T) => void
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ import { capitalize } from '@vue/shared'
|
||||
function injectHook(
|
||||
type: LifecycleHooks,
|
||||
hook: Function,
|
||||
target: ComponentInstance | null = currentInstance
|
||||
target: ComponentInstance | null
|
||||
) {
|
||||
if (target) {
|
||||
;(target[type] || (target[type] = [])).push((...args: any[]) => {
|
||||
@ -26,7 +26,7 @@ function injectHook(
|
||||
})
|
||||
} else if (__DEV__) {
|
||||
const apiName = `on${capitalize(
|
||||
ErrorTypeStrings[name].replace(/ hook$/, '')
|
||||
ErrorTypeStrings[type].replace(/ hook$/, '')
|
||||
)}`
|
||||
warn(
|
||||
`${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,
|
||||
Ref,
|
||||
ComputedRef,
|
||||
UnwrapRef
|
||||
UnwrapRef,
|
||||
ComputedOptions
|
||||
} from '@vue/reactivity'
|
||||
|
||||
import {
|
||||
|
@ -1,6 +1,13 @@
|
||||
import { VNode, normalizeVNode, VNodeChild, createVNode, Empty } from './vnode'
|
||||
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 { ComponentPropsOptions, ExtractPropTypes } from './componentProps'
|
||||
import { Slots } from './componentSlots'
|
||||
@ -15,6 +22,7 @@ import {
|
||||
} from './errorHandling'
|
||||
import { AppContext, createAppContext, resolveAsset } from './apiApp'
|
||||
import { Directive } from './directives'
|
||||
import { processOptions, LegacyOptions } from './apiOptions'
|
||||
|
||||
export type Data = { [key: string]: unknown }
|
||||
|
||||
@ -38,15 +46,16 @@ type RenderFunction<Props = {}, RawBindings = {}> = <
|
||||
this: ComponentRenderProxy<Props, Bindings>
|
||||
) => VNodeChild
|
||||
|
||||
interface ComponentOptionsBase<Props, RawBindings> {
|
||||
interface ComponentOptionsBase<Props, RawBindings> extends LegacyOptions {
|
||||
setup?: (
|
||||
props: Props,
|
||||
ctx: SetupContext
|
||||
) => RawBindings | (() => VNodeChild) | void
|
||||
name?: string
|
||||
template?: string
|
||||
render?: RenderFunction<Props, RawBindings>
|
||||
components?: Record<string, Component>
|
||||
directives?: Record<string, Directive>
|
||||
// TODO full 2.x options compat
|
||||
}
|
||||
|
||||
interface ComponentOptionsWithoutProps<Props = {}, RawBindings = {}>
|
||||
@ -279,6 +288,7 @@ export const setCurrentInstance = (instance: ComponentInstance | null) => {
|
||||
}
|
||||
|
||||
export function setupStatefulComponent(instance: ComponentInstance) {
|
||||
currentInstance = instance
|
||||
const Component = instance.type as ComponentOptions
|
||||
// 1. create render proxy
|
||||
instance.renderProxy = new Proxy(instance, RenderProxyHandlers) as any
|
||||
@ -291,15 +301,12 @@ export function setupStatefulComponent(instance: ComponentInstance) {
|
||||
if (setup) {
|
||||
const setupContext = (instance.setupContext =
|
||||
setup.length > 1 ? createSetupContext(instance) : null)
|
||||
|
||||
currentInstance = instance
|
||||
const setupResult = callWithErrorHandling(
|
||||
setup,
|
||||
instance,
|
||||
ErrorTypes.SETUP_FUNCTION,
|
||||
[propsProxy, setupContext]
|
||||
)
|
||||
currentInstance = null
|
||||
|
||||
if (isFunction(setupResult)) {
|
||||
// setup returned an inline render function
|
||||
@ -322,15 +329,32 @@ export function setupStatefulComponent(instance: ComponentInstance) {
|
||||
}
|
||||
// setup returned bindings.
|
||||
// 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
|
||||
}
|
||||
} else {
|
||||
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
|
||||
}
|
||||
// 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
|
||||
|
Loading…
Reference in New Issue
Block a user