Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Access Data in render #191

Closed
NikitaIT opened this issue Nov 15, 2019 · 15 comments · Fixed by #406
Closed

Access Data in render #191

NikitaIT opened this issue Nov 15, 2019 · 15 comments · Fixed by #406

Comments

@NikitaIT
Copy link

export function createComponent<
Props,
RawBindings = Data,
PropsOptions extends ComponentPropsOptions = ComponentPropsOptions
>(
// prettier-ignore
options: (
// prefer the provided Props, otherwise infer it from PropsOptions
HasDefined<Props> extends true
? ComponentOptionsWithProps<PropsOptions, RawBindings, Props>
: ComponentOptionsWithProps<PropsOptions, RawBindings>) &
Omit<Vue2ComponentOptions<Vue>, keyof ComponentOptionsWithProps<never, never>>
): VueProxy<PropsOptions, RawBindings>;

This is needed to get the return from setup to render as this.data // RawBindings.

Omit<Vue2ComponentOptions<Vue, RawBindings>

If now there is a way to do it right, I would like to know it.

@LinusBorg
Copy link
Member

I'm not sure what you are asking. Could you provide a complete exampe of your code that seems to not work as expected?

@NikitaIT
Copy link
Author

NikitaIT commented Nov 15, 2019

@LinusBorg my question is how to get data from setup
in the render function, the logic tells me that this is done like this:

setup: () => ({ state: 5 })
render() {
return this.data.state; // TypeError: Cannot destructure property 'state' of 'this.data' as it is undefined.
}

or this.$data.state, but this is not reflected in the types.

by old way i can:

setup: () => ({ state: 5 })
render() {
return (this as any).state;
}

but it was weird before whether it changed now?

@LinusBorg
Copy link
Member

LinusBorg commented Nov 15, 2019

there's not this.data, I'm not sure where you get that impression from?
Also you would return the render function from setup

import { createComponent, h } from '@vue/component-api'
export default createComponent({
setup: () => {
  const state = 5
  return () => h('div', this.state)
}

again, a really complete example would make this easier for me.

@NikitaIT
Copy link
Author

NikitaIT commented Nov 15, 2019

import { createComponent, h } from '@vue/component-api'
export default createComponent({
setup: () => {
  return { state: 5 };
},
render() {
return h('div', this.state); // for ts this has no state
}
});

but if

 export function createComponent< 
   Props, 
   RawBindings = Data, 
   PropsOptions extends ComponentPropsOptions = ComponentPropsOptions 
 >( 
   // prettier-ignore 
   options: ( 
     // prefer the provided Props, otherwise infer it from PropsOptions 
     HasDefined<Props> extends true 
       ? ComponentOptionsWithProps<PropsOptions, RawBindings, Props> 
       : ComponentOptionsWithProps<PropsOptions, RawBindings>) & 
     Partial<RawBindings> &
     Omit<Vue2ComponentOptions<Vue>, keyof ComponentOptionsWithProps<never, never>> 
 ): VueProxy<PropsOptions, RawBindings>;
import { createComponent, h } from '@vue/component-api'
export default createComponent({
setup: () => {
  return { state: 5 };
},
render() {
return h('div', this.state); // for ts this has state
}
});

@NikitaIT
Copy link
Author

NikitaIT commented Nov 15, 2019

or better we can type:

export interface ComponentOptions<
  V extends Vue,
  Data=DefaultData<V>,
  Methods=DefaultMethods<V>,
  Computed=DefaultComputed,
  PropsDef=PropsDefinition<DefaultProps>,
  Props=DefaultProps> {
...
  render?(this: Data & ComponentOptions<...>, createElement: CreateElement, hack: RenderContext<Props>): VNode;
...
}

and pass Omit<Vue2ComponentOptions<Vue, RawBindings>

@jacekkarczmarczyk
Copy link
Contributor

jacekkarczmarczyk commented Nov 29, 2019

Not sure if this is related, but it also doesn't recognize data from object API:

image

App.vue

// App.ts

import { VNode } from 'vue'
import { createComponent, ref } from '@vue/composition-api'

export default createComponent({
  name: 'app',

  setup () {
    return {
      foo: 'foo'
    }
  },

  props: {
    fizz: String
  },

  data: () => ({
    bar: 'bar'
  }),

  computed: {
    buzz (): string {
      return 'buzz'
    }
  },

  render (h): VNode {
    return h('div', [this.bar, this.fizz, this.buzz, this.foo])
  }
})

Error log

ERROR in C:/cygwin64/home/PC/jacekkarczmarczyk/vue-composition-bug-setup-data/src/App.ts(25,27):
25:27 Property 'foo' does not exist on type '(ComponentOptionsWithProps<PropsOptions, RawBindings, Props> & Pick<ComponentOptions<Vue, DefaultData<Vue>, DefaultMethods<Vue>, DefaultComputed, PropsDefinition<Record<string, any>>, Record<...>>, "mounted" | ... 34 more ... | "inheritAttrs">) | (ComponentOptionsWithProps<...> & Pick<...>)'.
  Property 'foo' does not exist on type 'ComponentOptionsWithProps<PropsOptions, RawBindings, Props> & Pick<ComponentOptions<Vue, DefaultData<Vue>, DefaultMethods<Vue>, DefaultComputed, PropsDefinition<Record<string, any>>, Record<...>>, "mounted" | ... 34 more ... | "inheritAttrs">'.
    23 |
    24 |   render (h) {
  > 25 |     return h('div', [this.foo, this.bar, this.fizz, this.buzz])
       |                           ^
    26 |   }
    27 | })
    28 |
