core: Initial proposal of breaking changes in 3.0

Changes

Props

Component no longer need to delcare props in order to receive props. Everything passed from parent vnode’s data (with the exception of internal properties, i,e. key, ref, slots and nativeOn*) will be available in this.$props and also as the first argument of the render function. This eliminates the need for this.$attrs and this.$listeners.

When no props are declared on a component, props will not be proxied on the component instance and can only be accessed via this.$props or the props argument in render functions.

You still can delcare props in order to specify default values and perform runtime type checking, and it works just like before. Declared props will also be proxied on the component instance. However, the behavior of undeclared props falling through as attrs will be removed; it’s as if inheritAttr now defaults to false. The component will be responsible for merging the props as attrs onto the desired element.

VNodes

Flat Data Format

// before
{
  attrs: { id: 'foo' },
  domProps: { innerHTML: '' },
  on: { click: foo },
  key: 'foo',
  ref: 'bar'
}

// after (consistent with JSX usage)
{
  id: 'foo',
  domPropsInnerHTML: '',
  onClick: foo,
  key: 'foo',
  ref: ref => {
    this.$refs.bar = ref
  }
}
  • Less memory allocation & faster diffs
  • Makes it consistent with JSX
  • Easier spread operations

VNodes are now context-free

h can now be globally imported and is no longer bound to component instacnes. VNodes created are also no longer bound to compomnent instances (this means you can no longer access vnode.context to get the component instance that created it)

Component in Render Functions

No longer resolves component by string names; Any h call with a string is considered an element. Components must be resolved before being passed to h.

import { resolveComponent } from 'vue'

render (h) {
  // only necessary when you are trying to access a registered component instead
  // of an imported one
  const Comp = resolveComponent(this, 'foo')
  return h(Comp)
}

In templates, components should be uppercase to differentiate from normal elements.

NOTE: how to tell in browser templates? In compiler, use the following intuitions:

  1. If uppercase -> Component
  2. If known HTML elements -> element
  3. Treat as unknown component - at runtime, try resolving as a component first, if not found, render as element. (resolveComponent returns name string if component is not found)

Slots

Unifying Normnal Slots and Scoped Slots

Scoped slots and normal slots are now unified. There’s no more difference between the two. Inside a component, all slots on this.$slots will be functions and all them can be passed arguments.

Usage Syntax Change

// before
h(Comp, [
  h('div', { slot: 'foo' }, 'foo')
  h('div', { slot: 'bar' }, 'bar')
])

// after
h(Comp, () => h('div', 'default slot'))

// or
import { childFlags } from 'vue/flags'

h(Comp, null, {
  slots: {
    foo: () => h('div', 'foo'),
    bar: () => h('div', 'bar')
  }
}, childFlags.COMPILED_SLOTS)

// also works
h(Comp, null, {
  foo: () => h('div', 'foo'),
  bar: () => h('div', 'bar')
})

Functional Component

Functional components can now really be just functions.

// before
const Func = {
  functional: true,
  render (h, ctx) {
    return h('div')
  }
}

// Now can also be:
const Func = (h, props, slots, ctx) => h('div')
Func.pure = true

Async Component

Async components now must be explicitly created.

import { createAsyncComponent } from 'vue'

const AsyncFoo = createAsyncComponent(() => import('./Foo.vue'))

Directives

  • Now are internally on-vnode hooks with the exact same lifecycle as components.

  • Custom directives are now applied via a helper:

import { applyDirective, resolveDirective } from 'vue'

render (h) {
  // equivalent for v-my-dir
  const myDir = resolveDirective(this, 'my-dir')
  return applyDirective(h('div', 'hello'), [[myDir, this.someValue]])
}

Styles

No longer performs auto-prefixing.

Attributes

  • No longer auto coerces boolean or enumerated attribute values.
  • No longer removes attribute if value is boolean false. Instead, it’s set as attr="false" instead. To remove the attribute, use null.

