Giter Club home page Giter Club logo

Comments (22)

yyx990803 avatar yyx990803 commented on May 5, 2024 1

@littledan nice! I just did a quick experiment and it seems it would indeed work and solve quite a few issues in our current implementation. I'll try to rework the current prototype to use this strategy to confirm that it fully resolves the issues.

from core.

yyx990803 avatar yyx990803 commented on May 5, 2024 1

@littledan great news - confirmed that this can indeed work. This also solves all the problems related to this identity in constructor, fantastic!

from core.

posva avatar posva commented on May 5, 2024

Looks good, my only concern right now is regarding Two Ways of Doing the Same Thing
There will be a preferred way that we will have to advise for (advanced) users and I'm afraid we lose a big part of our users who enjoy the simplicity of Vue because we cannot ensure all articles and content around there are written using the same syntax (or both), so when people search for resources, they will be frustrated if they found the class syntax they do not understand yet.

I don't think there is a solution to this, we cannot move forward if we do not adopt new syntaxes, and it definitely improves typings, which also improves dev experience for regular devs, I'm more rising a concern I have

from core.

nickmessing avatar nickmessing commented on May 5, 2024

Is there any plan to add type-checking for events?
At the moment for TSX I use this syntax:

type Props = {
  tmp: string
  alpha?: boolean
}

type Events = {
  click: string
  something: boolean
  a: number
  b: void
}

type ScopedSlots = {
  s1?: {
    click?: string
    attr: boolean
  }
  s2?: {
    s: null
  }
  s3: {
    a: 'tst'
  }
}

class Cmp extends Vue<Props, Events, ScopedSlots> {
  created() {
    console.log(this.$props.alpha)
    this.$emit('a', 2)
    this.$emit('b')
  }
}

const h = <Cmp tmp="al" on={{ b: () => console.log() }} scopedSlots={{ s3: (props) => <a /> }} />

Can we consider full TSX support in core with v3?

from core.

yyx990803 avatar yyx990803 commented on May 5, 2024

@nickmessing yes, you should consider improving this file: https://github.com/vuejs/vue-next/blob/master/packages/runtime-dom/jsx.d.ts

from core.

yyx990803 avatar yyx990803 commented on May 5, 2024

I just realized an important drawback of using decorators for props would be TSX inference :/

from core.

nickmessing avatar nickmessing commented on May 5, 2024

@yyx990803, I will release an alpha of TSX support for Vue 2 and then will try to port that to Vue 3.

from core.

gustojs avatar gustojs commented on May 5, 2024

I like it and in the basic form, it looks relatively easy even for people without TS, React or Angular experience. I can imagine two questions being asked by potential users:

  • how does it look like with Vuex?
  • will the tools support it from day 1

But in both cases it's too early for that.

from core.

chrisvfritz avatar chrisvfritz commented on May 5, 2024

I have the same hesitation as @posva regarding fragmentation and complexity. Even within the class implementation, we'd have different best practices and caveats depending on whether native classes, Babel classes, or TypeScript classes are used. We're also warning people not to use some features of classes in some or all cases, like constructors and decorators. I doubt we're even fully aware of all the caveats and edge cases, especially since more changes are likely to come. The fragmentation and complexity also places a greater burden on library authors, not only in terms of support and possible edge cases, but also documentation. It makes me wonder whether we should strongly discourage everyone except TypeScript users from using classes, at least until the features we'd rely on are fully stable and implementations more unified.

It's really a shame there's been no response from the TypeScript team regarding @octref's proposed addition to the TypeScript toolchain, since I think it would solve all our problems regarding TypeScript. We could provide an even better typing experience than this, without having to deal with classes while they're still stabilizing. Should we try to push the TypeScript team harder on that?

from core.

yyx990803 avatar yyx990803 commented on May 5, 2024

@chrisvfritz

Even within the class implementation, we'd have different best practices and caveats depending on whether native classes, Babel classes, or TypeScript classes are used.

There really is only one difference, that is the usage of decorator for props in TypeScript.

