Skip to content

Typescript #25

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

Closed
AleksueiR opened this issue Sep 17, 2017 · 39 comments
Closed

Typescript #25

AleksueiR opened this issue Sep 17, 2017 · 39 comments

Comments

@AleksueiR
Copy link

Any plans to add Typescript support?

Thanks.

@foxbenjaminfox
Copy link
Owner

foxbenjaminfox commented Sep 19, 2017

I'm not really into Typescript, so I haven't particularly thought about it. I'm always happy to accept pull requests, though.

(And hey, if I see that this is a thing that would be helpful to many people, I might look into implementing it myself.)

@CAD97
Copy link

CAD97 commented Sep 30, 2017

Typescript type definitions don't only give type definitions for Typescript code, but are used by multiple other tools to provide type information -- such as autocomplete in VSCode. Providing type definitions would therefor help more than just Typescript users.

For whoever ends up adding type definitions (which may end up being me, I'm looking into it currently), this reference from Vue is useful: https://vuejs.org/v2/guide/typescript.html#Declaring-Types-of-Vue-Plugins

@topcatarg
Copy link

@CAD97 Did you find a way to add this to a Ts defined Vue object? I don't understand how I can add this to a file defined as a class, without ussing a decorator (which I don't have, off course)

@CAD97
Copy link

CAD97 commented Jul 11, 2018

I never ended up going much further with this. I never actually ended up using Vue through typescript, and haven't worked with it in over a year.

@adam-cyclones
Copy link

adam-cyclones commented Aug 22, 2018

I still think that this should be built in for the above reasons, however the workaround for the moment is to dump this into your shims-vue.d.ts

// This is a very rough interface which says, this object must be functions only
// you could probably do better
interface AsyncComputedObject{
  [K : string]:()=>any;
}

// ComponentOptions is declared in types/options.d.ts
declare module "vue/types/options" {
  //enable async computed
  interface ComponentOptions<V extends Vue> {
    asyncComputed?: AsyncComputedObject;
  }
}

@racase
Copy link

racase commented Aug 28, 2018

Anyone has managed to add it with typescript?

@adam-cyclones
Copy link

@recase my comment above you explains how to do it.

@racase
Copy link

racase commented Aug 29, 2018

@acronamy I have done what your comment says I have already done. What happens is that I do not know how to use it from a component written with typescript

saraedum added a commit to saraedum/vue-async-computed that referenced this issue Nov 9, 2018
saraedum added a commit to saraedum/vue-async-computed that referenced this issue Nov 9, 2018
@saraedum
Copy link

saraedum commented Dec 7, 2018

I am currently using the typings from this index.d.ts. These provide the interface to asyncComputed as part of the ComponentOptions. Getting the correct types on the actual computed properties is a bit more complicated though.
Currently, I am using https://github.com/vuejs/vue-class-component for this with a custom decorator such as

import { createDecorator, VueDecorator } from "vue-class-component";

/**
 * Create a decorator which turns a property into an asynchronously computed property with vue-async-computed.
 *
 * @param get a method returning a Promise; the value of the computed property is going to be the resolved value
 * @param loading a placeholder to return in the computed property until the promise resolves
 * @param lazy whether the promise should be resolved immediately or upon the first request of the computed property
 */
export default function AsyncComputed<T>(get: (() => T | Promise<T>), loading?: T, lazy?: boolean): VueDecorator {
	return createDecorator((options, name) => {
		(options.asyncComputed || (options.asyncComputed = {}))[name] = {
			default: loading,
			get,
			lazy,
		};
	});
}

With this I can write components such as

@Component
export default class C extends Vue {
  @AsyncComputed(function(this: C){ return Promise.resolve("Hello World"); })
  private value!: string;
}

It's not perfect but seems to work for my use cases. It would be nice to get rid of the this: C somehow and also to link the type of the promise and the type of the prop.

@adam-cyclones
Copy link

Would this decorator work nicer without using the helper createDecorator the callback seems really odd to me. Still great effort!

@saraedum
Copy link

saraedum commented Dec 8, 2018

Thanks for having a look at this. I am not sure which callback you are referring to but the createDecorator doesn't do much so it does not seem to me that it's to blame for anything here.
Afaik there is currently no way to feed the type of the class back into the decorator with TypeScript, so there is probably not much that can be done about the this: C in my example.
The other bit that bothers me, namely that the type of the promise Hello World, is not connected to the type of the property value!: string probably cannot be fixed either currently. vue-property-decorator seems to have the same problem. You can write

@Prop({default: 1})
x!: string;

and TypeScript does not complain for me.

@adam-cyclones
Copy link

