vuejs / rfcs

RFCs for substantial changes / feature additions to Vue core
4.88k stars 546 forks source link

Vue TypeScript Friendly Class API #48

Closed adityapurwa closed 10 months ago

adityapurwa commented 5 years ago

This is not an RFC - this issue is written so we can discuss more on how to make Class API works perfectly with TypeScript. Discussion is welcome and highly appreciated!

Summary

This RFC tries to make Vue Class API TypeScript friendly by breaking some of the things that now exist in Vue but not really practical to use with TypeScript. This is TypeScript oriented, so expect a lot of things that are already in Vue to be broken so it could fit TypeScript designs.

Inspired by React TypeScript supports.

Basic example

The new Class API would be able to be used like:

interface State {
  counter: number;
}
interface Props {
  startValue: number;
}

class Counter extends Vue.Component<State, Props>{
  data(): State {
    return {
      counter: this.props.startValue
    }
  }
  render(){
    const props = this.props;
    const state = this.state;
    // Not actual JSX or template
    return (
      <button @click={this.addCounter}> Counter {{ state.counter }} </button>
    )
  }
  addCounter() {
    this.state.counter++;
  }
}

Motivation

The motivation behind this that we hope Vue will have first class TypeScript supports like React. It's been a breeze to use React with TypeScript, but couldn't say the same with Vue. Having first-class TypeScript support means we can reduce the number of bugs related to inconsistent typings and better coding assistance in IDE.

The Class API is well structured, so it is easier to read and explore, that is why we hoped that we can continue to improve the Class API.

Detailed design

The goal is to separate the main instance with user-land and type-land properties. We then won't have to worry about merging the user-land code and type-land code, we can safely annotate each part that we wanted to annotate.

So we might end breaking a lot of existing Vue code because everything would be removed from the main instance.

It might look like this:

abstract class Component<TState, TProps, TApis, TEvents> {
  props: TProps;
  abstract data(): TState;
  abstract render();
  abstract api: TApis;
  abstract events: TEvents;
}

TApi might be used to expose the API that the component exposes, such as its methods. And TEvents is used to expose events that can be fired or listened to by other components.

Drawbacks

Alternatives

The Function API is a good way of supporting TypeScript.

Adoption strategy

We can write tools that detect the user state and props, and then modify the code that accesses them so it got prefixed by this.state or this.props.

We can also make the state and props property live side by side with the user existing state and props that lives on the instance directly. But it might mean we have to support both designs on the run.

Unresolved questions

We still don't know how to utilize TypeScript and make these things better:

LinusBorg commented 5 years ago

Hi, thanks for wanting to contribute with an RFC.

However, you posted this as an issue whereas RFC are done as Pull requests.

So please follow these steps:

  1. fork this repository.
  2. in your fork, write out our proposal in a markdown file in the /active-rfcsdirectory, preferably in its own branch
  3. open a Pull request from your fork to this main repository.
  4. In the PRs comment body, give short Summary of your PR, then link to the rendered version of the md file that you added.
  5. Close this issue, as discussions should happen in the PR comments.

Please see existing RFCs for examples. If you have any more questions, feel free to ask here.

LinusBorg commented 5 years ago

Also, your RFC should address points from the abandoned class API RFC that we already had, and how your approach intends to solve them or why they don't apply etc.

https://github.com/vuejs/rfcs/pull/17

adityapurwa commented 5 years ago

Hi @LinusBorg and thank you for responding!

I am creating this as an issue because I wanted to have a discussion first about this with others, following:

Gathering feedback before submitting It's often helpful to get feedback on your concept before diving into the level of API design detail required for an RFC. You may open an issue on this repo to start a high-level discussion, with the goal of eventually formulating an RFC pull request with the specific implementation design.

At the meantime, while gathering more feedback - I am currently trying to implement a PoC for this API.

If a discussion in a PR is preferred, I would close this issue and starts working on the PR.

Thank You!

LinusBorg commented 5 years ago

Ah, I see. Well, that's totally fine. It's just that you already wrote the issue in the formulaic way of the actual RFC, so I thought you wanted to submit it as an RFC directly.

It's fine to discuss here, just make sure to give some context at the beginning of your post that this is not yet meant as an RFC. Otherwise people might be confused, as evidenced by me :-P

adityapurwa commented 5 years ago

Oops, sorry for the confusion @LinusBorg - I edited the issue description, I hope it doesn't cause any more confusion. Sorry!

smolinari commented 5 years ago

There are inherent issues with using Class based componets in general. It's the motivation for why React came up with hooks.

Please read....https://reactjs.org/docs/hooks-intro.html#classes-confuse-both-people-and-machines

However, we found that class components can encourage unintentional patterns that make these optimizations fall back to a slower path. Classes present issues for today’s tools, too. For example, classes don’t minify very well, and they make hot reloading flaky and unreliable. We want to present an API that makes it more likely for code to stay on the optimizable path.

I'm sure this is also what Evan knows and understands and is trying to avoid. 😃

Scott

lovetingyuan commented 5 years ago

Maybe TypeScript Transformer Plugin(typescript.CustomTransformers) is a possible way?

adityapurwa commented 5 years ago

Hi @lovetingyuan, what would be the use case for the transformer plugin as I never used it?

adityapurwa commented 5 years ago

I tried to create a sketch of the API design,

Demo available https://stackblitz.com/edit/vue-ts-class-api-0001

// A quick sketch of the API Design
// Not an actual Vue app
abstract class Component<TProps, TState>{  
  protected abstract data(): TState;    
  protected state: TState;
  abstract render();  

  constructor(protected props: TProps, protected onRefresh: (c: Component<TProps, TState>) => void) {
    this.constructProxyFromData();
    // This is to simulate Vue rendering logic
    this.onRefresh(this);
  }
  private constructProxyFromData(){
    const obj = this.data();
    this.state = new Proxy(obj as any, {
      set: (obj, prop, val) =>{
        obj[prop] = val;        
        this.onRefresh(this);        
        return true;
      }
    }) as any;    
  }
}

class Counter extends Component<{
  counter: number
}, {
  counter: number
}>{

  data(){
    return {
      counter: this.props.counter
    }
  }

  render(){
    // This would be the template in real Vue app
    return `<h1>Counter ${this.state.counter}</h1>`;
  }

  click(){
    // Simulates a button click
    this.state.counter++;
  }

}

const appDiv = document.getElementById("app");
// This would be written as <Counter counter="1" /> in template
const counter = new Counter({
  counter: 1
}, (c)=> { // simulates Vue rendering logic
  appDiv.innerHTML = c.render();
});

// Simulates click
setInterval(()=>counter.click(), 1000);
xxyuze commented 5 years ago

Using mobx we can build a ui framework-independent data layer. You are free to choose to use vue or react. And It has TypeScript Friendly Class API.

ishowman commented 3 years ago

Also, your RFC should address points from the abandoned class API RFC that we already had, and how your approach intends to solve them or why they don't apply etc.

17

Now that class component is not support in Vue3, is there anyway to migrate vue2 class component to vue3 component? @LinusBorg

LinusBorg commented 3 years ago

The vue-class-component is still being supported and has seen a release that's compatible with Vue 3.

so just continue using this.

the class API just won't be integrated into Vue core, which the dropped RFC was about.

adityapurwa commented 10 months ago

I believe this is no longer relevant as we have better TypeScript support now. As the issue poster, I won't mind if this issue is closed. Thank you to everyone involved on this discussion.

cc @LinusBorg Please feel free to either close or keep this issue opens depending on your insight. Thank you!