Filters

Filters are gone for good (or can it?)

Refs

  • Function refs are now supported.
  • String refs are no longer supported in render functions (now only supported in templates and compiled into function refs)
  • String refs no longer automatically generates an array when used with v-for. Instead, use something like :ref="'foo' + key" or function refs.

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 15
  • Comments: 99 (97 by maintainers)

Most upvoted comments

/cc @vuejs/collaborators @octref @Atinux @DanielRosenwasser @alexchopin @clarkdo @pi0 @chenjiahan @johnleider @Leopoldthecoder @KaelWD @icarusion @rstoenescu @rigor789 @Hanks10100

You have been added to this repo either because you are a core team member or as maintainer of notable projects that builds on top of Vue. I’m giving you early access to the WIP repo to provide early feedback on proposed breaking changes to Vue internals. What I am most interested in is how much would the above changes affect your project - for each change, how difficult would it be to adapt to it? Would any of these be a deal breaker? Any of these would really help? Any additional ideas? Any type of feedback is welcome - also keep in mind that this is very early stage and nothing is set in stone yet.

I was thinking a bit more about the render function signature and would like some feedback.

Should we remove h as the first argument?

Please vote with thumbs up for removing it, and thumbs down for keeping it.

Since in 3.0 VNodes are context free, it’s no longer necessary to use the instance-bound h function in render functions. Instead, we can just use the global h imported from Vue.

Pros for removing it:

  • Relying on instance specific h makes it necessary to always pass along the h when you want to split part of the render function into another function, which can be really annoying. Importing the global h allows you to just do it once and forget about it.

  • h injection has been annoying to deal with in JSX. We have some pretty hacky logic for automatic h injection in our current JSX implementation. Importing instead of passing argument allows us to get rid of that problem entirely.

  • Assuming we will be importing h in a reasonable amount of cases, we are essentially wasting one argument position in those cases. Removing the argument and enforcing importing h everywhere makes usage consistent.

Cons for removing it:

  • Incompatibility with 2.x API. Although this can easily be made compatible via an runtime adaptor or automatic codemods.

  • Always need to import h when using render functions. For end users, this isn’t that bad and is balanced by the benefit mentioned above.

    This has another implication for library authors, because they will be importing h from Vue as a peer dependency, this requires them to use a correct externals configuration when distributing their libraries. However, externals configuration will likely become a necessity for 3.0 compatible libs because we will have a lot of the framework become tree-shakable, and for libs to be able to use these tree-shakable features, they will always need correct externals setup (e.g. import { h } from 'vue' should be preserved in ESM builds but converted to const { h } = Vue in global builds.) The configuration can be standardized via Vue CLI or a library boilerplate.

Update: attribute fallthrough will be preserved when the component has declared props (the behavior will be the same as before). In addition, all parent class, style and nativeOn bindings will be in $attrs, so when the child component returns a Fragment, or has inheritAttrs: false, simply doing v-bind="$attrs" or {...this.$attrs} in JSX will place all non-props bindings from parent root node on that node instead.

Functional component function syntax

@yyx990803 Regarding:

const Func = (h, props, slots, ctx) => h('div')

I’m wondering if we should keep everything in an object in the 2nd argument, as we do now, so that users don’t have to worry about argument order and can just pull out what they need with destructuring.

why we need the pure props?

I think it’s to identify functional components from components created with Vue.extend/ async components. But I think we could add a flag to extended components, and omit pure from functional components.

Hi Evan,

Nice work! Here are some of my early thoughts:

Props

However, the behavior of undeclared props falling through as attrs will be removed; it’s as if inheritAttr now defaults to false. The component will be responsible for merging the props as attrs onto the desired element.

Does this mean component users cannot output arbitrary attributes onto the root element unless the component authors explicitly allow this? I think that might cause some troubles because component authors usually cannot know in advance what attributes are necessary in certain use cases (mostly interoperability issues). eg. A11Y related stuff aria-*/role or Microdata’s itemprop/itemtype/… or some necessary attributes when leveraging some existing frontend libraries that depend them.