The proposal intentionally covers all possible caveats in as much details as possible, but in practice all of them have very little impact on actual DX.

  • Regarding @prop decorator: this is something only TypeScript users need to be aware of, so it doesn't affect non-TS users at all. When decorators are finalized, the same syntax will also be able to work consistently between ES and TS without having to change.

  • Regarding [[Set]] vs [[Define]] semantics: this has been settled in the TC39 proposal. I've talked to @DanielRosenwasser and TypeScript will align with the [[Define]] syntax in 3.5 (opt-in) and then 3.6 (opt-out) in July. So by the time Vue 3 is actually out, all class implementations out there will be using consistent semantics.

  • Regarding this identity: this is also consistent between all class implementations. Plus this is a really really rare case because in practice 1. There's literally no use for constructor (which is more verbose as it requires super()) when there are class fields and lifecycle hooks. 2. Even when using constructor it's very rare for the user to rely on this identity from the constructor at all.

  • Regarding private fields: the runtime can detect usage of private fields by inspecting the error message and give appropriate warnings, so there's no chance for confusion here.

from core.

chrisvfritz avatar chrisvfritz commented on May 5, 2024

@yyx990803 What do you think about that addition to the TypeScript toolchain though? Should we try to push harder on that before trying to balance the compromises of making Vue more complex?

from core.

octref avatar octref commented on May 5, 2024

giving Vue no chance to overwrite the default value properly

class MyComponent extends Vue {
  @prop foo: number = 1
  bar = this.foo + 1
}

I believe you can hijack the setter of foo so 1 is merged into PropOptions.default instead of written to this.foo.

I do have other concerns and I'll pass them along after discussing with @DanielRosenwasser next week.

from core.

octref avatar octref commented on May 5, 2024

since I think it would solve all our problems regarding TypeScript.

It'll help Vetur to do auto completion and diagnostic error checking, but I think that's not necessarily a solution to the motivation on:

  • Having an authoring format (class) that aligns with internal implementation for alleviate Vue's maintenance.
  • Aligning with class semantics for a more intuitive API. If anyone understands classes in JS, I believe he can pick up the class API in a shorter time than the object-based API, because the API aligns with the class fields semantics.
  • Having a more intuitive and concise way to write types for props, especially in TS.

I'm not saying a better typing experience for the object based usage is not worth working on. I'll still work on it, and I can see it becoming doable.

from core.

littledan avatar littledan commented on May 5, 2024

I'd like to see if the issue with private can be fixed. I'm wondering, would it be reasonable for the Vue constructor to return the observation Proxy, rather than the $state? This way, the subclass constructor would see the observed thing as the this value, and private fields and methods would be added on the Proxy itself, so they would just work from methods. The Proxy would get defineProperty calls for field definitions, so there may need to be a little bit of logic to avoid observation callbacks from that.

I'd like to try this out and test it. Are there any example programs using Vue 3 that I could base this on?

from core.

littledan avatar littledan commented on May 5, 2024

@nicolo-ribaudo , who has been working on the implementation of private and decorators in Babel, has volunteered to help out with looking into whether Vue could use the pattern in #20 (comment) . Is there any way he could get access to this repository?

from core.

littledan avatar littledan commented on May 5, 2024

I'm very happy to hear this! Maybe we can work together on documenting this strategy well enough so that other framework authors can learn from this design.

from core.

octref avatar octref commented on May 5, 2024

So the decorator-less TS equivalent of this:

import { prop } from '@vue/decorators'

class MyComponent extends Vue {
  @prop count: number
}

Would be this:

interface Props {
  count: number;
}

class MyComponent extends Vue<Props> {
  static props = {
    count: Number;
  }
}

But for

class MyComponent extends Vue {
  @prop foo: ComplexType;
}

Is the equivalent below?

interface Props {
  foo: ComplexType; // but this types `this.$props.foo` not `this.foo`
}

class MyComponent extends Vue<Props> {
  static props = {
    foo: Object as Prop<ComplexType>
  }
}

