-
Notifications
You must be signed in to change notification settings - Fork 545
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
New script setup and ref sugar #222
Conversation
Just an opinion: Really don't like the idea. Too much magic going on. And no longer javascript. (sorry for being not helpful as a comment) |
@ycmjason that's expected, but we feel it's worth putting it out here. I know this is probably going to be a controversial one, so for anyone else commenting:
|
Sorry if I missed it, but does the compiler automatically register all imported Specifically, I'm curious how the compiler discerns what's a Vue component and what's not. For example, if I import a I think there could also be a possibility that we wouldn't want the imported component to be registered, for example, if we're applying a HOC to it. If we're adding custom syntax, could the ES 2021 proposed "export default from" syntax be implemented"? It could be just as concise but more explicit: export { default as Foo } from './Foo.vue'; |
I would prefer svelte's |
@privatenumber the component is exposed, but via the setup context. The template compiler knows that the component is available based on the bindings information extracted from the script compilation process. I think importing another component into an SFC and apply the HOC wrapping there is... very rare. In such cases you can always use a separate, normal |
One thing that I really enjoy about the composition api is how it invites modularisation in a way mixins never did. |
How about the combination of $: let foo = 1 // ref
$: const bar = foo + 1 // computed
$: let baz = computed(() => bar + 1) transform to(just for IDE):const $foo = ref(1)
let foo = unref($foo)
const $bar = computed(() => foo + 1)
const bar = unref($bar)
const $baz = ref(computed(() => bar + 1))
let baz = unref($baz) UpdateIt seems that everyone does not want to break away from the semantics of js, of course we can have a way based entirely on js semantics, but is this what we want? import { noValueRef, noValueComputed } from 'vue'
let foo = noValueRef(1) // type: number & { raw: Ref<number> }
const bar = noValueComputed(() => foo + 1) // type: number & { raw: ComputedRef<number> }
useNum(bar.raw)
function useNum(num: Ref<number>) compile to: import { ref, computed } from 'vue'
let foo = ref(1)
const bar = computed(() => foo.value + 1)
useNum(bar)
function useNum(num: Ref<number>) yes: 😄 |
A drawback of this RFC, which was also a drawback of In Vue 2, you only the had the Options API, and eventually we got the class component plugin. With this RFC, you'd have the following options:
This fragments the userbase a lot, making it harder to get started, to search for help on SO and to pick which one you should use. It'd be much easier if you're just starting out with Vue that you know: "Options API for the magic reactivity, composition API for more standard JS, better compositions and better TS support". However: it is clear that people like this "magic" VCA syntax (see the linked RFCS). Isn't it possible to extend the custom block API a bit more so that if people really want this syntax, it can also be achieved outside of Vue core, in a (third) party library? If this lands in Vue core it would need to be supported throughout future versions of Vue, so I'm not sure if you'd even want that, considering the very valid points already mentioned and the fact that you'd need to support many APIs. Going into the API itself a bit: I think the To conclude; why don't we just say to the users that don't like the verbosity of the composition API: use the Options API? |
@RobbinBaauw Just a supplement, if this feature is a third-party, should be considered use comment-based syntax, because IDE will not additionally support third-party features, and comment-based syntax is the way that requires less IDE support. |
There's prior art for this, in both Svelte and Apple's Combine1. 1 ...though their usage is the exact inverse of each other. In Svelte the prefix-less variable holds the Store (their version of Ref), and with the prefix you get the raw value. In Combine the prefixed variable holds the Binding (their version of Ref), and the prefix-less version gets you the raw value. |
@axetroy while I'm also extremely sceptical about new syntax, I should note - this syntax is valid javascript. It uses javascript labels (lol, who remembers them?) but just gives them new semantics |
yeah, I reconfirmed it again, it is indeed legal Javascript syntax. Except in the For loop, it is too rare. If this RC is implemented, it means that the label statement has another meaning. I’m just saying that Vue has many paradigms. So far, none is the "best practice" style. This makes the project chaos, and I prefer to do subtraction rather than addition. |
This is vastly exaggerating.
To sum up - there are two "paradigms": (1) Options API and (2) Composition API.
So - you agree that many people want a ref syntax sugar, but then suggest not supporting it in core, and instead encouraging them to each implement their own. Isn't this going to only lead to more fragmentation? Think CSS-in-JS in the React ecosystem.
You literally summed it up in a single sentence. I can also make it even simpler:
What exact, concrete problems can this lead to? Forgetting to add the
You are asking users to give up the benefits of Composition API because you don't like a syntax sugar that makes Composition API less verbose for them. I don't think that really makes sense. |
I love the general But I'm not shure about the |
I can 100% understand the feeling when people see an unfamiliar semantics added on top of JavaScript. The feeling of "I'm no longer writing 100% JavaScript" just feels uncomfortable. In fact, I felt exactly the same way when I first saw JSX, TypeScript or Svelte 3! But if you come to think about it... both JSX and TypeScript are exactly "extension/superset on top of JavaScript", and at a much, much larger scale. Both were strongly disliked and resisted (by some) when they were initially introduced, but today both are widely adopted and dominant in the JS ecosystem. Decorators - never part of the spec, but is the foundation which Angular is built on top of. Svelte 3, while not as popular yet, also have attracted a fair share of highly devout users. So the point here is - sometimes we need to look past the "not JavaScript" knee jerk reaction and reason at a more pragmatic level. Instead of dismissing it outright, we should argue about the concrete gains and loss caused by such an addition. So, putting "not JavaScript" aside, the other downsides of the Also another note: this RFC contains two parts:
When providing feedback, please be specific about which of the above you are focusing on, just like @mathe42 did. |
What would we do for intermediate variables? const store = inject('my-store')
ref: stateICareAbout = computed(() => store.state.myState)
function addState(newState) {
store.add(newState)
} This would expose On the downside, it will make the generated code bigger as it makes the object returned by setup larger, and we loose a bit of "clarity" about what's being exposed to the template. On the upside, one could argue that explicitly defining what gets exposed to the templates is tedious, and this change would make SFCs act a bit more like JSX in that every(*) variable in (*) except imports of non-vue files, if I'm right. |
@LinusBorg yes, they are always exposed. Technically, if this is merged, we can also introduce a different template compilation mode where the render function is returned directly from setup. This makes the scoping even more direct and avoids the need of the render proxy. |
Will it work with typescript? ref: foo: number | string = 'bar'; Will the IDE autocomplete the name when i start typing Other thing is that i'm not sure if it will be easy enough to quickly determine whether variable is a normal js variable or a ref. Now when for example I type |
@jacekkarczmarczyk please do read the full RFC. TS support is discussed here and here. Also in the tooling section it's mentioned that Vetur can easily add different syntax highlighting and hover information for |
sorry, but it's awful. i dont want see svelte(invalid js) in vue. |
Maybe just parse ref(...) and generate variable.value automatically? Or i something miss there? |
@Asv1 parsing usage of Although, I would like to hear more about why the Svelte approach of making all let assignments reactive wouldn't work. If done just at the top level, and perhaps as shallowRef, I would think sugaring all let assignments could be a plausible alternative (perhaps with acceptable trade-offs in less common cases, and maybe an override mechanism via special comment). All variables are exposed to the template, and most of the time you want them to be reactive, so I think marking the places (via const and comments) where you don't want reactivity syntax sugar makes sense rather than the other way around using special labels for reactivity sugar. |
@aztalbot with the ref label there is syntax "there happens something (magic)" but with all let to ref there is no indication that something spezial happens. |
@mathe42 using script setup with this syntax sugaring enabled seems indication enough to me that magic processing will occur. That seems to be the user's expectation with svelte, for example. |
@aztalbot what happens in that case when you run for(let i=0;i<2;i++) {
setTimeout(()=>{console.log(i)})
}
console.log('end') In normal JS world it logs So the question is what does the example above compile to. |
@mathe42 I wouldn't expect that let binding to be sugared. That variable i is scoped to the for loop block. I would only expect top level let assignments in the main setup block to be sugared. That variable i for example wouldn't be exposed to the template. But you're right, there may be issues with that approach. I'm just curious to hear it discussed more before going with something like using labels. And to just say this: I think this RFC is a great improvement on the previous version of |
@aztalbot <script setup>
{
let count = 0;
}
console.log(count)
</script> will throw an error with "Uncaught ReferenceError: count is not defined". Will the next snippet also throw? <script setup>
{
ref: count = 0;
}
console.log(count)
</script> In wich scope the refs are created? ref: let count = 0;
ref: var count = 0;
// ref: const count = 0;
Also something like ref: let a = 6, b=7, c = 42 would be nice. [EDIT] {
a=5;
}
console.log(a) and this does not throw. |
How about this:// REF and COMPUTED are empty functions, just to tell the compiler where to do its tricks
import REF, COMPUTED from 'vue'
let a = REF(1)
let b = COMPUTED(a + 1) WHY:People hate writing Some people hate using So why not just use some empty functions to indicate the behavior of the compiler? WHY 'REF' and 'COMPUTED'?Vue already exports Of coz we could also use something like |
@beeplin I'd recommend reading #223, for a more thorough explanation of a similar idea. I like that proposal a lot, as it doesn't require any changes to IDEs and such, it's just something that works on the generated JS code. Generating a mock typescript file just for DX sounds questionable to me, given that:
Besides that, for me there is much less mental burdon compared to the labels and the |
Let me share my experience with this proposal. I would like in this comment to avoid any kind of assumptions (except very end of the comment, obviously), just stick to facts and measurable metrics. I understand that from scientific or pedagogical perspective this could be not be treated as good raw research data for many different reasons and do not position it in this way. My goal is to provide some feedback, because I often fail in estimating complexity of learning curves. Experiment detailsAudience40 backend engineers, 1 to 5 years of background in C#, 0 experience with React, Vue, Angular, Svelte or any other major framework, minor experience in maintaining JavaScript related to ASP.NET MVC Framework. 2 months of 10h/week JavaScript training focused on pure language without libraries, everyone in group capable of doing project similar to https://github.com/gothinkster/realworld in pure JavaScript without frameworks SetupAudience was split in two equal groups. Training (12 hours total, split in 4 parts) included:
Obviously, one group was taught using current composition API approach (with current experimental syntax for Students were not allowed to use eslint for this task - I've done this change intentionally, since Group 1 (current Top 5 student problems based on anonymous feedback after task
Group 2 (this proposal): averate time to complete task: 4h 18 minutes. Top 5 student problems based on anonymous feedback after task
Fun fact #1: I've received zero complains about having to write ConclusionBased on this data I would like to question statement that new proposed approach is mentally easier to understand. Private biased opinionI am really concerned with approach which relies on heavy tooling support. This usually means significant lag in adoption for JetBrains users (which are a big part of frontenders at least in our local community), big troubles for marginal users (like VIM users for examples) and so on. I do not feel that having |
I love the DX you get with But we still have the "problem" of the composition functions: we can avoid The other sticky point is to know when to pass the Now, hear me out, if we can use the new syntactic sugar everywhere, i.e. template, script, and composition functions, then there is no reason to have the compiler automatically pass the raw ref when we invoke a function and to return the raw ref from a composition function. It wouldn't matter we are always passing the raw ref because it will get automatically unwrapped anyway. This will also avoid the problem of having a composition function using Let's not forget we are already doing this in the As the last point, if we have a way to explicitly enable "auto-unwrap-refs |
当前探讨的提案,是为了让 function-api 的实际使用更完美;而 function-api 本身的发明,是为了更好的类型推导,和把相关功能书写在一起。 而 class-api 天生就是为解决这些问题存在的。 TypeScript(以及 ECMAScript class 语法)的发展非常迅猛,当初 class-api 可能确实有很多问题,但目前已经可以完美解决 function-api 所旨在解决(但顺带造成了新困难)的任何问题。 这里不是专门讨论 class-api 的场合,所以暂不细说,但是由于目前所面临的擦屁股的问题,实际上是这一根源性的道路选择所必然衍生的,因此适时重新考虑 class-api 的问题,或许恰逢其会。 This rfc wants function-api to be perfect; while the designing of function-api wanting perfect type notice, and writing related prop/data/computed/method together. But class-api is born for them. The development of TypeScript (and the class syntax of ECMAScript) is so quick, that some problems in early class-api, not exist any more, it could be real perfect. Here is not the place to discuss class-API specifically, so I won't go into detail right now, but since the current issue is actually a necessary derivation of the fundamental road choosing, it may be timely to reconsider the class-API, perhaps just in time. |
I understand what you are saying, and agree with props/events/slots. But the variables exposed to the template, most of the time, represent either text shown on screen, or are used to toggle the visibility of DOM elements. When dealing with UI components, this is a form of output, and in that sense, a public interface. Consider a component in which a button toggles the visibility of some other element. The vue test utils contains a Granted, you will still check for the actual DOM element using the other test utils functions, but if it is visible when it should not be, or vice versa, knowing the value of the variable bound to the v-if or v-show is vital, and part of that public interface in my mind. |
@xanf thank you so much for conducting this experiment - this is very insightful. I agree the study shows that mental overhead of
Nevertheless this is extremely useful and helpful information, and I greatly appreciate the effort. I'm very curious about the raw data collected (time used each individual so we can see the time distribution and correlation to their experience level, detailed feedback on each of the problems listed, and the actual code written for the task). Is it possible to share it in a repository somewhere (can be private)? I believe it will not just benefit this specific RFC but Composition API and our docs in general. |
I disagree for the most part. Yes, test-utils does allow that via In your example the best way to test your component would be to trigger a click on the button, and then check the HTML. Checking the visibility functionality by manually setting the data isn't testing the unit, it's testing the internals by doing things in a way that the component could not do its its own in a real app. Granted, there are edge cases that make this necessary or at least useful as a shortcut for an otherwise overly complicated test, but those are not common. |
I love this feature, reason: https://www.zhihu.com/question/429036806/answer/1566414257 |
I decided to make some comments here. If you are going to explore the way similar to svelte (better combination by means of compiler), I think you can do this. label syntax only works on compilation, not runtime. If it's just to simplify the APIs, there's no need. Most of the ideas listed in the RFC are not bad enough to make such a big sacrifice. It looks bad, but it's not bad enough to be unacceptable. JSX also wanted to add more compilation methods, but in the end they didn't. So my personal point of view is whether you want to get close to svelte in the future. Although this is only a sugar, but it determines many things after, please be careful. Svelte looks attractive, but not sure suitable for Vue. In the future, it may destroy the stability of the ecosystem. |
I'm with @mgdodge here. Front-end frameworks such as Vue are descendants of the MVVM pattern pioneered by WPF. A well coded VM contains only "business" logic and no reference to its UI. It is defined by what it provides to the view and is testable without the View. Calling the function directly and checking if the exported property value has changed as expected is the recommended way to unit test it. Putting the View into the mix makes things more complicated, slower and more fragile (template may change and break the test for unrelated reasons). Vue is flexible. There have been many variations of MV* since MVVM and you don't even have to use one if you don't want to. |
Sounds like a "composition function". extract it from setup, test it as a unit. Then call it in
Bikeshedding about how to properly unit tests aside, I don't see how this RFC makes what you describe any harder. |
The unit testing discussion can be tabled, it was more a justification for the idea that I prefer the concept of |
No. A "composition function" is meant for re-use and composition. <script>
import pageXY from "./pagexY"
return {
setup() {
return pageXY()
}
}
</script> Note that if I followed your advice and did this, I would loose all benefits of this RFC as they don't apply outside SFC (and it's one of the main drawbacks of this RFC).
Go back to @mgdodge's comment. |
Maybe this is nitpicking but I had a quicklook at the ECMAScript language specification. |
@jods4 You win. |
@jods4 NB !👍 |
@jods4 maybe exposiing everything to the template (and ONLY to the template) would make sense with |
Is this part of the reason why exposes all top level bindings to template? I really wish you can consider comment-based syntax again if possible. In my opinion, comment-based syntax have some benefits:
Can you share your thoughts about comment-based syntax? I didn't see above, sorry if I missed. |
@jacekkarczmarczyk What is visible to the template from setup. This is more controversial and was changed from previous proposal using Yes it's gonna work. Yet several people have commented that it didn't feel clean to lose the interface between setup and the template. TL;DR: from here there's nothing new compared to previous comments It would look weird because there would be some unreferenced variables lying around, for the sole purpose of being used in template. Tools would squiggle/warn about those. A related point is that it won't be obvious that they are used in template, so you can be tempted to delete, rename, inline those variables, or extract a function from a piece of code, and break the template unwillingly. If my component grows large and complex (a case which would benefit from early exports), I may end up putting the code of the script or template, or both, in a separate file, i.e. Having a clearly defined interface is also useful if you intend on testing the component without UI (something that MVVM promotes, if you follow those patterns). It tells component maintainers what might be used even outside the template, something Vetur can't know; and it tells test writers what should be tested. Saying this is like having render functions or JSX reference local variables is far from a perfect comparaison. Note that none of those are problems in the previous |
So in more general terms - are we trying to stick to the spec or from now on we're starting to introduce all kinds of "magic" leading us to 'VueScript'? |
nested is invalid: And on the contrary, even if it's invalid, I think that's a good thing. It could even prevent the code directly run in the wrong way before compiled. So that's okay. |
why not use ref.someReactiveVar?
// maybe we can shortify it as
// maybe we can shortify it as |
I really like the first proposal. At first, I was skeptical because of the implicitness of this approach. But thinking more about it and reading the comments, it makes perfect sense as, using a render function or jsx, the whole scope of the setup function is also accessible there. Regarding the second proposal, I've got a few concerns though. I think it is very unfortunate that those who put their composition functions inside separate .js files or (if support for this will not be implemented) those who use nested functions or those who use the reactivity API outside of Vue won't benefit from this additional syntax. If the main purpose of the second proposal is to reduce the verbosity of typing import { ref } from 'vue'
const count = ref(0)
function inc() {
count.$++
} I think this would also play nicely with the other shorthands inside the framework. What Maybe this was already proposed and dismissed somewhere. I couldn't find any reference to something like this. Please don't get me wrong, I very much appreciate this RFC and the general direction this is going. I just want to bring another approach with advantages for everyone into the discussion. |
re unit testing & As mentioned,
It "looks weird" because your mental model still screams "separation of concerns!!!" - you want template and script of the same component to be clearly separated even though they are inherently coupled. Funny enough we faced much criticisms along this line when introducing SFC years ago (from people who insisted to put templates, scripts and styles in separate files). You say this is different from JSX inside re tooling:
In comparison, providing the same experience via Options API is much more challenging. For example it's very difficult to connect an object key being renamed to related bindings in the template.
You will have exactly the same problem with or without Conclusion: I don't think any of the argument against the export-less scoping model sounds convincing. |
Thanks everyone for voicing your opinions - all the feedbacks are highly appreciated. Judging from the feedback, I believe the new
To avoid spawning fragmented threads, this PR will be closed and locked. |
Summary
Introduce a new script type in Single File Components:
<script setup>
, which exposes all its top level bindings to the template.Introduce a compiler-based syntax sugar for using refs without
.value
inside<script setup>
.Note: this is intended to replace the current
<script setup>
as proposed in #182.Basic example
1.
<script setup>
now directly exposes top level bindings to templateCompiled Output
Note: the SFC compiler also extracts binding metadata from
<script setup>
and use it during template compilation. This is why the template can useFoo
as a component here even though it's returned fromsetup()
instead of registered viacomponents
option.2.
ref:
sugar makes use of refs more succinctCompiled Output
Before commenting:
Please read the full RFC.
Please make sure to not simply reply with "I like it / I don't like it" - it does not contribute meaningfully to the discussion.
If not in favor of the proposal, please make concrete arguments in the context of the points made in the motivation and drawbacks. Do note that
label: statement
is validate JavaScript syntax. We are only giving theref:
label a different semantics. This is similar to how Vue directives are syntactically-valid HTML attributes given different semantics by the framework.Full rendered proposal