仮に、Vue.extendに渡されたオプションの型を出来上がったコンポーネントから参照可能になるとする。 これはそれを疑似的に可能にするためのヘルパ
/*
* const Component = createComponentExperimental({ ... }); でコンポーネントを定義すれば
* typeof Component["_options"] でオプションの型を取得できる
*/
import Vue, { VueConstructor } from "vue";
import { ThisTypedComponentOptionsWithRecordProps } from "vue/types/options";
export type ExperimentalComponent<Options> = VueConstructor<Vue> & {
_options: Options;
};
export function createComponentExperimental<
Data,
Methods,
Computed,
Props,
Options extends ThisTypedComponentOptionsWithRecordProps<Vue, Data, Methods, Computed, Props>
>(
options: ThisTypedComponentOptionsWithRecordProps<Vue, Data, Methods, Computed, Props> &
Options
): ExperimentalComponent<Options> {
return Vue.extend(options) as any;
}
これはRecordPropsDefinitionのrequired指定をよみとって 「コンポーネントの外部から見たPropsの型」を導出するための型
import { RecordPropsDefinition } from "vue/types/options";
type RequiredPropNames<P> = {
[K in keyof P]: P[K] extends { required: true } ? K : never
}[keyof P] &
keyof P;
type OptionalPropNames<P> = Exclude<keyof P, RequiredPropNames<P>>;
// P はPropDefから導出可能なので型引数にしなくてもいいけど、どうせ呼び出し側で既知なので
// 渡す
export type OuterProps<P, PropDef extends RecordPropsDefinition<P>> = {
[K in RequiredPropNames<PropDef> & keyof P]: P[K]
} &
{ [K in OptionalPropNames<PropDef> & keyof P]?: P[K] };
上記を踏まえたLibraryManagedAttributesの定義。
実際には ["foo", "bar"]
の形式でpropsを指定された場合とか、functional componentの場合とか
いろいろ考えることが他にもあるので、あくまでサンプルとして。
namespace JSX {
// TComponentに対象のコンポーネントの型が渡ってくるので、_options -> props でProps Definitionの型を取得して
// JSX向けのAttributeを構成する
type LibraryManagedAttributes<TComponent, TProps> = KnownAttrs & (
TComponent extends { _options: { props: infer PropDef } }
? (PropDef extends RecordPropsDefinition<infer P>
? OuterProps<P, PropDef>
: never)
: never;
}) /*
& (TComponent extends { _scopedSlots: infer S } ? ... : {})
みたいに、 _scopedSlots というメンバがあればScopedSlots関連のアトリビュートも生やすとか、
_events というメンバがあればイベントハンドラも生やすとかも可能
*/;