VNodes

VNodes are now context-free

This seem that we cannot access components inside directives anymore (which we do quite a lot currently in our projects). I personally prefer directives over components on certain use cases and I haven’t think of a way to migrate without drastically breaking our current API ATM.

Component in Render Functions

In templates, components should be uppercase to differentiate from normal elements.

Does this mean we no longer allow kebab-casing for Vue components in templates?

NOTE: how to tell in browser templates?

Just skip step 1 for compiler intuitions seems fine?

Slots

Unifying Normnal Slots and Scoped Slots

Great. Though this might hurt those components providing slots and scoped slots with the same name (but for different purposes). It already doesn’t work as expected when using template, while those who are using render functions for this might gonna change slot names.

Directives

  • Custom directives are now applied via a helper:

Does this only affects render functions? Is there any difference for templates?

Attributes

No longer removes attribute if value is boolean false. Instead, it’s set as attr=“false” instead. To remove the attribute, use null.

Do we still have predefined boolean attributes (true boolean attrs like disabled/checked, not boolean-ish strings like draggable)?

Personally I would much rather prefer inconsistence here, than inconvenience by having to explicitly pass class to the child component as a prop. Being them merged automagically is very convenient and feels kinda natural. I use it all the time to have my components independent of the place they’re being used in. This is a very good candidate for an RFC imo.

Additional considerations for h removal and render functions signatures:

First of all we are making render functions signatures for stateful vs. functional components exactly the same.

With h always taking up the first argument position, render functions would look clunky with flat arguments:

render (h, props, slots, attrs) {
  // ...
}

Which is what lead to @chrisvfritz 's suggestion of using a context object:

render (h, { props }) {
  // ...
}

Side note: in 3.x functional components don’t really need the ctx object anymore. (I’ve always thought about the functional render context being a bit messy). With the new implementation, props, slots and attrs are all you need to transparently pass everything down to a child component. And if a component needs access to its parent, use a stateful one instead (as the performance has improved greatly).

So, after h removal, we have the following options:

render (props, slots, attrs) {
  // ...
}

vs.

render ({ props, slots, attrs }) {
}

attrs is going to be a rarely used value, as it’s automatically merged onto the root node and needed only when returning fragments or with inheritAttrs: false. (It’s also accessible via this.$attrs.) So in most cases you actually end up with just two arguments, or even just one if your component doesn’t deal with slots:

render (props, slots) {
  // ...
}

render (props) {
  // ...
}

The benefit of this over a context object:

  • No need for destructuring syntax. Note a context object also makes ES5 usage more cumbersome due to the lack of destructuring.

  • No need to allocate an additional object and destructuring from it for each render. Tiny cost, but it adds up.

  • Easier TS typing. In TS, typing object destructuring is a bit annoying.

So I’m actually inclined to use a flat, no h render function signature:

render (props, slots, attrs) {
  // ...
}

Thoughts?

@chrisvfritz I work on TypeScript, but I hope you can trust this is in good faith. 🙂

I really don’t want to push the Vue community into anything that would negatively affect it for the sake of TypeScript support, so I have some of the same concerns you have on the Vue community’s behalf; however, the truth of it is that so many of the things Vue’s API does today is to just construct a class without using any language syntax. It feels more comfortable from a pure ES5 world, but as time goes on the concept will look stranger to newer JS users who come from other languages or who already know about classes. Anecdotally, I have friends who’ve used some frameworks that started in the ES3-era whose reactivity model was based around calling .get() and .set(). They now find it strange in a world with getters and setters.

In fact, most of the basic concepts boil down to something simpler when you use classes. methods are now just instance methods. computeds are just get-ers and set-ers. The name field can just be the class name. As a small bonus, these things don’t need to be separated by commas. 😉