So if you want to keep accessing props on this like in 2.0, the Vue.Component<Props> wouldn't help you, and you should still use the Prop<Type> on the static props declaration. Is that correct?

But then why should I still write the Props interface, if I can just do below? What's the reason someone would use this.$props.foo over this.foo and keep this.$props typed?

class MyComponent extends Vue<Props> {
  static props = {
    foo: Object as Prop<ComplexType>
  }
}

from core.

yyx990803 avatar yyx990803 commented on May 5, 2024

@octref

For the following

class MyComponent extends Vue {
  @prop foo: ComplexType;
}

This doesn't provide types for this.$props. So the equivalent would only have static props = { ... }.

On the other hand, I don't know if we can keep the inference working with the following:

class MyComponent extends Vue {
  static props = {
    foo: Object as Prop<ComplexType>
  }
}

This works for Object-based API by inferring it as an argument for Vue.extend. I don't know if we can even make it work with classes.

If a prop is already exposed and properly typed on this, I don't think there's much point in using this.$props unless they want to dynamically inspect / copy existing props, which should be rare. So in most cases, TypeScript users should just use the decorator.

from core.

DanielRosenwasser avatar DanielRosenwasser commented on May 5, 2024

So I spoke with @octref a bit about the current proposal. On the whole, data, computed, and methods all seem great. The only strange piece is props for TypeScript users. Here's a bit of the friction that users will likely run into that you may want to consider.

Issues with decorators

While decorators make internal type-checking easy, they don't solve the problem for external type-checking. Given that one of the ideas was to allow Vue to work better with JSX syntax or h calls, this may be an issue.

class SpecialButton extends Vue.Component {
    @prop text: string;

    static template = `
        <button>
            I am a special button!
            {{text}}
        </button>
    `;
}

class App {
    render(h) {
        // How does this get type-checked?
        return h(SpecialButton, {
            text: "click",
        });
    }

    static components = {
        SpecialButton,
    };
}

The solution is to create another interface.

interface Props {
    text: string;
}

class SpecialButton extends Vue.Component<Props> {
    @prop text: string;

    static template = `
        <button>
            I am a special button!
            {{text}}
        </button>
    `;
}

It may be reasonable to say "well, templates are the primary way users will consume these either, and we'll always be able to read the source, so users who use render methods can opt in here", but that's not entirely true and there are caveats to that.

Anything that tries to check templates will inevitably have to deal with components in npm which are already compiled. .d.ts files are a lightweight way to serialize the public API without containing the full source code, and that's the only thing TypeScript understands today.

Issues with the schema

The props schema itself is just a lot of repetition for TypeScript users, and in our experience, it's not expressive enough. Users quickly hit this as soon as an they have props more complex than primitive types. Even if it was expressive enough, TypeScript doesn't currently support the sort of inference you'd need to make that work correctly when subtyping.

The most reasonable thing to do with the schema is to just define the names of each prop instead of defining the respective validators.

interface Props {
    text: string;
}

class SpecialButton extends Vue.Component<Props> {
    static props = ["text"];
}

Issues with no schema/decorators

By default, users who provide no schema for props need to access props through this.$props.

interface Props {
    text: string;
}

class SpecialButton extends Vue.Component<Props> {
    static template = `
        <button>
            I am a special button!
            {{$props.text}}
        </button>
    `;
}

This feels a little strange to explain to a user, and it can be a pain to refactor to if you already have a props schema.

I think the best experience I can imagine is just

interface Props {
    text: string;
}

class SpecialButton extends Vue.Component<Props> {
    static template = `
        <button>
            I am a special button!
            {{text}}
        </button>
    `;
}

but I know that there are concerns over accidentally defining an instance member with the same name as a prop.
Maybe if that's the concern, you could run the type of props through the Readonly type to get the same sort of checking at design-time.

from core.

yyx990803 avatar yyx990803 commented on May 5, 2024

