refactor: use flat options on class
This commit is contained in:
parent
b527705928
commit
da6f0d7adc
@ -95,10 +95,8 @@ describe('attribute fallthrough', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class Child extends Component<{ foo: number }> {
|
class Child extends Component<{ foo: number }> {
|
||||||
static options = {
|
static props = {
|
||||||
props: {
|
foo: Number
|
||||||
foo: Number
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
updated() {
|
updated() {
|
||||||
childUpdated()
|
childUpdated()
|
||||||
@ -169,10 +167,8 @@ describe('attribute fallthrough', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class Child extends Component {
|
class Child extends Component {
|
||||||
static options = {
|
static props = {
|
||||||
props: {
|
foo: Number
|
||||||
foo: Number
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
updated() {
|
updated() {
|
||||||
childUpdated()
|
childUpdated()
|
||||||
@ -183,10 +179,8 @@ describe('attribute fallthrough', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class GrandChild extends Component<{ foo: number }> {
|
class GrandChild extends Component<{ foo: number }> {
|
||||||
static options = {
|
static props = {
|
||||||
props: {
|
foo: Number
|
||||||
foo: Number
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
updated() {
|
updated() {
|
||||||
grandChildUpdated()
|
grandChildUpdated()
|
||||||
|
@ -3,6 +3,7 @@ import { VNode, Slots, RenderNode, MountedVNode } from './vdom'
|
|||||||
import {
|
import {
|
||||||
Data,
|
Data,
|
||||||
ComponentOptions,
|
ComponentOptions,
|
||||||
|
ComponentClassOptions,
|
||||||
ComponentPropsOptions,
|
ComponentPropsOptions,
|
||||||
WatchOptions
|
WatchOptions
|
||||||
} from './componentOptions'
|
} from './componentOptions'
|
||||||
@ -10,10 +11,10 @@ import { setupWatcher } from './componentWatch'
|
|||||||
import { Autorun, DebuggerEvent, ComputedGetter } from '@vue/observer'
|
import { Autorun, DebuggerEvent, ComputedGetter } from '@vue/observer'
|
||||||
import { nextTick } from '@vue/scheduler'
|
import { nextTick } from '@vue/scheduler'
|
||||||
import { ErrorTypes } from './errorHandling'
|
import { ErrorTypes } from './errorHandling'
|
||||||
|
import { resolveComponentOptions } from './componentUtils'
|
||||||
|
|
||||||
type Flatten<T> = { [K in keyof T]: T[K] }
|
export interface ComponentClass extends ComponentClassOptions {
|
||||||
|
options?: ComponentOptions
|
||||||
export interface ComponentClass extends Flatten<typeof InternalComponent> {
|
|
||||||
new <P extends object = {}, D extends object = {}>(): MergedComponent<P, D>
|
new <P extends object = {}, D extends object = {}>(): MergedComponent<P, D>
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,15 +31,15 @@ export interface FunctionalComponent<P = {}> {
|
|||||||
export type ComponentType = ComponentClass | FunctionalComponent
|
export type ComponentType = ComponentClass | FunctionalComponent
|
||||||
|
|
||||||
export interface ComponentInstance<P = {}, D = {}> extends InternalComponent {
|
export interface ComponentInstance<P = {}, D = {}> extends InternalComponent {
|
||||||
|
constructor: ComponentClass
|
||||||
|
|
||||||
$vnode: MountedVNode
|
$vnode: MountedVNode
|
||||||
$data: D
|
$data: D
|
||||||
$props: Readonly<P>
|
$props: Readonly<P>
|
||||||
$attrs: Data
|
$attrs: Data
|
||||||
$computed: Data
|
|
||||||
$slots: Slots
|
$slots: Slots
|
||||||
$root: ComponentInstance
|
$root: ComponentInstance
|
||||||
$children: ComponentInstance[]
|
$children: ComponentInstance[]
|
||||||
$options: ComponentOptions<this>
|
|
||||||
|
|
||||||
data?(): Partial<D>
|
data?(): Partial<D>
|
||||||
render(props: Readonly<P>, slots: Slots, attrs: Data): any
|
render(props: Readonly<P>, slots: Slots, attrs: Data): any
|
||||||
@ -70,8 +71,6 @@ export interface ComponentInstance<P = {}, D = {}> extends InternalComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class InternalComponent {
|
class InternalComponent {
|
||||||
public static options?: ComponentOptions
|
|
||||||
|
|
||||||
public get $el(): RenderNode | null {
|
public get $el(): RenderNode | null {
|
||||||
return this.$vnode && this.$vnode.el
|
return this.$vnode && this.$vnode.el
|
||||||
}
|
}
|
||||||
@ -81,12 +80,11 @@ class InternalComponent {
|
|||||||
public $data: Data | null = null
|
public $data: Data | null = null
|
||||||
public $props: Data | null = null
|
public $props: Data | null = null
|
||||||
public $attrs: Data | null = null
|
public $attrs: Data | null = null
|
||||||
public $computed: Data | null = null
|
|
||||||
public $slots: Slots | null = null
|
public $slots: Slots | null = null
|
||||||
public $root: ComponentInstance | null = null
|
public $root: ComponentInstance | null = null
|
||||||
public $parent: ComponentInstance | null = null
|
public $parent: ComponentInstance | null = null
|
||||||
public $children: ComponentInstance[] = []
|
public $children: ComponentInstance[] = []
|
||||||
public $options: any
|
public $options: ComponentOptions
|
||||||
public $refs: Record<string, ComponentInstance | RenderNode> = {}
|
public $refs: Record<string, ComponentInstance | RenderNode> = {}
|
||||||
public $proxy: any = null
|
public $proxy: any = null
|
||||||
public $forceUpdate: (() => void) | null = null
|
public $forceUpdate: (() => void) | null = null
|
||||||
@ -103,12 +101,10 @@ class InternalComponent {
|
|||||||
public _isVue: boolean = true
|
public _isVue: boolean = true
|
||||||
public _inactiveRoot: boolean = false
|
public _inactiveRoot: boolean = false
|
||||||
|
|
||||||
constructor(options?: ComponentOptions) {
|
constructor() {
|
||||||
this.$options = options || (this.constructor as any).options || EMPTY_OBJ
|
this.$options =
|
||||||
// root instance
|
(this.constructor as ComponentClass).options ||
|
||||||
if (options !== void 0) {
|
resolveComponentOptions(this.constructor as ComponentClass)
|
||||||
// mount this
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$nextTick(fn: () => any): Promise<any> {
|
$nextTick(fn: () => any): Promise<any> {
|
||||||
|
@ -1,21 +1,12 @@
|
|||||||
import { EMPTY_OBJ, NOOP } from './utils'
|
import { NOOP } from './utils'
|
||||||
import { computed, stop, ComputedGetter } from '@vue/observer'
|
import { computed, stop, ComputedGetter } from '@vue/observer'
|
||||||
import { ComponentClass, ComponentInstance } from './component'
|
import { ComponentClass, ComponentInstance } from './component'
|
||||||
import { ComponentComputedOptions } from './componentOptions'
|
import { ComponentComputedOptions } from './componentOptions'
|
||||||
|
|
||||||
const extractionCache: WeakMap<
|
export function resolveComputedOptions(
|
||||||
ComponentClass,
|
|
||||||
ComponentComputedOptions
|
|
||||||
> = new WeakMap()
|
|
||||||
|
|
||||||
export function getComputedOptions(
|
|
||||||
comp: ComponentClass
|
comp: ComponentClass
|
||||||
): ComponentComputedOptions {
|
): ComponentComputedOptions {
|
||||||
let computedOptions = extractionCache.get(comp)
|
const computedOptions: ComponentComputedOptions = {}
|
||||||
if (computedOptions) {
|
|
||||||
return computedOptions
|
|
||||||
}
|
|
||||||
computedOptions = {}
|
|
||||||
const descriptors = Object.getOwnPropertyDescriptors(comp.prototype as any)
|
const descriptors = Object.getOwnPropertyDescriptors(comp.prototype as any)
|
||||||
for (const key in descriptors) {
|
for (const key in descriptors) {
|
||||||
const d = descriptors[key]
|
const d = descriptors[key]
|
||||||
@ -25,7 +16,6 @@ export function getComputedOptions(
|
|||||||
// as it's already defined on the prototype
|
// as it's already defined on the prototype
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
extractionCache.set(comp, computedOptions)
|
|
||||||
return computedOptions
|
return computedOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,7 +24,6 @@ export function initializeComputed(
|
|||||||
computedOptions: ComponentComputedOptions | undefined
|
computedOptions: ComponentComputedOptions | undefined
|
||||||
) {
|
) {
|
||||||
if (!computedOptions) {
|
if (!computedOptions) {
|
||||||
instance.$computed = EMPTY_OBJ
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const handles: Record<
|
const handles: Record<
|
||||||
@ -47,17 +36,6 @@ export function initializeComputed(
|
|||||||
const getter = typeof option === 'function' ? option : option.get || NOOP
|
const getter = typeof option === 'function' ? option : option.get || NOOP
|
||||||
handles[key] = computed(getter, proxy)
|
handles[key] = computed(getter, proxy)
|
||||||
}
|
}
|
||||||
instance.$computed = new Proxy(
|
|
||||||
{},
|
|
||||||
{
|
|
||||||
get(_, key: any) {
|
|
||||||
if (handles.hasOwnProperty(key)) {
|
|
||||||
return handles[key]()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// TODO should be readonly
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function teardownComputed(instance: ComponentInstance) {
|
export function teardownComputed(instance: ComponentInstance) {
|
||||||
|
@ -3,14 +3,18 @@ import { Slots } from './vdom'
|
|||||||
|
|
||||||
export type Data = Record<string, any>
|
export type Data = Record<string, any>
|
||||||
|
|
||||||
export interface ComponentOptions<This = ComponentInstance> {
|
export interface ComponentClassOptions<This = ComponentInstance> {
|
||||||
data?(): object
|
|
||||||
props?: ComponentPropsOptions
|
props?: ComponentPropsOptions
|
||||||
computed?: ComponentComputedOptions<This>
|
computed?: ComponentComputedOptions<This>
|
||||||
watch?: ComponentWatchOptions<This>
|
watch?: ComponentWatchOptions<This>
|
||||||
render?: (this: This, props: Readonly<Data>, slots: Slots, attrs: Data) => any
|
|
||||||
inheritAttrs?: boolean
|
|
||||||
displayName?: string
|
displayName?: string
|
||||||
|
inheritAttrs?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ComponentOptions<This = ComponentInstance>
|
||||||
|
extends ComponentClassOptions<This> {
|
||||||
|
data?(): object
|
||||||
|
render?: (this: This, props: Readonly<Data>, slots: Slots, attrs: Data) => any
|
||||||
// TODO other options
|
// TODO other options
|
||||||
readonly [key: string]: any
|
readonly [key: string]: any
|
||||||
}
|
}
|
||||||
|
@ -18,9 +18,10 @@ import {
|
|||||||
|
|
||||||
export function initializeProps(
|
export function initializeProps(
|
||||||
instance: ComponentInstance,
|
instance: ComponentInstance,
|
||||||
|
options: ComponentPropsOptions | undefined,
|
||||||
data: Data | null
|
data: Data | null
|
||||||
) {
|
) {
|
||||||
const { props, attrs } = resolveProps(data, instance.$options.props)
|
const { props, attrs } = resolveProps(data, options)
|
||||||
instance.$props = immutable(props || {})
|
instance.$props = immutable(props || {})
|
||||||
instance.$attrs = immutable(attrs || {})
|
instance.$attrs = immutable(attrs || {})
|
||||||
}
|
}
|
||||||
@ -32,7 +33,7 @@ export function updateProps(instance: ComponentInstance, nextData: Data) {
|
|||||||
if (nextData != null) {
|
if (nextData != null) {
|
||||||
const { props: nextProps, attrs: nextAttrs } = resolveProps(
|
const { props: nextProps, attrs: nextAttrs } = resolveProps(
|
||||||
nextData,
|
nextData,
|
||||||
instance.$options.props
|
instance.constructor.props
|
||||||
)
|
)
|
||||||
// unlock to temporarily allow mutatiing props
|
// unlock to temporarily allow mutatiing props
|
||||||
unlock()
|
unlock()
|
||||||
|
@ -25,8 +25,8 @@ const renderProxyHandlers = {
|
|||||||
// data
|
// data
|
||||||
return target.$data[key]
|
return target.$data[key]
|
||||||
} else if (
|
} else if (
|
||||||
target.$options.props != null &&
|
target.constructor.props != null &&
|
||||||
target.$options.props.hasOwnProperty(key)
|
target.constructor.props.hasOwnProperty(key)
|
||||||
) {
|
) {
|
||||||
// props are only proxied if declared
|
// props are only proxied if declared
|
||||||
return target.$props[key]
|
return target.$props[key]
|
||||||
@ -61,8 +61,8 @@ const renderProxyHandlers = {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
target.$options.props != null &&
|
target.constructor.props != null &&
|
||||||
target.$options.props.hasOwnProperty(key)
|
target.constructor.props.hasOwnProperty(key)
|
||||||
) {
|
) {
|
||||||
// TODO warn props are immutable
|
// TODO warn props are immutable
|
||||||
return false
|
return false
|
||||||
|
@ -8,7 +8,7 @@ import { initializeState } from './componentState'
|
|||||||
import { initializeProps } from './componentProps'
|
import { initializeProps } from './componentProps'
|
||||||
import {
|
import {
|
||||||
initializeComputed,
|
initializeComputed,
|
||||||
getComputedOptions,
|
resolveComputedOptions,
|
||||||
teardownComputed
|
teardownComputed
|
||||||
} from './componentComputed'
|
} from './componentComputed'
|
||||||
import { initializeWatch, teardownWatch } from './componentWatch'
|
import { initializeWatch, teardownWatch } from './componentWatch'
|
||||||
@ -40,11 +40,10 @@ export function createComponentInstance(
|
|||||||
if (instance.beforeCreate) {
|
if (instance.beforeCreate) {
|
||||||
instance.beforeCreate.call(proxy)
|
instance.beforeCreate.call(proxy)
|
||||||
}
|
}
|
||||||
// TODO provide/inject
|
initializeProps(instance, Component.props, vnode.data)
|
||||||
initializeProps(instance, vnode.data)
|
|
||||||
initializeState(instance)
|
initializeState(instance)
|
||||||
initializeComputed(instance, getComputedOptions(Component))
|
initializeComputed(instance, Component.computed)
|
||||||
initializeWatch(instance, instance.$options.watch)
|
initializeWatch(instance, Component.watch)
|
||||||
instance.$slots = vnode.slots || EMPTY_OBJ
|
instance.$slots = vnode.slots || EMPTY_OBJ
|
||||||
if (instance.created) {
|
if (instance.created) {
|
||||||
instance.created.call(proxy)
|
instance.created.call(proxy)
|
||||||
@ -75,7 +74,7 @@ export function renderInstanceRoot(instance: ComponentInstance): VNode {
|
|||||||
vnode,
|
vnode,
|
||||||
instance.$parentVNode,
|
instance.$parentVNode,
|
||||||
instance.$attrs,
|
instance.$attrs,
|
||||||
instance.$options.inheritAttrs
|
instance.constructor.inheritAttrs
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,36 +170,40 @@ export function createComponentClassFromOptions(
|
|||||||
options: ComponentOptions
|
options: ComponentOptions
|
||||||
): ComponentClass {
|
): ComponentClass {
|
||||||
class AnonymousComponent extends Component {
|
class AnonymousComponent extends Component {
|
||||||
constructor() {
|
static options = options
|
||||||
super()
|
|
||||||
this.$options = options
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
const proto = AnonymousComponent.prototype as any
|
const proto = AnonymousComponent.prototype as any
|
||||||
for (const key in options) {
|
for (const key in options) {
|
||||||
const value = options[key]
|
const value = options[key]
|
||||||
// name -> displayName
|
// name -> displayName
|
||||||
if (__COMPAT__ && key === 'name') {
|
if (key === 'name') {
|
||||||
options.displayName = options.name
|
AnonymousComponent.displayName = options.name
|
||||||
}
|
} else if (typeof value === 'function') {
|
||||||
if (typeof value === 'function') {
|
if (__COMPAT__) {
|
||||||
if (__COMPAT__ && key === 'render') {
|
if (key === 'render') {
|
||||||
proto[key] = function() {
|
proto[key] = function() {
|
||||||
return value.call(this, h)
|
return value.call(this, h)
|
||||||
|
}
|
||||||
|
} else if (key === 'beforeDestroy') {
|
||||||
|
proto.beforeUnmount = value
|
||||||
|
} else if (key === 'destroyed') {
|
||||||
|
proto.unmounted = value
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
proto[key] = value
|
proto[key] = value
|
||||||
}
|
}
|
||||||
}
|
} else if (key === 'computed') {
|
||||||
if (key === 'computed') {
|
AnonymousComponent.computed = value
|
||||||
const isGet = typeof value === 'function'
|
for (const computedKey in value) {
|
||||||
Object.defineProperty(proto, key, {
|
const computed = value[computedKey]
|
||||||
configurable: true,
|
const isGet = typeof computed === 'function'
|
||||||
get: isGet ? value : value.get,
|
Object.defineProperty(proto, computedKey, {
|
||||||
set: isGet ? undefined : value.set
|
configurable: true,
|
||||||
})
|
get: isGet ? computed : computed.get,
|
||||||
}
|
set: isGet ? undefined : computed.set
|
||||||
if (key === 'methods') {
|
})
|
||||||
|
}
|
||||||
|
} else if (key === 'methods') {
|
||||||
for (const method in value) {
|
for (const method in value) {
|
||||||
if (__DEV__ && proto.hasOwnProperty(method)) {
|
if (__DEV__ && proto.hasOwnProperty(method)) {
|
||||||
console.warn(
|
console.warn(
|
||||||
@ -210,7 +213,23 @@ export function createComponentClassFromOptions(
|
|||||||
}
|
}
|
||||||
proto[method] = value[method]
|
proto[method] = value[method]
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
;(AnonymousComponent as any)[key] = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return AnonymousComponent as ComponentClass
|
return AnonymousComponent as ComponentClass
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function resolveComponentOptions(
|
||||||
|
Component: ComponentClass
|
||||||
|
): ComponentOptions {
|
||||||
|
const keys = Object.keys(Component)
|
||||||
|
const options = {} as any
|
||||||
|
for (let i = 0; i < keys.length; i++) {
|
||||||
|
const key = keys[i]
|
||||||
|
options[key] = (Component as any)[key]
|
||||||
|
}
|
||||||
|
Component.computed = options.computed = resolveComputedOptions(Component)
|
||||||
|
Component.options = options
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
@ -9,6 +9,16 @@ interface ProviderProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class Provide extends Component<ProviderProps> {
|
export class Provide extends Component<ProviderProps> {
|
||||||
|
static props = {
|
||||||
|
id: {
|
||||||
|
type: [String, Symbol],
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
updateValue() {
|
updateValue() {
|
||||||
// TS doesn't allow symbol as index :/
|
// TS doesn't allow symbol as index :/
|
||||||
// https://github.com/Microsoft/TypeScript/issues/24587
|
// https://github.com/Microsoft/TypeScript/issues/24587
|
||||||
@ -43,18 +53,6 @@ export class Provide extends Component<ProviderProps> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Provide.options = {
|
|
||||||
props: {
|
|
||||||
id: {
|
|
||||||
type: [String, Symbol],
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
value: {
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Inject extends Component {
|
export class Inject extends Component {
|
||||||
render(props: any, slots: any) {
|
render(props: any, slots: any) {
|
||||||
return slots.default && slots.default(contextStore[props.id])
|
return slots.default && slots.default(contextStore[props.id])
|
||||||
|
@ -114,7 +114,7 @@ export class KeepAlive extends Component<KeepAliveProps> {
|
|||||||
;(KeepAlive as any)[KeepAliveSymbol] = true
|
;(KeepAlive as any)[KeepAliveSymbol] = true
|
||||||
|
|
||||||
function getName(comp: ComponentClass): string | void {
|
function getName(comp: ComponentClass): string | void {
|
||||||
return comp.options && comp.options.name
|
return comp.displayName || comp.name
|
||||||
}
|
}
|
||||||
|
|
||||||
function matches(pattern: MatchPattern, name: string): boolean {
|
function matches(pattern: MatchPattern, name: string): boolean {
|
||||||
|
Loading…
Reference in New Issue
Block a user