ERROR in C:/cygwin64/home/PC/jacekkarczmarczyk/vue-composition-bug-setup-data/src/App.ts(25,37):
25:37 Property 'bar' does not exist on type '(ComponentOptionsWithProps<PropsOptions, RawBindings, Props> & Pick<ComponentOptions<Vue, DefaultData<Vue>, DefaultMethods<Vue>, DefaultComputed, PropsDefinition<Record<string, any>>, Record<...>>, "mounted" | ... 34 more ... | "inheritAttrs">) | (ComponentOptionsWithProps<...> & Pick<...>)'.
  Property 'bar' does not exist on type 'ComponentOptionsWithProps<PropsOptions, RawBindings, Props> & Pick<ComponentOptions<Vue, DefaultData<Vue>, DefaultMethods<Vue>, DefaultComputed, PropsDefinition<Record<string, any>>, Record<...>>, "mounted" | ... 34 more ... | "inheritAttrs">'.
    23 |
    24 |   render (h) {
  > 25 |     return h('div', [this.foo, this.bar, this.fizz, this.buzz])
       |                                     ^
    26 |   }
    27 | })
    28 |
ERROR in C:/cygwin64/home/PC/jacekkarczmarczyk/vue-composition-bug-setup-data/src/App.ts(25,47):
25:47 Property 'fizz' does not exist on type '(ComponentOptionsWithProps<PropsOptions, RawBindings, Props> & Pick<ComponentOptions<Vue, DefaultData<Vue>, DefaultMethods<Vue>, DefaultComputed, PropsDefinition<Record<string, any>>, Record<...>>, "mounted" | ... 34 more ... | "inheritAttrs">) | (ComponentOptionsWithProps<...> & Pick<...>)'.
  Property 'fizz' does not exist on type 'ComponentOptionsWithProps<PropsOptions, RawBindings, Props> & Pick<ComponentOptions<Vue, DefaultData<Vue>, DefaultMethods<Vue>, DefaultComputed, PropsDefinition<Record<string, any>>, Record<...>>, "mounted" | ... 34 more ... | "inheritAttrs">'.
    23 |
    24 |   render (h) {
  > 25 |     return h('div', [this.foo, this.bar, this.fizz, this.buzz])
       |                                               ^
    26 |   }
    27 | })
    28 |