So I think I'm wrong in saying callback. As this function parameter passed through the decorator is what I am referring to, it's just a function that decorates the target. I am use to seeing decorators affecting methods, intercepting the internals of that method and changing the output. I think your right on second glance though. The property is the thing to be decorated and it matters not how. I'm coming from a vanilla TS standpoint / ex Angular fanboy. As for the things that bother you, are there any PRs or plans for type info in decorators I wonder.

Personally I've moved away from this library and use vux module class syntax with tsx render functions. Everything seems to just work better for Typescript this way. I think that if your not using vuex for many reasons this is a very useful bit of work you are contributing and please keep it up!

@saraedum
Copy link

[…] As for the things that bother you, are there any PRs or plans for type info in decorators I wonder.

There had been some discussion at microsoft/TypeScript#4881 but from browsing over this issue it does not seem like there are going to be any changes to this soon.

@wujekbogdan
Copy link

@foxbenjaminfox

Is there any changes you could merge the typings from the forked saraedum/vue-async-computed repo? They seem to be correct.

@TheNoim
Copy link

TheNoim commented Jan 31, 2019

I use this ts definitions https://raw.githubusercontent.com/saraedum/vue-async-computed/5debb7dcd81f52183be55e05b866fc43278a9905/types/index.d.ts

With this tweak:

interface IAsyncComputedProperty<T> {
    default?: T | (() => T);
+   get?: AsyncComputedGetter<T>;
-   get: AsyncComputedGetter<T>;
    watch?: () => void;
    shouldUpdate?: () => boolean;
    lazy?: boolean;
}

Then I took the decorator idea of @saraedum and improved it.

import { createDecorator, VueDecorator } from 'vue-class-component';
import { IAsyncComputedProperty } from '..';

export function AsyncComputed<T>(
    computedOptions?: IAsyncComputedProperty<T>,
): VueDecorator {
    return createDecorator((options, key) => {
        options.asyncComputed = options.asyncComputed || {};
        const method = options.methods![key];
        options.asyncComputed![key] = <IAsyncComputedProperty<T>>{
            get: method,
            ...computedOptions,
        };
        delete options.methods![key];
    });
}

Usage:

@Component
export default class C extends Vue {
  @AsyncComputed()
  async helloWorld() {
      return "Hello World";
  }
}

@saraedum
Copy link

saraedum commented Jan 31, 2019

Nice. Let me try how your decorator works out here. I think you don't have to patch the type definition if you do something like this:

type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;

export default function AsyncComputed<T>(computedOptions?: Omit<IAsyncComputedProperty<T>, "get">): VueDecorator {
	return createDecorator((options, key) => {
		options.asyncComputed = options.asyncComputed || {};
		const method = options.methods![key];
		options.asyncComputed[key] = {
			get: method,
			...computedOptions,
		} as IAsyncComputedProperty<T>;
		delete options.methods![key];
	});
}

@TheNoim
Copy link

TheNoim commented Feb 4, 2019

@saraedum This looks nice. Thank you.

@perezmlal
Copy link

Hi @saraedum @TheNoim i have tried your tweak.

But it is not working for me 😢

ERROR: Property or method "helloWorld" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property.

<template>
    <p>Test {{ helloWorld }}</p>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import AsyncComputed from '@/plugins/AsyncComputed';

@Component
export default class Test extends Vue {   
    @AsyncComputed()
    public async helloWorld() {
        return 'Hello World';
    }
}
</script>

@saraedum
Copy link

saraedum commented Apr 5, 2019

We use this everyday so I am surprised it does not work. Do you have a repo to reproduce this?

@nwtgck
Copy link
Contributor

nwtgck commented Apr 8, 2019

Hi! Thank you very much, everyone.

I read comments here and made a simple Vue app for asynchronous computation in Vue + TypeScript.

Online demo

https://nwtgck-typescript-vue-async-computed-prac.netlify.com/
A message, "This is an asynchronous message!" will be shown after 1 sec

How to make

This is a Git diff for this adaptation.
nwtgck/typescript-vue-async-computed-prac@ed88abd

I hope this will be helpful to people reaching this issue :D

@foxbenjaminfox
Copy link
Owner

I do want to support this officially, but I'm not all that personally familiar with typescript in this context. I've pushed an experimental types branch, with an index.d.ts file based off of the versions by @saraedum and @nwtgck. Can you all confirm that this version (with this index.d.ts file) works?

If you confirm that these types work (and meet all of your needs), I can merge it into master and publish a new npm release with them. Of course, if any changes are necessary, let me know: I'm far from confident that everything is exactly right here.

@mrozekma
Copy link

Can you all confirm that this version (with this index.d.ts file) works?

I'm not doing anything particularly fancy with vue-async-computed, but it does work for me. It would be nice if it could enforce that default is valid, to avoid e.g.:

