vue3-core/packages/runtime-core/src/optional/mixins.ts

85 lines
2.3 KiB
TypeScript

import { Component } from '../component'
import { createComponentClassFromOptions } from '../componentUtils'
import {
ComponentOptions,
resolveComponentOptionsFromClass,
mergeComponentOptions,
mergeDataFn
} from '../componentOptions'
import { normalizePropsOptions } from '../componentProps'
import { extractInitializers } from '../componentState'
import { isFunction } from '@vue/shared'
interface ComponentConstructor<This = Component> {
new (): This
}
interface ComponentConstructorWithMixins<This> {
new <P = {}, D = {}>(): This & { $data: D } & D & { $props: Readonly<P> } & P
}
// mind = blown
// https://stackoverflow.com/questions/50374908/transform-union-type-to-intersection-type
type UnionToIntersection<U> = (U extends any
? (k: U) => void
: never) extends ((k: infer I) => void)
? I
: never
type ExtractInstance<T> = T extends (infer U)[]
? UnionToIntersection<U extends ComponentConstructor<infer V> ? V : never>
: never
export function mixins<
T extends ComponentConstructor[] = [],
V = ExtractInstance<T>
>(...args: T): ComponentConstructorWithMixins<V>
export function mixins(...args: any[]): any {
let options: ComponentOptions = {}
args.forEach(mixin => {
if (isFunction(mixin)) {
const Class = mixin
mixin = resolveComponentOptionsFromClass(Class)
// in order to extract properties initialized in the mixin's constructor,
// we create an instance of it and pass in the actual props - this
// short-circuits the normal component initialization and allows us to
// relatively-cheaply extract the properties added in the constructor.
function extractData() {
return extractInitializers(new Class(this.$props))
}
const { data } = mixin
mixin.data = data ? mergeDataFn(data, extractData) : extractData
} else {
mixin.props = normalizePropsOptions(mixin.props)
}
options = mergeComponentOptions(options, mixin)
})
return createComponentClassFromOptions(options)
}
/* Example usage
class Foo extends Component<{ foo: number }> {
test() {
}
}
class Bar extends Component<{ bar: string }> {
ok() {
}
}
class Baz extends mixins(Foo, Bar)<{ baz: number }> {
created() {
this.foo
this.bar
this.baz
this.test()
this.ok()
}
}
*/