@DanielRosenwasser thanks for the feedback! I agree that ideally, a single interface would offer a decent dev experience and covers both internal and external type checking, however, our current constraints are:

  • A runtime schema is required to be able to expose props on this, due to potential conflicts;

    • We can potentially remove this requirement, however that could make it too easy for plain ES users to author components that lack proper documentation for its own props.
  • The interface generic argument doesn't offer any runtime metadata, so there's no way for Vue's runtime to know about the declared props, thus it won't proxy the props on this (in runtime land). If the interface exposes props on this in type land, there would be a mismatch between the types and runtime behavior.

    • Maybe we can provide a tool that auto-compiles such interfaces into equivalent runtime schema, but it's one additional thing the user needs on top of standard TS tooling (or is there anyway to hook into TS codegen?) It feels too heavy-handed to require an additional build step for almost any TS user.

    • Decorators can leave runtime metadata so Vue's runtime can pick it up and proxy the props properly.

  • The interface does not cover the full runtime behavior available from the props schema, e.g. custom validators, default value and required-ness. To specify a default value the user still need separate declarations.

    • Decorators on the other hand can do both in one place.

So it seems the main downside of decorators is that it doesn't work well with TSX. Is the generic argument a hard requirement for TSX to work? Maybe instead of ElementAttributesProperty we can introduce something like this:

interface ElementAttributesDecorator {
  '@prop': {}
}

Alternatively, is there anyway to infer this.$props based on decorators?

from core.

octref avatar octref commented on May 5, 2024

@yyx990803 You might want to look into Stencil.

  • They have a similar @Prop API: https://stenciljs.com/docs/my-first-component/

  • From @Prop decorator they generate such d.ts for each component (so JSX/TSX auto completion works):

    import { Component, Prop } from '@stencil/core';
    
    @Component({
      tag: 'my-component',
    })
    export class MyComponent {
    
      /**
       * My Prop
       */
      @Prop() size: 'small' | 'medium' | 'large';
    
      render() {
        return <div>Hello, Stencil! I'm {this.size}</div>
      }
    }
    import '../../stencil.core';
    export declare class MyComponent {
        /**
         * My Prop
         */
        size: 'small' | 'medium' | 'large';
        render(): JSX.Element;
    }

What do you think about dropping <Props> as a public generic argument?

  • It doesn't help typing this props (for either in template or JSX/TSX)
  • It doesn't help d.ts generation for others to use your component
  • From what I read $props is not the recommended way to access props inside your own components
  • From parent component you should use static props of child component to infer props, not the <Props> interface

IMO $props should be an implementation detail and typing it is not useful.

from core.

yyx990803 avatar yyx990803 commented on May 5, 2024

@octref interesting, so the .dts is updated by a build step... (and can be done continuously during dev) which I think acceptable for users using TSX. However the way Stencil does it requires the JSX elements to be registered as global intrinsic elements instead of value-based elements. Is there anyway for a .d.ts file to augment an existing module?

RE dropping <Props>: for some users they do want access to props on this.$props because they feel that is more explicit (i.e. this is definitely a prop, not a computed property or state). And in some cases a user may want to dynamically inspect this.$props to see what props are being passed in, or copy/augment it, or pass it down to another component via spread. So I think typing this.$props is definitely still needed.

$props is also used for TSX inference for now with:

interface ElementAttributesProperty {
  $props: {}
}

But technically, this doesn't have to be done via a generic argument... the user can just declare $props themselves on the class. So yeah I think the generic argument can be dropped (assuming we don't allow it to expose properties on this).

The problem with inferring from static props is that TSX doesn't have a way to do it easily (unlike grabbing it from $props with ElementAttributesProperty).

So if we can find a way to generate the proper type augmentations (basically auto-generate proper $props types for the user based on their @prop or static props usage, but without modifying the file the user is editing) we can get TSX covered - however, I think it would be even better if we can open up the TSX API a little bit by offering alternative ways to specify where to infer props (e.g. a way to say: "these properties using the @prop decorator are the ones exposed as JSX attributes.")

/cc @DanielRosenwasser

from core.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.