prop: {
    async get() { return 0; },
    default: 'foo'
}

But I get the impression that's fairly difficult in Typescript without support for existential types

@mrozekma
Copy link

mrozekma commented Apr 23, 2019

Not sure if this is a known limitation, but the new properties created by async-computed aren't known to Typescript. That is, if you do:

asyncComputed: {
    async foo() {
        return 'test';
    }
}

you can't refer to this.foo anywhere else. My workaround for the moment is to add on to data's type like this:

type AsyncComputedFields = {
    foo?: string;
}

Vue.extend({
    asyncComputed: {
        async foo() {
            return 'test';
        }
    },
    data() {
        const rtn = {
            bar: true,
            baz: false,
        };
        // Make the return type { foo: string | undefined; bar: boolean; baz: boolean; }
        return rtn as typeof rtn & AsyncComputedFields;
    }
});

@Sharlaan
Copy link

Sharlaan commented May 9, 2019

I'm trying to use asyncComputed in a THREE.js webgl app, in particular for loading a model.

I got a working version in a vanilla environnement 👍

asyncComputed: {
  geometry: {
    default: { position: [], normal: [] },
    get() { // Note it works without async keyword XD !
      const loader = new THREE.STLLoader();
      return new Promise((resolve, reject) => {
        const onLoad = (bufferGeometry) =>
          resolve({
            position: Array.from(bufferGeometry.attributes.position.array),
            normal: Array.from(bufferGeometry.attributes.normal.array),
          });
        const onProgress = ({ loaded, total }) =>
          console.info('progress', ((loaded / total) * 100).toFixed(2) + '%');
        const onError = (error) => reject(error);
        loader.load(this.stlModelURL, onLoad, onProgress, onError);
      });
    },
  },
},

... which is then referenced in template as geometry.position and geometry.normal (empty arrays at start, thanks to the default value).

But now, using the index.d.ts linked above, i want to integrate this in a full Typescript'ed class-based Vue-CLI environnement (strict: true) :

<script lang="ts">
// @ts-ignore
import AsyncComputed from 'vue-async-computed'; // TS complains about missing declaration
import { Component, Prop, Vue } from 'vue-property-decorator';
import { BufferGeometry, Vector3 } from 'three';
// @ts-ignore
import { STLLoader } from 'three/examples/jsm/loaders/STLLoader';

Vue.use(AsyncComputed);

...

@AsyncComputed({
  default: { position: [], normal: [] },
})
public async geometry() {
  const loader = new stlLoader();
  return new Promise((resolve, reject) => {
    const onLoad = (bufferGeometry: BufferGeometry) =>
      resolve({
        position: Array.from(bufferGeometry.attributes.position.array) as number[],
        normal: Array.from(bufferGeometry.attributes.normal.array) as number[],
      });
    const onProgress = ({ loaded, total }: { [key: string]: number }) =>
      // tslint:disable-next-line:no-console
      console.info('progress', ((loaded / total) * 100).toFixed(2) + '%');
    const onError = (error: string) => reject(error);
    loader.load(this.stlModelURL, onLoad, onProgress, onError);
  });
}

I get error in console :
Uncaught TypeError: Object(...) is not a function pointing at the public async geometry() line.

Did i write correctly the default ?
Should the computed property remain an object instead of a function ?
I'm clueless and my attempts to fix were unsuccessful...
Any ideas ?

Environnement :

  • Node12
  • "vue": "^2.6.10",
  • "vue-async-computed": "^3.6.1"
  • "vue-class-component": "^7.0.2"
  • "vue-property-decorator": "^8.1.1"
  • "typescript": "^3.4.5"

@saraedum
Copy link

@mrozekma the typescript typings won't show up correctly the way you've been using them. You have to use the @AsyncComputed decorator as shown in some samples above.

@saraedum
Copy link

@Sharlaan your code snippet says

import AsyncComputed from 'vue-async-computed'; // TS complains about missing declaration

Somehow you are not pulling in the typings of async computed. I am not surprised the other line then leads to type errors. Check that you have the correct index.d.ts available and nothing else tries to provide types for vue-async-computed.

@Sharlaan
Copy link

Sharlaan commented May 16, 2019

@saraedum i double checked if there were any declare module 'vue-async-computed' anywhere else but nothing. same for AsyncComputed. not a single mention except in the above index.d.ts.

TS is asking for some line starting with declare module, while the provided index.d.ts doesn't have any. But even adding this missing line not only does not take away the error, but also TS complains about augmentation not possible because the installed vue-async-computed dist folder is defined as untyped module...

Other thing, the error i'm speaking about in my previous post does not seem to point at a typing problem, i'm thinking more about a syntax problem, hence my question :
How to import and write AsyncComputed decorator with a default value and proper typings ?

