Skip to content

Commit

Permalink
imporve: imporve type infer of "createComponent"
Browse files Browse the repository at this point in the history
Resolves: #15
  • Loading branch information
liximomo committed Aug 15, 2019
1 parent 096d531 commit eb94237
Show file tree
Hide file tree
Showing 13 changed files with 256 additions and 97 deletions.
17 changes: 10 additions & 7 deletions src/functions/computed.ts
@@ -1,19 +1,22 @@
import { compoundComputed } from '../helper';
import { getCurrentVue } from '../runtimeContext';
import { createComponentInstance } from '../helper';
import { Wrapper, ComputedWrapper } from '../wrappers';

export function computed<T>(getter: () => T, setter?: (x: T) => void): Wrapper<T> {
const computedHost = compoundComputed({
$$state: {
get: getter,
set: setter,
const computedHost = createComponentInstance(getCurrentVue(), {
computed: {
$$state: {
get: getter,
set: setter,
},
},
});

return new ComputedWrapper({
read: () => computedHost.$$state,
read: () => (computedHost as any).$$state,
...(setter && {
write: (v: T) => {
computedHost.$$state = v;
(computedHost as any).$$state = v;
},
}),
});
Expand Down
4 changes: 2 additions & 2 deletions src/functions/lifecycle.ts
@@ -1,5 +1,5 @@
import { VueConstructor } from 'vue';
import { VueInstance } from '../types/vue';
import { ComponentInstance } from '../ts-api';
import { getCurrentVue } from '../runtimeContext';
import { ensureCurrentVMInFn } from '../helper';

Expand All @@ -21,7 +21,7 @@ function createLifeCycles(lifeCyclehooks: string[], name: string) {
};
}

function injectHookOption(Vue: VueConstructor, vm: VueInstance, hook: string, val: Function) {
function injectHookOption(Vue: VueConstructor, vm: ComponentInstance, hook: string, val: Function) {
const options = vm.$options as any;
const mergeFn = Vue.config.optionMergeStrategies[hook];
options[hook] = mergeFn(options[hook], val);
Expand Down
15 changes: 6 additions & 9 deletions src/functions/watch.ts
@@ -1,6 +1,7 @@
import { VueInstance } from '../types/vue';
import { ComponentInstance } from '../ts-api';
import { Wrapper } from '../wrappers';
import { isArray, assert } from '../utils';
import { createComponentInstance } from '../helper';
import { isWrapper } from '../wrappers';
import { getCurrentVM, getCurrentVue } from '../runtimeContext';
import { WatcherPreFlushQueueKey, WatcherPostFlushQueueKey } from '../symbols';
Expand All @@ -22,7 +23,7 @@ interface WatcherContext<T> {
watcherStopHandle: Function;
}

let fallbackVM: VueInstance;
let fallbackVM: ComponentInstance;

function hasWatchEnv(vm: any) {
return vm[WatcherPreFlushQueueKey] !== undefined;
Expand Down Expand Up @@ -81,7 +82,7 @@ function flushWatcherCallback(vm: any, fn: Function, mode: FlushMode) {
}

function createSingleSourceWatcher<T>(
vm: VueInstance,
vm: ComponentInstance,
source: watchedValue<T>,
cb: watcherCallBack<T>,
options: WatcherOption
Expand Down Expand Up @@ -128,7 +129,7 @@ function createSingleSourceWatcher<T>(
}

function createMuiltSourceWatcher<T>(
vm: VueInstance,
vm: ComponentInstance,
sources: Array<watchedValue<T>>,
cb: watcherCallBack<T[]>,
options: WatcherOption
Expand Down Expand Up @@ -245,11 +246,7 @@ export function watch<T = any>(
let vm = getCurrentVM();
if (!vm) {
if (!fallbackVM) {
const Vue = getCurrentVue();
const silent = Vue.config.silent;
Vue.config.silent = true;
fallbackVM = new Vue();
Vue.config.silent = silent;
fallbackVM = createComponentInstance(getCurrentVue());
}
vm = fallbackVM;
opts.flush = 'sync';
Expand Down
34 changes: 14 additions & 20 deletions src/helper.ts
@@ -1,33 +1,27 @@
import { VueInstance } from './types/vue';
import { currentVue, getCurrentVue, getCurrentVM } from './runtimeContext';
import Vue, { ComponentOptions, VueConstructor } from 'vue';
import { ComponentInstance } from './ts-api';
import { currentVue, getCurrentVM } from './runtimeContext';
import { assert } from './utils';

export function ensureCurrentVMInFn(hook: string): VueInstance {
export function ensureCurrentVMInFn(hook: string): ComponentInstance {
const vm = getCurrentVM();
if (process.env.NODE_ENV !== 'production') {
assert(vm, `"${hook}" get called outside of "setup()"`);
}
return vm!;
}

export function compoundComputed(computed: {
[key: string]:
| (() => any)
| {
get?: () => any;
set?: (v: any) => void;
};
}) {
const Vue = getCurrentVue();
const silent = Vue.config.silent;
Vue.config.silent = true;
const reactive = new Vue({
computed,
});
Vue.config.silent = silent;
return reactive;
export function createComponentInstance<V extends Vue = Vue>(
Ctor: VueConstructor<V>,
options: ComponentOptions<V> = {}
) {
const silent = Ctor.config.silent;
Ctor.config.silent = true;
const vm = new Ctor(options);
Ctor.config.silent = silent;
return vm;
}

export function isVueInstance(obj: any) {
export function isComponentInstance(obj: any) {
return currentVue && obj instanceof currentVue;
}
10 changes: 3 additions & 7 deletions src/index.ts
@@ -1,17 +1,13 @@
import Vue, { VueConstructor } from 'vue';
import { SetupContext } from './types/vue';
import { SetupFunction } from './ts-api';
import { currentVue } from './runtimeContext';
import { Wrapper } from './wrappers';
import { install } from './install';
import { mixin } from './setup';

declare module 'vue/types/options' {
interface ComponentOptions<V extends Vue> {
setup?: (
this: void,
props: { [x: string]: any },
context: SetupContext
) => object | null | undefined | void;
setup?: SetupFunction<{}, {}>;
}
}

Expand All @@ -27,7 +23,7 @@ if (currentVue && typeof window !== 'undefined' && window.Vue) {

export { plugin, Wrapper };
export { set } from './reactivity';
export * from './ts-api';
export { createComponent, PropType } from './ts-api';
export * from './functions/state';
export * from './functions/lifecycle';
export * from './functions/watch';
Expand Down
14 changes: 8 additions & 6 deletions src/reactivity/observable.ts
@@ -1,7 +1,7 @@
import { AnyObject } from '../types/basic';
import { getCurrentVue } from '../runtimeContext';
import { isObject, def, hasOwn } from '../utils';
import { isVueInstance } from '../helper';
import { isComponentInstance, createComponentInstance } from '../helper';
import { isWrapper } from '../wrappers';
import { AccessControIdentifierlKey, ObservableIdentifierKey } from '../symbols';

Expand All @@ -12,7 +12,12 @@ const ObservableIdentifier = {};
* We can do unwrapping and other things here.
*/
function setupAccessControl(target: AnyObject) {
if (!isObject(target) || Array.isArray(target) || isWrapper(target) || isVueInstance(target)) {
if (
!isObject(target) ||
Array.isArray(target) ||
isWrapper(target) ||
isComponentInstance(target)
) {
return;
}

Expand Down Expand Up @@ -103,14 +108,11 @@ export function observable<T = any>(obj: T): T {
if (Vue.observable) {
observed = Vue.observable(obj);
} else {
const silent = Vue.config.silent;
Vue.config.silent = true;
const vm = new Vue({
const vm = createComponentInstance(Vue, {
data: {
$$state: obj,
},
});
Vue.config.silent = silent;
observed = vm._data.$$state;
}

Expand Down
13 changes: 7 additions & 6 deletions src/runtimeContext.ts
@@ -1,8 +1,9 @@
import Vue, { VueConstructor } from 'vue';
import { VueConstructor } from 'vue';
import { ComponentInstance } from './ts-api';
import { assert } from './utils';

let currentVue: VueConstructor | null = null;
let currentVM: Vue | null = null;
let currentVM: ComponentInstance | null = null;

export function getCurrentVue(): VueConstructor {
if (process.env.NODE_ENV !== 'production') {
Expand All @@ -16,12 +17,12 @@ export function setCurrentVue(vue: VueConstructor) {
currentVue = vue;
}

export function getCurrentVM(): Vue | null {
export function getCurrentVM(): ComponentInstance | null {
return currentVM;
}

export function setCurrentVM(vue: Vue | null) {
currentVM = vue;
export function setCurrentVM(vm: ComponentInstance | null) {
currentVM = vm;
}

export { currentVue };
export { currentVue, currentVM };
10 changes: 5 additions & 5 deletions src/setup.ts
@@ -1,5 +1,5 @@
import VueInstance, { VueConstructor } from 'vue';
import { SetupContext } from './types/vue';
import { VueConstructor } from 'vue';
import { ComponentInstance, SetupContext } from './ts-api';
import { isWrapper } from './wrappers';
import { getCurrentVM, setCurrentVM } from './runtimeContext';
import { isPlainObject, assert, proxy, warn, logError, isFunction } from './utils';
Expand All @@ -13,7 +13,7 @@ export function mixin(Vue: VueConstructor) {
/**
* Vuex init hook, injected into each instances init hooks list.
*/
function functionApiInit(this: VueInstance) {
function functionApiInit(this: ComponentInstance) {
const vm = this;
const $options = vm.$options;
const { setup } = $options;
Expand All @@ -39,7 +39,7 @@ export function mixin(Vue: VueConstructor) {
};
}

function initSetup(vm: VueInstance, props: Record<any, any> = {}) {
function initSetup(vm: ComponentInstance, props: Record<any, any> = {}) {
const setup = vm.$options.setup!;
const ctx = createSetupContext(vm);
let binding: any;
Expand Down Expand Up @@ -83,7 +83,7 @@ export function mixin(Vue: VueConstructor) {
}
}

function createSetupContext(vm: VueInstance & { [x: string]: any }): SetupContext {
function createSetupContext(vm: ComponentInstance & { [x: string]: any }): SetupContext {
const ctx = {} as SetupContext;
const props: Array<string | [string, string]> = [
'root',
Expand Down
78 changes: 78 additions & 0 deletions src/ts-api/component.ts
@@ -0,0 +1,78 @@
// import Vue, { VueConstructor, VNode, ComponentOptions as Vue2ComponentOptions } from 'vue';
import { VueConstructor, VNode, ComponentOptions as Vue2ComponentOptions } from 'vue';
import { ComponentPropsOptions, ExtractPropTypes } from './componentProps';
import { UnwrapValue } from '../wrappers';

export type Data = { [key: string]: unknown };

export type ComponentInstance = InstanceType<VueConstructor>;

// public properties exposed on the proxy, which is used as the render context
// in templates (as `this` in the render option)
type ComponentRenderProxy<P = {}, S = {}, PublicProps = P> = {
$data: S;
$props: PublicProps;
$attrs: Data;
$refs: Data;
$slots: Data;
$root: ComponentInstance | null;
$parent: ComponentInstance | null;
$emit: (event: string, ...args: unknown[]) => void;
} & P &
S;

// for Vetur and TSX support
type VueConstructorProxy<PropsOptions, RawBindings> = {
new (): ComponentRenderProxy<
ExtractPropTypes<PropsOptions>,
UnwrapValue<RawBindings>,
ExtractPropTypes<PropsOptions, false>
>;
};

type VueProxy<PropsOptions, RawBindings> = Vue2ComponentOptions<
never,
UnwrapValue<RawBindings>,
never,
never,
PropsOptions,
ExtractPropTypes<PropsOptions, false>
> &
VueConstructorProxy<PropsOptions, RawBindings>;

export interface SetupContext {
readonly parent: ComponentInstance;
readonly root: ComponentInstance;
readonly refs: { [key: string]: ComponentInstance | Element | ComponentInstance[] | Element[] };
readonly slots: { [key: string]: VNode[] | undefined };
readonly attrs: Record<string, string>;

emit(event: string, ...args: any[]): void;
}

type RenderFunction<Props> = (props: Props, ctx: SetupContext) => VNode;

export type SetupFunction<Props, RawBindings> = (
this: void,
props: Props,
ctx: SetupContext
) => RawBindings | RenderFunction<Props>;

export interface ComponentOptions<
PropsOptions = ComponentPropsOptions,
RawBindings = Data,
Props = ExtractPropTypes<PropsOptions>
> {
props?: PropsOptions;
setup?: SetupFunction<Props, RawBindings>;
}

// object format with object props declaration
// see `ExtractPropTypes` in ./componentProps.ts
export function createComponent<PropsOptions, RawBindings>(
options: ComponentOptions<PropsOptions, RawBindings>
): VueProxy<PropsOptions, RawBindings>;
// implementation, close to no-op
export function createComponent(options: any) {
return options as any;
}

0 comments on commit eb94237

Please sign in to comment.