ERROR in C:/cygwin64/home/PC/jacekkarczmarczyk/vue-composition-bug-setup-data/src/App.ts(25,58):
25:58 Property 'buzz' does not exist on type '(ComponentOptionsWithProps<PropsOptions, RawBindings, Props> & Pick<ComponentOptions<Vue, DefaultData<Vue>, DefaultMethods<Vue>, DefaultComputed, PropsDefinition<Record<string, any>>, Record<...>>, "mounted" | ... 34 more ... | "inheritAttrs">) | (ComponentOptionsWithProps<...> & Pick<...>)'.
  Property 'buzz' does not exist on type 'ComponentOptionsWithProps<PropsOptions, RawBindings, Props> & Pick<ComponentOptions<Vue, DefaultData<Vue>, DefaultMethods<Vue>, DefaultComputed, PropsDefinition<Record<string, any>>, Record<...>>, "mounted" | ... 34 more ... | "inheritAttrs">'.
    23 |
    24 |   render (h) {
  > 25 |     return h('div', [this.foo, this.bar, this.fizz, this.buzz])
       |                                                          ^
    26 |   }
    27 | })
    28 |
Version: typescript 3.5.3

Tried on new project with babel, typescript, eslint

@jacekkarczmarczyk
Copy link
Contributor

Also it's not a problem with just render(), see the example below. That's quite serious issue because it doesn't allow to incrementally introduce composition api in object api based projects, you need to convert full component to composition api to be able to use it

<script lang="ts">
import { createComponent } from '@vue/composition-api'

export default createComponent({
  setup: () => ({ foo: 'foo' }),

  props: {
    bar: String
  },

  computed: {
    buzz (): string {
      return this.foo + this.bar
    }
  }
})
</script>
ERROR in C:/cygwin64/home/PC/jacekkarczmarczyk/vue-composition-bug-setup-data/src/App.vue(13,14):
13:14 Operator '+' cannot be applied to types 'Vue.ComputedOptions<any> | (() => any)' and 'Vue.ComputedOptions<any> | (() => any)'.
    11 |   computed: {
    12 |     buzz (): string {
  > 13 |       return this.foo + this.bar
       |              ^
    14 |     }
    15 |   }
    16 | })

@bbugh
Copy link

bbugh commented Dec 8, 2019

According to many of the specs, the render function should have access to the setup bindings on this. For example, here's a spec using the return value of setup:

return {
count,
};
},
render(h) {
return h('div', this.count);
},