And while I get the concern over ECMA 262 proposal churn, as an occasional TC39 attendee, I think you’ll see less of this over time as these features stabilize - something @yyx990803 and @octref partially alluded to.

I’ve even heard from Dan and Andrew that they’re moving completely away from class components, partly due to these chronic problems (and the awkwardness of JavaScript’s classes in many cases

Admittedly I’m not the expert on component models here; I’d be curious to see what they have as an alternative, but I’m very surprised given that the community very quickly switched over from createClass even without things like auto-binding this on methods.

if there’s literally any other way to improve support - even relying on an as-yet-unreleased feature of TypeScript - I feel like we should strongly consider it.

This is tough - I have spent a lot of hours trying to make things work better between Vue and TypeScript with the goal of making it so that the Vue experience is 1:1 between JavaScript and TypeScript users. I don’t have any ideas that would significantly improve the experience around the current API. In short, my goal was always to make it so the Vue ecosystem wouldn’t have to accommodate TypeScript, but rather that the TypeScript language would find a way to accommodate Vue. We have that in some capacity, and it’s been helpful for tooling in Vetur. But:

  1. The types are hard to reason about.
  2. The UX isn’t ideal - inference is hard, loopiness makes the experience unpredictable, and scary-looking intersection types end up leaking out to the user.

We’re working on driving UX improvements from our side. But on the whole, if there’s an opportunity to use modern features that’s largely backwards compatible, and it opens up the chance for things like

then I feel like this is a reasonable direction for Vue to take.

The ES class implementation is particularly hacky within engines

I can’t speak to that (I’m not an engine person) but I’m wondering what you mean. Maybe something we can chat about that elsewhere. 🙂

Adding .attr make me feel Vue templates are not HTML anymore.

This looks a amazing, I finally feel like this is something I can navigate and undestand 😛

Points I want to comment on:

Props

Like others I’m not too sure about the whole droppinf of $attrs and $listeners, and especially automatic interance of attributes.

While $attrs and $listeners can probably be re-implemented in userland pretty easily, the last point could be a point of great pain for people - unless we find a way to easily allow for that to be done in userland as well without requiring to touch every single template in your project?

Filters

I’m torn. Filters are usually formatters that people use appliction-wide. When we drop them, people will be forced to implement them as methods, possibly via extensions to the Vue prototype.

  1. this creates the risk of name conflicts. currency is a create name for a filter or a data/prop. Sure, naming rules can help here, but the current implementation doesn’t have this problems as filters live in there own namespace.
  2. if people decide to collect their filters in one object with which they expend the prototype to minimize the riks of the previous point - what to call it? And what about conflicts with filters added by plugins?

I’m not too attached to them, but if we drop them, we need a good guide about how to replace them in a maintainable way.

Slots

Awesome, will be very good for performance to make them lazy I imagine. Even though that change requires changes to manually-written render function, the changes are small and easy (and maybe possible to be automated?)

Vnodes

The new API seems great, slim and easy to parse. and I see how attrs and listeners don’t fint in there, but could be re-implemented on the instance as mentioned above.

Of course this means, similar to slots, that manually written render components have to be updated, but unlike slots, the change is a little more work. I could imagine that we provide a little helper method that people can wrap their manually written VNodeData objects in to be converted to the new format. That would allow for a quick fix, and can be cleaned manually later.

Mixins

This is a point that wasn’t mentioned at all in the OP, and I can’t find anything about them in the source either.

I hope they’re not killed like React did when they switched to a class-based syntax. So much of the ecosystem relies on them (most of Vuetify is implemented as mixins I think), so I can imagine it would be a big problem.

Would it be possible to make them a static property on the class that is used by the renderer to apply the mixin after creating the instance? what about beforeCreate mixins, then? How do we make them works with types? I have no idea. 😕

And on a lighter note: Not sure how to feel about the fact that Vue 3 will have Portals, which kills the need for my only popular OS library 😄 😄

Really be excited with Vue 3, and looking forward to it.

Weex is built on bottom of Vue.js, so the breaking changes of syntax will not affect Weex actually. But some compatibility work still can’t be omitted, mostly for the new package structure and VNode, not syntax.

VNodes are now context-free

By the way, I think it is a good idea and should be insisted. I wish the Component and VNode could be separate, the interactive API between them could be explicit and minimal.

If be more radical, the vdom (or VNode) may not be needed for native-rendering and server-side-rendering scenarios, at least it should not be handled by javascript. For Weex, it’s feasible to implement the vdom (VNode) natively by C++, and expose the corresponding APIs to the running context. Moreover, the create/diff process of vdom can also be compiled to WebAssembly, although it may not certainly improve the performance since WebAssembly can’t assess DOM API yet, it can be used to generate HTML strings in the server side. However, if component and vnode have so many coupled properties or features, it would be very hard to make the rendering process to take advantage of native platform abilities.

So, I think separate template, component, and vdom is good for long-term evolving, even if it hurts.

I share the same concern with @Justineo . Other than ariia/itemprop like attributes, class is a common usage of prop falling. Migration tool might help but code modification seems to be unavoidable and tedious.

why we need the pure props?

@Kingwl I guess it might be used to differentiate between class component or Vue.extend from pure functional component. pure seems to be optimization hint. https://github.com/vuejs/vue-next/blob/8de1c484ff2c9bab81f1a93fcb58f53859ff0227/packages/core/src/createRenderer.ts#L558-L560

Filters are gone for good (or can it?)

a nice sugar for template

Oh man, I can still recall the chaos it created when the same proposal was raised for v2. Too bad it’s been a while and pipe operator is still not a thing yet. FWIW, all the arguments (for both sides) in the linked issue should still be valid.

Edit: Yes, they can be gone now that users are more used to a v2 world with limited support for filters 😃

@yyx990803 As an aside, would it be possible to see a kitchen sink example of the proposed class-based component API?

And as Evan mentioned, people would never be able to rely on the behavior, since any component could return multiple root nodes. This means for 3rd party components, changing the template or render function in a way that does not affect the public interface or behavior would still result in breaking changes for users, because we’d have allowed users to reach in and bypass the public interface.

Deprecating nativeOn (and maybe inheritAttrs altogether?)

@yyx990803 I worry that nativeOn is actually an anti-pattern though. I personally teach people to never use it, since you can accidentally break any component by changing its root node. And unlike attributes passed to a component, it actually requires a refactor to remove the .native in the parent after using v-bind="$attrs"/{...this.$attrs} and inheritAttrs: false in the parent.

In general, maybe it would be best to force the explicitness, rather than having parent and child components coupled by default, with the parent making assumptions about the root node of the child? When we first shipped Vue 2.0, it wasn’t easy to choose an element to pass all attrs/listeners to, which is why implicit attribute passing and .native were useful. Now we’ve solved that problem and moving to explicitness, even when you want to pass to the root node, is a really quick refactor, so I feel like those features are no longer necessary.

Btw @octref - I’d like the 3.0 compiler to provide infrastructure for even better IDE support - e.g. template type checking / type-aware completions. Maybe even keep the language service in the repo.

How do we make them works with types?

I wrote a little helper for that, could probably be included with vue depending on what the API ends up looking like.

export default mixins(A, B, C).extend({...})
// OR
export default class MyComponent extends mixins(A, B, C) {...}

As this thread has become super long and hard to navigate, let’s open separate issues to discuss specific topics.

I’m not sure if there will be such use cases that the author want to hand-pick all allowed attributes and declare them inside props so nothing in $attrs will be touched. Technically it is a valid usage but users may come across the warning.

@Justineo Beyond the unnecessary clutter this would add to the component definition (especially for the minimum 21 aria attributes every element must accept), wouldn’t this also prevent unpredictable attributes from being used (e.g. data-testid for e2e tests)? For that reason, wouldn’t hand-picking attributes always be an anti-pattern?

Now that I think about it, it is indeed going to be problematic if we keep the implicit class/style fallthrough behavior. Most of the time, we can class/style to style the child component assuming it has a single root node AND that the class/style is going to be merged onto the root node. However, it’s possible that:

  • The component returns a fragment (may or may not spreading $attrs on one of the nodes in the returned fragment)
  • The component uses inheritAttrs: false and spreads $attrs on a non-root node (e.g. <input> inside <label>)

In either case above, the layout is not going to work as intended. The only way to guarantee desired styling behavior is to always wrap the child component in a <div> or <span> before styling it.

Incidentally, this also avoids the problem of the parent accidentally passing down a class that is used inside the child’s scoped styles.

@chrisvfritz, we already have a runtime merge helper, but the object that needs to be passed to spread has to be structured like this:

{
  attrs: { id: 'foo' },
  domProps: { innerHTML: '' },
  on: { click: foo },
  key: 'foo',
  ref: 'bar'
}

With flat data, users can spread object structured just like normal attributes they write.

Looks like I’m the only one super excited about flat data format for VNodes. There’s always a lot of people having trouble with JSX spreading and this is a great quality-of-life improvement for JSX users.

However, some library authors in the community will inevitably want to take advantage of stage-x features, thus encouraging their users to, and sometimes they’ll be so useful that they eventually become widespread enough that most apps just break sometimes, when part of the spec changes.

Honestly, it’s like throwing the baby out with the water if we don’t use classes because of this. It’s a valid concern, but only for the now. Note 3.0 is designed for the future where these proposals are likely more stable than they are now and breaking changes becoming less frequent (not like they are actually frequent now). What’s more important is we make it clear in our docs that we are aware of these stage-x features that can be used, but there’s risk and the user should be responsible for opting into such risk.

And even if we only use features in native class, when many users pull themselves back to the Babel implementation to get newer stage-x features, we end up with 3 different implementations of class (the Babel one, the native one, and the TypeScript one) being used in the wild, with many, subtle or not-so-subtle behavior differences.

Unless the user wants to support IE11, their Babel / TS setup will emit native classes so this is not really a problem. Also see implementation notes below.

And finally a (perhaps unjustified) fear. The ES class implementation is particlarly hacky within engines and a relatively new feature, so I worry there might be many subtle behavior differences and optimization choices between browser engines that we’ll keep discovering and have to deal with.

I think this is unfounded. Classes isn’t really that new - earliest browser support shipped in mid 2015 (Edge 12) and it became available in stable versions of all major evergreen browsers in March 2016. So that’s two and half years since they’ve been supported in these major browsers and I can’t really recall any “subtle behavior differences” that would affect us. In fact, our implementation doesn’t even care if it’s a native class or not. The code treats a component as a good old constructor function with a prototype and can be newed.

@chrisvfritz

High level notes: anything not specifically mentioned is in principle unchanged.

Handling multiple listeners to the same event

h('button', {
  onClick: [handlerA, handlerB]
})

Also when you cloneVNode(vnode, { onClick: foo }), the listeners is merged with existing ones instead of overriding. Same for nativeOn. A component that does not want nativeOn to be placed on its root node should specify inheritAttrs: false and then spread $attrs on to desired root node, which includes all nativeOn listeners.

Events Interpretation

With the flat structure, does this mean any prop/attr that starts with on will be interpreted as an event?

Inside render functions, yes

So in a template, does this mean @click="foo" would do the same thing as on-click="foo"/onClick="foo"?

No. Template syntax is irrelevant and does not change.

Emitting Events

With listeners being props, does this mean a listener would have to be declared as a prop to be used?

No.

Would we still need $emit or would we just call the function passed to the prop?

You can still use $emit.

Functional Syntax

I’m wondering if we should keep everything in an object in the 2nd argument, as we do now, so that users don’t have to worry about argument order and can just pull out what they need with destructuring.

Yeah, probably good for normal render fns too.

Async Components

Does that mean we’d have this kind of API for advanced async components?

Yes (slightly different), see implementation

resolveComponent

So this means globally registered components have no effect on render functions?

No. resolveComponent still checks for globally registered components. The change simply moves the part that is coupled to this out of the vdom implementation itself (into user render functions) so that VNodes can be context-free.

If we do need resolveComponent though, should it actually be asynchronous in case the component being resolved is async?

Async components are created inside a wrapper HOC so there’s no need for async resolving.

class

Class will be the new recommended API for any setup. It’s designed specifically to be usable in native ES2015 environments without a build step AND without any reliance on stage-x features. The reason is because it serves well consistently for all major setup types:

  1. Plain ES2015 without build step / stage-x features: that’s the default.

  2. Babel: same API, but can optionally use class fields or (new) decorators. Stage-x features may change, so we don’t encourage it, but you also don’t want to prevent users from willingly opt-in.

  3. TS: same API + optional class fields / decorators usage + type inference (important).

Right now, projects not using TS and projects using TS look completely different, and with TS becoming more prevalent, it’s going to make it difficult for TS and non-TS users to cross-contribute.

That said, object syntax will still be supported and the user will never have to use classes. (And the compat code is quite simple. Right now in 2.x, what we are doing is actually converting an object component into a constructor internally. 3.0 simply exposes the ability to directly author this constructor using classes.

Concerns about class components being the default

Would class components be the recommended default for build-step apps? Would there be any cases where users would have to use a class component, instead of the object syntax? If the answer to any of these is yes, I have strong reservations.

Since many features of class are still in flux in the spec, we’re opening up the possibility for users to take advantage of these stage-x features, even if we don’t rely on them ourselves. For example, React has faced issues on several occasions when something changes in the spec, and so changes in Babel, and everyone’s apps are suddenly broken. In the next version of React, I’ve even heard from Dan and Andrew that they’re moving completely away from class components, partly due to these chronic problems (and the awkwardness of JavaScript’s classes in many cases).

I also have some other thoughts on the many advantages of the object syntax, particularly from a learning and organization perspective, but I’ll hold those for now. 🙂

@prop bar: number = 'baz'

TS2322: Type '"baz"' is not assignable to type 'number'.

@DanielRosenwasser it’s definitely not complete yet.

  1. In a functional component there’s no this, so the only way to access its props is via the argument.

  2. Yes.

  3. No, props on this are readonly (both type-wise and implementation-wise).

Here’s how a user would specify props types:

interface Data {
  foo: string
}

interface Props {
  bar: number
}

class Foo extends Component<Data, Props> {
  static options = {
    props: {
      bar: { type: Number, default: 123 }
    }
  }

  data () {
    return {
      foo: 'hello' // will be an error if type doesn't match interface
    }
  }

  render (props) {
    // accessing data
    this.foo
    this.$data.foo

    // accessing props
    this.bar
    props.bar
    this.$props.bar
  }
}

A few obvious things to improve here:

  1. If the user provided a Props interface but didn’t specify the static props options, the props will still be merged onto this in types but not in implementation.

  2. User has to specify the Props interface for compile-time type checking AND the props options for runtime type checking. It’d be nice if the compile-time types can be inferred from the props options like it does for 2.x, although I haven’t figured out how to do that yet.

Note the reason we are designing it like this is because we want to make plain ES usage and TS usage as close as possible. As you can see, an ES2015 version of the above would simply be removing the interfaces and replacing the static options with an assignment.

If we can get a decorator implementation that matches the new stage-2/3 proposal, we can provide decorators that make it simpler:

class Foo extends Component {
  @data foo: string = 'hello'
  @prop bar: number = 123
}

@KaelWD that mixins helper is great, I was actually still thinking about how to deal with it 😂

VNodes are now context-free

I guess this would affect devtools. /cc @Akryum