@saraedum
Copy link

@Sharlaan can you share a repository where things don't work as expected so I can see for myself?

@Sharlaan
Copy link

Sharlaan commented May 17, 2019

Yes, here

AsyncComputed added in last commit.

The typings from @foxbenjaminfox are in typings/shims-async-computed.d.ts.

Note: the working original integration is in testIndex.html:L363, directly runnable with the VSCode extension Live Server.

@saraedum
Copy link

@Sharlaan I think you really misunderstood what's going on in this thread. Typescript support is not in vue-async-computed yet, you have to pull it in from somewhere else. There was simply no typing in your setup so it could not work. Also it seems you did not see #25 (comment) and the surrounding posts. You did not define the @AsyncComputed so it could not be found. As a general note, @ts-ignore means that you are doing something wrong. I know of very few cases where it is warranted.

The fixes to make things work (I didn't test, but TypeScript is happy) are here: https://github.com/Sharlaan/webgl-area-picking-vue/pull/1/files

@Sharlaan
Copy link

Sharlaan commented May 17, 2019

ah ok thanks, i'm not expert in Vue and TS indeed, i thought copying the index.d.ts linked in this thread would be enough :s

EDIT: ok it works ! :)

all that were needed is :

import VueAsyncComputed, { IAsyncComputedProperty } from 'vue-async-computed';
import { createDecorator, VueDecorator } from 'vue-class-component';

type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;

function AsyncComputed<T>(computedOptions?: Omit<IAsyncComputedProperty<T>, 'get'>): VueDecorator {
  return createDecorator((options, key) => {
    options.asyncComputed = options.asyncComputed || {};
    const method = options.methods![key];
    options.asyncComputed[key] = {
      get: method,
      ...computedOptions,
    } as IAsyncComputedProperty<T>;
    delete options.methods![key];
  });
}

@Sharlaan
Copy link

Sharlaan commented Jun 6, 2019

now with TS v3.5, Omit is built-in ;)

@blackmad
Copy link

blackmad commented Jun 18, 2019

Hmm, I can't compile Sharlaan's current project, tons of errors about

ERROR in /private/tmp/webgl-area-picking-vue/src/typings/async-computed.ts
12:13 Property 'asyncComputed' does not exist on type 'ComponentOptions<Vue, DefaultData<Vue>, DefaultMethods<Vue>, DefaultComputed, PropsDefinition<Record<string, any>>, Record<string, any>>'.
    10 | export default function AsyncComputed<T>(computedOptions?: Omit<IAsyncComputedProperty<T>, 'get'>): VueDecorator {
    11 |   return createDecorator((options, key) => {
  > 12 |     options.asyncComputed = options.asyncComputed || {};
       |             ^
    13 |     const method = options.methods![key];
    14 |     options.asyncComputed[key] = {
    15 |       get: method,

 error  in /private/tmp/webgl-area-picking-vue/src/typings/async-computed.ts

@lundmikkel
Copy link

@tioperez Did you ever find a solution to the "Property or method "foo" is not defined on the instance but referenced during render" problem? I've suddenly started getting it as well.

@doits
Copy link

doits commented Dec 4, 2019

Not sure if this is a known limitation, but the new properties created by async-computed aren't known to Typescript. That is, if you do:

asyncComputed: {
    async foo() {
        return 'test';
    }
}

you can't refer to this.foo anywhere else. My workaround for the moment is to add on to data's type like this:

type AsyncComputedFields = {
    foo?: string;
}

Vue.extend({
    asyncComputed: {
        async foo() {
            return 'test';
        }
    },
    data() {
        const rtn = {
            bar: true,
            baz: false,
        };
        // Make the return type { foo: string | undefined; bar: boolean; baz: boolean; }
        return rtn as typeof rtn & AsyncComputedFields;
    }
});

@mrozekma did you maybe find a solution to this that does not require those workarounds?

@saraedum
Copy link

saraedum commented Dec 4, 2019

@doits Have you tried the decorator approach described earlier? That mostly works for me in TypeScript.

@foxbenjaminfox
Copy link
Owner

As of v3.8.0, vue-async-computed has official typescript support!

If any issues are found, or if it's hard to use, please feel free to open other issues on this repository about it. For now I'm going to be closing this one.

@foxbenjaminfox
Copy link
Owner

foxbenjaminfox commented Dec 24, 2019

Update: v3.8.0 didn't actually include the types directory by accident. If you've updated to v3.8.0 and are wondering why the types aren't there, that's why. Please update to the v3.8.1 release, which includes the missing typescript types which were intended to be in v3.8.0.

@petr-motejlek
Copy link

Thank you!

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

Successfully merging a pull request may close this issue.