However, as reported in this issue, TypeScript does not reflect this.

    return {
      project,
      loading
    }
  },
  render (h, context) {
    this.project // ERROR: property does not exist on type

Property 'project' does not exist on type 'ComponentOptionsWithProps<{ projectId: { type: StringConstructor; required: true; }; }, { project: Readonly<Ref<Readonly<Pick<Project, "id"> & Pick<Project, "allContractorIncentivesTotal" | ... 7 more ... | "totalSalesTax">> | Readonly<...> | null>>; loading: Ref<...>; }, ExtractPropTypes<...>> & Pick<...>'

@NikitaIT
Copy link
Author

NikitaIT commented Dec 9, 2019

@bbugh @jacekkarczmarczyk
I think for a start it will be nice to add, maybe I will finalize my version, but my project was able to build without errors.

// ComponentOptions2.d.ts
// ComponentOptions2 is Vue2ComponentOptions in composition api
import {
  Vue,
  ComponentOptions,
  CreateElement,
  VNode
} from "vue";
import { PropsDefinition } from "vue/types/options";
declare global {
  type DefaultData<V> = object | ((this: V) => object);
  type DefaultProps = Record<string, any>;
  type DefaultMethods<V> = { [key: string]: (this: V, ...args: any[]) => any };
  type DefaultComputed = { [key: string]: any };
  interface ComponentOptions2<
    V extends Vue,
    Data = DefaultData<V>,
    Methods = DefaultMethods<V>,
    Computed = DefaultComputed,
    PropsDef = PropsDefinition<DefaultProps>,
    Props = DefaultProps
  > extends ComponentOptions<V, Data, Methods, Computed, PropsDef, Props> {
    render?(
      this: Computed & Methods & Data & Props & V,
      createElement: CreateElement
    ): VNode;
  }
}

and

// composition-api.d.tsimport "@vue/composition-api";
import Vue, { VNode, VueConstructor } from "vue";
import { UnwrapRef } from "@vue/composition-api/dist/reactivity";
import { SetupContext as _SetupContext } from "@vue/composition-api";
declare global {
  type SlotsData = {
    [key: string]: (...args: any[]) => VNode[];
  };
  interface SetupContext<TEvents, TSlots = SlotsData> extends _SetupContext {
    emit<TEvent extends keyof TEvents>(
      event: TEvent,
      data: TEvents[TEvent]
    ): void;
    readonly slots: SlotsData & TSlots;
  }
}
declare module "@vue/composition-api" {
  type Equal<Left, Right> = (<U>() => U extends Left ? 1 : 0) extends <
    U
  >() => U extends Right ? 1 : 0
    ? true
    : false;
  type HasDefined<T> = Equal<T, unknown> extends true ? false : true;
  type Data = {
    [key: string]: unknown;
  };
  type ComponentPropsOptions<P = Data> = {
    [K in keyof P]: Prop<P[K], true | false> | null;
  };
  type Prop<T, Required extends boolean> =
    | PropOptions<T, Required>
    | PropType<T>;
  interface PropOptions<T = any, Required extends boolean = false> {
    type?: PropType<T> | null;
    required?: Required;
    default?: T | null | undefined | (() => T | null | undefined);
    validator?(value: any): boolean;
  }
  type PropType<T> = PropConstructor<T> | PropConstructor<T>[];
  type PropConstructor<T> =
    | {
        new (...args: any[]): T & object;
      }
    | {
        (): T;
      };
  type RequiredKeys<T, MakeDefaultRequired> = {
    [K in keyof T]: T[K] extends
      | {
          required: true;
        }
      | (MakeDefaultRequired extends true
          ? {
              default: any;
            }
          : never)
      ? K
      : never;
  }[keyof T];
  type OptionalKeys<T, MakeDefaultRequired> = Exclude<
    keyof T,
    RequiredKeys<T, MakeDefaultRequired>
  >;
  type InferPropType<T> = T extends null
    ? any
    : T extends {
        type: null;
      }
    ? any
    : T extends
        | ObjectConstructor
        | {
            type: ObjectConstructor;
          }
    ? {
        [key: string]: any;
      }
    : T extends Prop<infer V, true | false>
    ? V
    : T;
  type ExtractPropTypes<O, MakeDefaultRequired extends boolean = true> = {
    readonly [K in RequiredKeys<O, MakeDefaultRequired>]: InferPropType<O[K]>;
  } &
    {
      readonly [K in OptionalKeys<O, MakeDefaultRequired>]?: InferPropType<
        O[K]
      >;
    };
  type ComponentPropsOptions<P = Data> = {
    [K in keyof P]: Prop<P[K], true | false> | null;
  };
  type ComponentInstance = InstanceType<VueConstructor>;
  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;
  type VueConstructorProxy<PropsOptions, RawBindings> = {
    new (): ComponentRenderProxy<
      ExtractPropTypes<PropsOptions>,
      UnwrapRef<RawBindings>,
      ExtractPropTypes<PropsOptions, false>
    >;
  };
  type VueProxy<PropsOptions, RawBindings> = ComponentOptions2<
    // !!!!!!!!!!!!!
    Vue,
    UnwrapRef<RawBindings>,
    never,
    never,
    PropsOptions,
    ExtractPropTypes<PropsOptions, false>
  > &
    VueConstructorProxy<PropsOptions, RawBindings>;
  interface SetupContext {
    readonly attrs: Record<string, string>;
    readonly slots: {
      [key: string]: (...args: any[]) => VNode[];
    };
    readonly parent: ComponentInstance | null;
    readonly root: ComponentInstance;
    readonly listeners: {
      [key: string]: Function;
    };
    emit(event: string, ...args: any[]): void;
  }
  type SetupFunction<Props, RawBindings> = (
    this: void,
    props: Props,
    ctx: SetupContext
  ) => RawBindings | (() => VNode | null);
  interface ComponentOptionsWithProps<
    PropsOptions = ComponentPropsOptions,
    RawBindings = Data,
    Props = ExtractPropTypes<PropsOptions>
  > {
    props?: PropsOptions;
    setup?: SetupFunction<Props, RawBindings>;
  }
  interface ComponentOptionsWithoutProps<Props = never, RawBindings = Data> {
    props?: undefined;
    setup?: SetupFunction<Props, RawBindings>;
  }
  function createComponent<RawBindings>(
    options: ComponentOptionsWithoutProps<never, RawBindings>
  ): VueProxy<never, RawBindings>;
  function createComponent<
    Props,
    RawBindings,
    PropsOptions extends ComponentPropsOptions = ComponentPropsOptions
  >(
    options: (HasDefined<Props> extends true
      ? ComponentOptionsWithProps<PropsOptions, RawBindings, Props>
      : ComponentOptionsWithProps<PropsOptions, RawBindings>) &
      Omit<
        ComponentOptions2<
          Vue,
          UnwrapRef<RawBindings>,
          {},
          {},
          {},
          HasDefined<Props> extends true
            ? Props
            : ExtractPropTypes<PropsOptions>
        >, // !!!!!!!!!!!!!
        keyof ComponentOptionsWithProps<never, never>
      >
  ): VueProxy<PropsOptions, RawBindings>;
}

expect
image
result
image

@bbugh
Copy link

bbugh commented Dec 11, 2019

@NikitaIT is there something in that patch you could add as a PR to this repo? Sounds like you've got it figured out.

@NikitaIT
Copy link
Author

@bbugh I could, but I don’t think it is worth doing.
I was not able to make the correct "infer" types for RawBindings and Props at the same time, maybe you should ask someone with a better understanding of dependent types to do this. My code does more than there is now, but it does it very dirty, forcing explicitly specifying types and making it difficult to expand types further.
To turn this into a good PR you need to do the following things:

  1. Proper type overloading in a convenient manner for SetupFunction.
  2. Relevant specification arguments to ComponentOptions.
  3. Type inference for methods, RawBindings and Props
  4. Limitation of PropsOptions extends ComponentPropsOptions = ComponentPropsOptions to the real interface of the component, because if you pass arguments to ComponentPropsOptions - JSX stops seeing the JSX.Node / Comp / JSX.Element interface methods.

@LeBenLeBen
Copy link

LeBenLeBen commented Feb 29, 2020

It seems this issue also occur with Vue own properties such as $data, $slots, $listeners, …

In the following component:

import { provide, InjectionKey, defineComponent } from '@vue/composition-api';
import { VNode, CreateElement } from 'vue/types/umd';
import useToggle from './useToggle';

export const ToggleSymbol: InjectionKey<object> = Symbol();

export default defineComponent({
  name: 'CToggle',

  setup() {
    const toggle = useToggle();
    provide(ToggleSymbol, toggle);
  },

  render(h: CreateElement): VNode {
    return h('div', this.$slots.default);
  },
});

TypeScript throws:

Property '$slots' does not exist on type 'ComponentOptionsWithProps<ComponentPropsOptions<Data>, void, ExtractPropTypes<ComponentPropsOptions<Data>, true>> & Pick<...>'.ts(2339)

Note that the component works as expected, it's just TypeScript being sassy.

@antfu
Copy link
Member

antfu commented Jun 23, 2020

Should be covered in #406

@NikitaIT
Copy link
Author

At the time this issue was created, changes were needed in the typing of vue itself due to problems with this in render in Vue2ComponentOptions.
#191 (comment)
I have not tracked master for a long time, so I’m not ready to say that this is done or a workaround is found.
If this has been fixed, then perhaps we can end here, and close issue.

@Dan503
Copy link

Dan503 commented Aug 5, 2020

@antfu Property '$slots' does not exist on type still appears to be an issue in the latest version of vue-composition-api

image

My package-lock.json

@vue/composition-api version 1.0.0-beta.6

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants