-
Notifications
You must be signed in to change notification settings - Fork 546
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
Function-based Component API #42
Conversation
While I was initially disappointed about the class API being dropped, I’m now convinced that it was the right decision - this proposal is far superior. I can see how this will help to more easily break up component logic in a very typescript friendly manner with a clean and beautiful API, while avoiding annoying caveats that exist with React Hooks. Will solve so many issues we’ve had with “monster” components and the difficulty of sharing stateful logic. Bravo Evan and team. Edit: I've written up some thoughts expanding on why I think the new syntax is great. |
Like |
What about the In #17 (comment) the render({ state, props, slots }) {
// `this` points to the render context and works same as before (exposes everything)
// `state` exposes bindings returned from `setup()` (with value wrappers unwrapped)
} While in the old https://github.com/vuejs/rfcs/blob/render-fn-api-change/active-rfcs/0000-render-function-api-change.md the render(
// declared props
props,
// resolved slots
slots,
// fallthrough attributes
attrs,
// the raw vnode in parent scope representing this component
vnode
) {
} Which will be the final design? |
onUpdated(() => { | ||
console.log('updated!') | ||
}) | ||
onUnmounted(() => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the renaming from destroy to unmount verb intentional?
Or is the mount/unmount pair analogous to create/destroy pair?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, destroy
-> unmount
is intentional.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All current lifecycle hooks will have an equivalent onXXX function that can be used inside setup() stated here
Since there will be a renaming, I'd recommend listing all available lifecycle hook functions in the proposal explicitly.
Is What's more, in the old |
I think |
This comment has been minimized.
This comment has been minimized.
On type inference: how to share type information among React hooks, despite its all weirdness and performance penalties, helps put all component logics in one single function and are therefore naturally type-friendly. Our function-based API, on the other hand, divides component logics into two functions, |
Or perhaps we need a wrapper like |
We do need a wrapper function playground link Some typescript-foo can bring us full type safety. |
This RFC looks really promising. Thank you Vue team for struggling so hard to achieve a monsterkill design. It looks really polished and clean. Can't wait to play with it! |
This is so awesome and I wouldn't have known how awesome 3.0's new API would be, if it weren't for this great write-up (it reads like future documentation), which puts the other RFCs into a much better context Evan. Great job! I love the fact you are sticking to JavaScript's roots and still accommodating the TS crowd (to a point). That is a tight-rope act and you are handling it amazingly well. It shows your not just a great developer, but a great leader too! 🥇 💯 👍 💟 Scott |
@beeplin yes, you need to put the object in a function to get inferred typing for arguments passed to |
Love this! I cannot wait for it to be available to play around with, thank you and the Vue team for all your hard work on these RFCs. I do have a question on I ask as I found it confusing using Strings as provider keys. There is the potential problem of conflicting identifiers available for injection. Seeing this Symbol example makes it a lot more clearer. :) Is it worth only allowing passing Symbols? (If out of scope of this discussion, feel free to gloss over this!) Again, amazing work and can't wait! |
@HerringtonDarkholme Regarding TypeScript. Will there be a way to define props interface (like in the example below), because the one thing I've always missed in vue is the ability to define the shape of an object prop.. interface UserCardProps {
user: {
firstName: string;
lastName: string;
email: string;
}
}
const UserCard = createComponent<UserCardProps>({
props: {
user: Object
}
}) Also what about case when prop has a default value: props: {
count: {
type: Number,
default: 0
}
} Your example seems broken currently |
In current Consider a list component which fetches a user's articles. We probably will write some code like
The example code now is simple. But it already duplicates logic: the dependency array reflects reactive variables used in watch handler. And later we might add more variables like filter or sort, making dependency array unwieldy. Also, data fetching usually won't need to know previous value. Since we have already tracked reactive variable usage. It is nice to have a watcher sensitive to implicit dependencies. |
I like the new API, I'm excited about it, but I'm worried about the depreciation of some of the 2.x props. The new API, though extremely powerful, it has a steeper learning curve than 2.x. I believe beginners will have a harder time getting started, robbing Vue of one of his bigger strengths. Wouldn't it be better to keep all of the 2.x options and opt-in to have them removed if you choose to? And yes, I'm aware the 2.x will still be around on a compatibility mode, but it will eventually go away and my argument will hold then. |
@tochomero I think you misunderstand. There will be a compatibility build of Vue 3, which will contain all of the deprecated features. You can optionaly use the smaller build where these features are already removed. So users do have the choice that you are asking for, as far as I can see. |
@LinusBorg but eventually the deprecated properties will go away, that is what deprecated means, doesn't it? I guess what I'm asking is to keep them around for the foreseeable future. And maybe that is the plan already, I was just tipped off by the |
Will we be able to inspect components created by the Function-based Component API using the Vue Devtools? I specifically ask about the component properties (state, computed, etc). |
@skyrpex Yes as long as you expose them by returning them in |
Ah yes, of course. Not sure what I was thinking 🤔 |
@Akryum I know people were quite upset about the whole lean/standard build thing, but I think there are a few lingering questions related to the bundle. If we convert all our components to the composable function API, will there be some sort of shrink in bundle size? What if we use none of the composable functions, will they be tree-shaken out? I'm curious about how these new changes will affect the final size of Vue itself. If this info isn't clear yet, I understand. All in all, great work everyone! I'm excited to start using the new API. |
I strongly want to avoid having value() and .value all throughout my code, as I like to have functions/classes in files that can be dropped into another javascript project and reused. I'd like to be able to wrap them to vue-ify them, and drop them into the setup function. So I can do: { count, name } = state({ count: 0, name: { first: 'Bob'', last: 'Jones'} }) Sorry, I'm no expert, haven't used the observable API. I've used the class component plugin with this kind of plug and play, and I think this RFC looks very good for similar benefits except for the dependence on importing 'value from vue' into lots of different places. |
I think that const data = state({ count: 0, name: { first: 'Bob'', last: 'Jones'} });
data.count++; // works
data.name.first = 'Mike'; // works as well. Which isn't that bad :) |
@milky2028 I'm curious about the exact numbers as well, but definitely there'll be some sort of shrink in bundle size. All the variable names that you destructure ( For example, the following code:
Can be mangled into this: const b = require("vue"), v = b.value, c = b.computed;
module.exports = {
setup() {
const f = v("");
const l = v("");
const n = c(() => `${f.value} ${l.value}`);
return {
fullName: n,
};
},
}; |
I just wonder why |
It's short, it has meaning (create a value), and it's generic. I personally don't think |
@skyrpex Ahhh, this makes sense. I know a big reason React was pushing for hooks is because class methods can't be minified. I didn't realize the same applied for class properties. Thanks! |
Thank you, helpful. Not bad at all, that works. I'll definitely use data.count over count.value most of the time. I guess it wasn't mentioned a lot in this writeup becaus ethe observable api is old news to you guys and the RFC is about the new setup function. When I install the plugin for this functional API RFC, do I get the rename to state, or do I continue using observable until 3.0 comes out?
One thing I noticed in this writeup is I was getting confused by the 'value' in the watch method, until I re-read and saw that it was a reference to the output of the first parameter. The name was definitely peppered in examples throughout, mixed with the wrapper method being discussed. |
The plugin uses this RFC's original naming conventions. So Scott |
Recently I wrap vue2 to vue3's function APIs, and write some applications. I realize that if one can provide // A has type of Component<PropsA, { inc(a: number): void; count: number; }>
const A = createComponent((props: PropsA) => {
const count = value(0)
const inc = (a: number) => { stateA.value += a }
return {
inc,
count,
// render is reserved to render
render(props) { // props' type can be inferred from PropsA
return <div>{props.start + count.value}</div>
},
}
})
const B = createComponent(() => {
const root = ref<HTMLElement>()
const a = ref<typeof A>() // can be inferred a's exposed props and methods from A.
onMounted(() => {
a.value.inc(10)
})
// we can use `ref={a}` to inject A's instance to a.
return () => (
<div ref={root}><A ref={a} start={0} /></div>
)
}) |
The current proposal does not show how to handle default values for injections. I would assume that it would just be the second argument to const Consumer = createComponent({
setup() {
const count = inject(CountSymbol, 42) // count's type is Value<number>
console.log(count.value) // 42 if no provide is found
return {
count
}
}
}) But for clarity's sake it should probably be mentioned in the proposal? |
@TerenceZ i like this idea. only one thing: i would use a symbol for the render method, just to leave the namespace untouched |
Intelligibility and simplicity are my favorite part of Vue, and it's how I sold my team on Vue 3 years ago when the majority weren't initially on board. This RFC introduces a few concerns for me:
Having said this, I'm not opposed to this RFC, and I think there's plenty of room for improvement in Vue. My hope is that some of the simplicity and readability could be restored. (Yes, I know some feel the new way is neater, but I am politely disagreeing for the reasons above which I think are objective enough) Simplicity is everything and it can't be a 2nd class citizen. It's the first thing anyone notices is missing when choosing a framework. |
Thanks everyone for your feedback here! This RFC has gone through significant iteration, including an important change in direction regarding the purpose of the proposed API and its role in the Vue ecosystem. Most notably, it originally mentioned possible deprecations which are no longer being considered, based on your feedback. Due to all these changes, navigating the discussion has become difficult and confusing, particularly for newcomers. To make it easier, we're closing this PR and migrating to an updated RFC that includes all the latest technical changes, and also better communicates the updated role we now expect the API to have in most users' day-to-day work. Please continue your feedback and questions there. 🙂 |
This comment has been minimized.
This comment has been minimized.
The composition API is a really awesome thing 😉 It takes a bit of getting used to but, it makes so much sense it’s almost strange you guys did not come up with this before :-) |
It's an interesting api. I just don't like having to export all the variables you declared at the end of I don't exactly have a solution for it, but as a user the idea of defining a variable and then having to export it somewhere else seems like housekeeping that will slow me down and introduce bugs as I add and remove variables. In other words, I think we should figure out a way to be DRY. |
What's wrong with vue-class-component decorator? It allows grouping data with their respective methods in logical sections, it also enforces types, and it avoids the issue @turbobuilt mentioned regarding forgetting to export variables. |
A proposal that consolidates upon #22 (Advanced Reactivity API), #23 (Dynamic Lifecycle Injection) and the discontinuation of #17 (Class API).
Full Rendered Proposal
High-level Q&A
Is this like Python 3 / Angular 2 / Do I have to rewrite all my code?
No. The new API is 100% compatible with current syntax and purely additive. All new additions are contained within the new
setup()
function. Nothing is being removed or deprecated (in this RFC), nor do we have plan to remove / deprecate anything in the foreseeable future. (A previous draft of this RFC indicated that there is the possibility of deprecating a number of 2.x options in a future major release, which has been redacted based on user feedback.)Details
Is this set in stone?
No. This is an RFC (Request for Comments) - as long as this pull request is still open, this is just a proposal for soliciting feedback. We encourage you to voice your opinion, but please actually read the RFC itself before commenting, as the information you got from a random Reddit/HN thread can be incomplete, outdated or outright misleading.
Vue is all about simplicity and this RFC is not.
RFCs are written for implementors and advanced users who are aware of the internal design constraints of the framework. It focuses on the technical details, and has to be extremely thorough and cover all possible edge cases, which is why it may seem complex at first glance.
We will provide tutorials targeting normal users which will be much easier to follow along with. In the meanwhile, check out some examples to see if the new API really makes things more complex.
I don't see what problems this proposal solves.
Please read this reply.
This will lead to spaghetti code and is much harder to read.
Please read this section and this reply.
The Class API is much better!
We respectfully disagree.
This RFC also provides strictly superior logic composition and better type inference than the Class API. As it stands, the only "advantage" the Class API has is familiarity - and we don't believe it's enough to outweigh the benefits this RFC provides over it.
This looks like React, why don't I just use React?
First, the template syntax doesn't change, and you are not forced to use this API for your
<script>
sections at all.Second, if you use React, you'll most likely be using React Hooks. This API is certainly inspired by React hooks, but it works fundamentally differently and is rooted in Vue's very own reactivity system. In addition, we believe this API addresses a number of important usability issues in React Hooks. If you cannot put up with this API, you will most likely dislike React Hooks even more.