-
-
Notifications
You must be signed in to change notification settings - Fork 8.3k
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
Initial proposal of breaking changes in 3.0 #2
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. |
Hi Evan, Nice work! Here are some of my early thoughts: Props
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 VNodesVNodes are now context-freeThis 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
Does this mean we no longer allow kebab-casing for Vue components in templates?
Just skip step 1 for compiler intuitions seems fine? SlotsUnifying Normnal Slots and Scoped SlotsGreat. 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
Does this only affects Attributes
Do we still have predefined boolean attributes (true boolean attrs like |
You could probably use
I think we use this in a few places too. |
What I get here is: |
I guess this would affect devtools. /cc @Akryum |
Maybe we can add type to vnode so that it's easy do the check |
seems friendly to tsx, should we add event(emit) declaration into component?
why we need the
a nice sugar for template |
I think it's to identify functional components from components created with |
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 :) |
What would be the prop merging behavior for |
I share the same concern with @Justineo . Other than ariia/itemprop like attributes,
@Kingwl |
@phanan oh, i forget the pipeline operator syntax 🤣 |
I also share some of the concerns @Justineo has.
Without
This seems to be a fairly common use case in our projects as well.
I personally have never used filters in my projects since Vue 2.0, so for me removing it doesn't hurt. |
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.
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. |
This looks a amazing, I finally feel like this is something I can navigate and undestand :-P Points I want to comment on: PropsLike 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? FiltersI'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.
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. SlotsAwesome, 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?) VnodesThe new API seems great, slim and easy to parse. and I see how 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 MixinsThis 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 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 :D :D |
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) {...} |
The only thing affecting Vetur is removal of filter. This is great because I can treat interpolations as JS statements without any custom syntax handling. But also +1 to what @LinusBorg said:
I might be able to provide editor support that:
|
Answering a few concerns: $attrsFirst, Second, I think the ability to render arbitrary attributes on the root of a child component is a useful one, but currently it's a very implicit behavior. The problem I see with the implicit fall-through is that you read the code of a component being passed props, you won't know which ones will be treated as props and which ones will be treated as attributes without knowing what props the component declares: <!-- is label a prop or an attribute? -->
<Foo label="123" /> In addition, because props declarations are now optional, we actually don't have a way to implicitly extract attributes for component that does not declare props. Maybe we can differentiate the two (component props vs. native attributes) similar to how we differentiate component events vs. native events with the <!-- this is always a prop, although the component *may* explicitly render it as an attribute, there's no guarantee -->
<Foo label="123" />
<!-- this is always an attribute on the component root -->
<Foo label.attr="123" /> In the compiled code, props with h(Comp, {
// explicitly merged on to child component root, like `class` and `style`.
attrs: { /* ...* / }
}) Then, we need to consider the case where the component may return a Fragment, or may have a wrapper element and want to take full control of the full-through. In such case, Or, maybe I'm overthinking all this and implicit fall-through is fine (and actually useful). Although, note that the following are orthogonal to whether we keep implicit fall-through or not:
The only difference is that with implicit fall-through, any props not declared (plus ones with Thoughts? Accessing component instance in directivesThis is still possible. The vnodes are context-free, but directives are applied in render functions with the component Also re @Justineo : the directive change only affects render functions. Template syntax remains the same.
|
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. |
@KaelWD that mixins helper is great, I was actually still thinking about how to deal with it 😂 |
Adding |
That's great. I'll take a look of the parser / compiler and let you know what change would it take. If we have Error Tolerance in the core parser I should be able to use it in Vetur. |
I think it's implemention detail and should be encapsulated by component authors. As I understand, the separation of content attributes (attrs) and IDL attributes (props) only makes sense for native elements, as we can only specify string values in HTML. While Vue templates can specify any data type with
Actually because props declarations are optional so we cannot tell the true semantics of a prop. It feels like you define a function without defining parameter types, but pass in types at each time you call a function and this is kinda weird. I'd rather explicitly define props by component authors instead of let users dig into details. |
Will there be an easier way for devtools to access to functional components if we cannot access context of vnodes anymore? |
Update: attribute fallthrough will be preserved when the component has declared props (the behavior will be the same as before). In addition, all parent |
Thanks for inviting me to this early (and exciting) preview of Vue 3.0! VNodes Does the change in VNodes affect the template compiler modules? In NativeScript-Vue we have some syntactic sugar that is handled by the template compiler through modules, I'm guessing these will need to be updated (not a deal breaker, just wondering) Component in Render Functions
This is not a deal breaker, but currently we use
Does uppercase mean Slots
Will the template syntax for scoped slots change due to this, or is this just an implementation detail/render function specific change? Filters I'm not attached to them, but I agree with @LinusBorg about the potential risks of name conflicts. I haven't had the time to dig through the codebase entirely but just glancing at some parts of it, I'm really liking the new structure, seems a lot easier to follow / contribute to! |
How about |
I've read the source but I'm not sure if things are complete yet, but here's my understanding of that:
Is that correct? If not, can you give an example of the two scenarios in action just so I can get an idea of what the workflow is? |
@DanielRosenwasser it's definitely not complete yet.
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:
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
} |
|
Could we define events like we do for props? They would be omitted from |
IMO component consumers will expect the fall-through behavior. The real difference is whether component author have to spread If we prevent the fall-through behavior by default, I will expect there will be less 3rd party libs which allow passing arbitrary attributes to DOM, which may have an impact on a11y and other aspects I mentioned in my initial comment. If component authors will need to spread |
@Justineo I agree with your concerns, but I feel like this is an education issue we can solve with documentation, encouraging people to always define a pass-through with |
I think it's an education issue in either way. We can also educate users that if you don't explicitly take over And the current behavior only fails when component authors didn't do anything to redirect I agree that making small changes to components are trivial. But if we are using a 3rd party component lib we have no guarantee whether/when the PR is gonna be merged and published in a new version, while currently it doesn't require users to ask for permission to spread |
It's not just React users, unfortunately. Probably most new Vue developers I work with are also surprised by this behavior.
I'd rather have something work or not work consistently, rather than seeming to work sometimes while very likely to break at any time, even in a patch release, because library authors will accidentally make a breaking change with a different root node or making the root node a fragment, both of which will be common (I know I've been affected by the former with Vue 2). Until the library moves to explicitness, you're essentially using an unstable API.
If adding |
@yyx990803 As an aside, would it be possible to see a kitchen sink example of the proposed class-based component API? |
@chrisvfritz created an issue for that: #4 |
I'd prefer a consistent behavior as I don't want to jump into component definition to check if a custom prop can be added or not. It's better to never put extra prop/attrs unless component API/docs explicitly specifies so. |
@znck For the reasons @Justineo mentioned, I do think there should be an expectation that unless a component renders |
I have some different thoughts. 😅 First it's great to support fragment and portal. But at the same time, we break up, at least, weaken the "future ready" feature that Vue components could be built into native web components. Because in 3.0, we are not sure a Vue component has a root DOM element. If we couldn't get a way to build fragment/portal component into native web components someway, I would feel like we leave future web standard a little more far away. Second, In web platform, I suggest to make global html attributes like And a small question: I'm not sure what the render functions will be if I write more than 2 custom directives like Thanks. |
Native web components always have its own custom element / shadow root that can serve as the root element, so I don't really think that's a problem.
With the introduction of fragments, we can no longer rely on implicit behavior. I think the solution is making it a best practice to always spread
It will look something like this: const comp = resolveComponent(this, 'comp')
const foo = resolveDirective(this, 'foo')
const bar = resolveDirective(this, 'bar')
return applyDirectives(
h(comp),
this,
[foo, this.x],
[bar, this.y]
) |
I guess there would be quite amount of mis-operations when:
Maybe it's better to make Btw, till now, I sometimes mis-wrote Thanks. |
The best/common practice would be always merging
This we can actually detect and throw a warning for (if |
What if this is intentional and all allowed attributes are declared in props instead? |
@Jinjiang I really like the idea of a warning for users, but with any implicit pass-through, I really think we're only delaying pain by allowing users to rely on an unstable API that the component author didn't explicitly define.
@Jinjiang Me too. 😅 But if we remove all implicit pass-through, that problem is solved. 😉
@Justineo Since there will be valid use cases for not binding Component <Foo> was passed attribute "bar", but "bar" was not used. If this is not a typo and the component renders its own markup, it's recommended that the component author spreads arbitrary attributes to some element using v-bind="$attrs" in a template, or {...this.$attrs} in JSX. What do you think? |
@Justineo Also, I think null and children renders are the only cases where intentionally not spreading |
@yyx990803 And actually, is there potentially a way we could detect if a component has generated its own markup, rather than rendering Component <Foo> was passed attribute "bar", but "bar" was not used. Since this component generates its own markup, it's recommended that the component author spreads arbitrary attributes to some element using v-bind="$attrs" in a template, or {...this.$attrs} in JSX. And if the component doesn't generate its own markup: Component <Foo> was passed attribute "bar", but "bar" is not a prop. The valid props are: What do you think? |
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 |
@Justineo Beyond the unnecessary clutter this would add to the component definition (especially for the minimum 21 |
Yes. I'm just saying it's valid in the current mechanism, despite being an anti-pattern. On a second thought it may be good enough to suggest them spread Another issue might be whether we suggest authors to always spread the |
That would be my preference, as I'm still not convinced there's a valid use case for whitelisting. I feel like there can always be exceptions where an attribute beyond
If users really need to do attribute whitelisting (or blacklisting), they technically already can, e.g. with: pickBy(
this.$attrs,
(value, key) =>
/^(data|aria)[A-Z]/.test(key) ||
['role'].includes(key)
) And this would still trigger the getter for |
As this thread has become super long and hard to navigate, let's open separate issues to discuss specific topics. |
Open separate issue for attrs fallthrough behavior: #5 |
Closing in favor of public RFCs. |
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
andnativeOn*
) will be available inthis.$props
and also as the first argument of the render function. This eliminates the need forthis.$attrs
andthis.$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 theprops
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 tofalse
. The component will be responsible for merging the props as attrs onto the desired element.VNodes
Flat Data Format
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 accessvnode.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 toh
.In templates, components should be uppercase to differentiate from normal elements.
NOTE: how to tell in browser templates? In compiler, use the following intuitions:
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
Functional Component
Functional components can now really be just functions.
Async Component
Async components now must be explicitly created.
Directives
Now are internally on-vnode hooks with the exact same lifecycle as components.
Custom directives are now applied via a helper:
Styles
No longer performs auto-prefixing.
Attributes
false
. Instead, it's set asattr="false"
instead. To remove the attribute, usenull
.Filters
Filters are gone for good (or can it?)
Refs
v-for
. Instead, use something like:ref="'foo' + key"
or function refs.The text was updated successfully, but these errors were encountered: