-
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
Ref sugar #228
Conversation
I hope we can do the same shortcut for computed functions! |
i hope it will only in rfc, not in vue code base. this is hard to learn. just composition api, without this ref is simplest, more simplest. this increases the cognitive load very strongly |
I would like to see more discussion about using the new But we still have the "problem" of the composition functions: we can avoid .value in script and in templates but not on a composition function, that adds mental overhead. @yyx990803 said it is technically possible to do it, but I haven't seen much discussion about it. The other sticky point is to know when to pass the $ version of a ref instead of just the primitive value, I believe that will be a big source of confusion. 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 not 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 template so it is only natural it gets extend to the other parts of the composition model. As en extra point, if we have a way to explicitly enable "auto-unwrap-refs", we could argue there is really no need to use |
Since this is no longer javascript, I suggest give this a name, say "vuescript", and the feature is only enabled if you put |
Adding to my previous suggestion about Perhaps we could also provide a VueScript compiler which compiles it down to JS. This way we can even use the ref sugar in non vue files. I think this is a quite interesting idea dispite I don't personally like it. |
For what I understand, this proposal has nothing to do with This way I kinda like the idea of @ycmjason : add a |
@fajuchem valid syntactically, but with different semantics |
I agree @ycmjason and @babarizbak, I think it would go far to avoid confusion among beginners if there is an explicit extra step to enabling the |
What this sets out to do is good: using a code transformation to simplify writing common reactive code. The way it implements it is IMHO bad:
That last one is IMHO a really important one. IMHO the key insight here is to realize that the transform idea is real good, and through a loader it can easily be applied to all code, even standalone JS/TS files. To do that we just need to choose a marker syntax that is semantically valid JS. To demonstrate that it's possible I wrote #223. It is exactly the same as this proposal but uses functions as markers instead of labels. With this simple change in the choice of syntax, all problems above disappear: everything is semantically correct, it is easier to understand for newcomers, no special IDE support is needed, it works with any language that compiles to JS (incl. typed languages such as TS) and best of all: you can use it even in JS files. |
The reliance on seemingly undeclared variables could be avoided in this proposal if instead we transformed |
@aztalbot that's an interesting idea. It works naturally with type inference as well. |
For label syntax, if javascript user won't get any syntax autocompletion ? |
@yyx990803 Combining the comment syntax alternative from the proposal ( I hesitate to recommend that, though, because I like the succinctness of I guess I wouldn't mind the comment syntax alternative as much if multi-line transformation was allowed, like below. But that might go too far, since you could conceivably indicate you want to transform the whole block: // indicates all assignments between @ref and @fer should be transformed
/** @ref */
let count = 0
const double = computed(() => count * 2)
/** @fer */ but there is some advantage to multi-line transformation. For example you could do this: const {
/** @ref */
isListening,
result,
error,
/** @fer */
isSupported, // this is a plain boolean
start,
stop
} = useSpeechRecognition() |
I think what makes Svelte's syntax feel more natural here is that there is no reactive atom concept. You are just writing some variables. So this RFC doesn't have any of those downsides. But it feels like a thing, where Svelte's syntax does not. It's only when you get to, "Hey, I want to derive a value", I want this to keep up to date that Svelte introduces a new concept. There is the reactive Ok, let's backpedal. This is the first time I've seen Vue now. So I will see the Not immediately having to explain reactive scope in a system like Vue that is component scoped anyway is a pretty big win I think. Maybe not the same level as Svelte where you don't even need to know what reactivity is to get started, but this is pretty big. I think the only possibly unfortunate part is technically speaking the only thing that needs calling out are in computations like computeds and watches so we can't just reuse the syntax and need to still import Ex.. import { computed } from "vue";
ref: count = 1;
// we can't just use this syntax to differentiate these 2 cases
ref: doubleCount = count * 2 // ref(count * 2)
ref: doubleCountDerived = computed(() => count * 2) Compared to Svelte: let count = 1;
let doubleCount = count * 2;
$: doubleCountDerived = count * 2; // no wrapper, no import Our victory is shortlived as soon as we need to write that thunk |
@aztalbot proposal looks good but I think there could be cases when passing a newly created ref to a composable may appear. Maybe something like:
The composable returns the passed reference. @antfu's vueuse useRefHistory is doing this for example: https://vueuse.js.org/?path=/story/utilities--userefhistory. And here we may want to use the ref: counter as an init value. If we go this way using a function to avoid the magic $counter, I think we should use another name, maybe |
@griest024 Please read the RFC. |
@ryansolid I think it would be great if we could avoid the computed import. I think that some part of the community may end up using auto import transforms so they do not have to maintain and see that imports. IMO, having to write the function in the code when declaring a computed is good, because the semantics of evaluating a function to re-evaluate a computed is aligned with it being a function and not just an expression. One possible way to avoid the import, may be to also use a label for computed refs:
But the types are off so this doesn't work. I think the only way is to use a direct expression in this case (and looks like svelte is having success explaining $: without having to write functions). So, computed values would be declared as:
In the same way as with
Typing should work correctly in this case. If we already have to explain how |
@aztalbot Doing so creates some strange semantic situations ref: count = 0
watch(ref(count), () => {})
const copy = count
watch(ref(copy), () => {}) Which one should be the compiled output? const count = ref(0)
watch(count, () => {})
const copy = count
watch(copy, () => {}) OR const count = ref(0)
watch(count, () => {})
const copy = count.value
watch(ref(copy), () => {}) It suddenly occurred to me that a similar problem exists when assigning ref: copyRef = count
const copyNonRef = count
// compiled output:
const copyRef = ref(count)
const copyNonRef = count.value Another question: can grammar with sugar be mixed with grammar without sugar? This is not what we expected, but it could happen. Do we need to disable the syntax without sugar in <script setup>
import { ref } from 'vue'
ref: count = 1
const copy = count
const foo = ref(count)
const bar = ref(copy)
</script> |
I think the Because when try to copy the code contain it to normal js code. make |
I think this is confusing, as no ' |
|
I love it. Let's put aside for a second that we all have a solid JS background and think of it as a newcomer. I think this would be a huge step forward for people who are not developers, yet want to learn how to put together a website. Some write that non-standard semantics adds a learning cost, but it actually makes learning much easier if you jump straight to Vue development with little JS knowledge. And while that sounds like skipping the foundations, if you are coming from a designer background, where you already work with components, you might not want to learn programming via writing functions that return something to your console, but jump straight to a component-based library that helps you realize your mockups. They wouldn't care if this is standard JS or not, they just want to put together some data and event handlers and let it work as simple as possible. To stay on the path of and to have the most succinct syntax for people who just want things to work, I'd push it even more. Unify ref and reactiveFrom a dev/user perspective, I don't see much difference between ref and reactive. Okay, one is a primitive value, one isn't, but it's basically just data, so why not unify these two things? Maybe we could refer to them as data like below. Or if we would move a bit towards React, we could call it state. data: foo = value; // Could be either ref or reactive under the hood Expand it to computed valuesAnd I would also join in with expanding this syntax to computed values. As @matias-capeletto wrote they could even look something like this: ref: counter = 2
computed: double = counter * 2 Why the colon postfixAnd if it's compiled anyway, I'd argue if we need that colon after ref. Why not use ref as if it was a new type after let and const? I see this syntax is valid in non-strict mode, yet if we compile it anyway, for me it's just an extra character. |
|
I like the suggestion of |
@HendrikJan so treat everything as the But syntax is hard. |
Let me share my feels. Today, we have more and more ways to do the same things. We can already write with Templates, or render functions. We can use options API or Composition API. We can already write on js/ts/jsx/tsx ... And now in addition new syntax for The more options to do the same, the harder it is to exist in this community. And I'm talking not only about beginners, but also people who have some experience. You may say: "Hey, there are still options api! Just use it!". And you will be right, but only partially. Imagine that you are a beginner who has studied several sections of the documentation but without deep dive. Or you have any experience with Vue 2, but have never worked with Vue 3 and have never encountered Composition API, You start doing something and face a problem (which is normal, no one knows everything). What does a person do when faced with a problem? She starts looking for solutions on the internet.
The variety of languages and syntaxes can be a major obstacle to immersion in open source. People are more likely to read code written by someone else than to write it themselves. And the less variety, the better.
And don't forget about tools, editors, consoles, linters, typecheckers and more. I'm sure I've written about it many times, so I'll just say that I'm worried about it. Forgive my English 😅 |
I have some opinions after using ref sugar for a long time. About Label SyntaxThe main factor affecting DX is Destructuring.
Without About
|
In this case you precisely const newCount as a
Your code:
eq
That means you do unnecessary operation yourself, Vue just honestly do what you want it to do. |
Is it possible that we do not need change the code and still be able to transform all function rref <T>(val: T) {
return ref(val) as unknown as T
} let keyword = rref('')
keyword = 'this is key'
let keyword2 = keyword.toUpperCase() compiled result: let keyword = rref('')
keyword.value = 'this is key'
let keyword2 = keyword.value.toUpperCase() |
What not many have mentioned is the TS support of the new syntax, ref: foo = 2; How do we specify the type if we need to? With another colon? ref: foo: number = 2; I'm not sure this is a nice way. Wouldn't it be better to use the decorator syntax? @ref() foo: number = 2; Decorators are a common syntax, unlike the proposed one. |
I'v been using ref sugar in TypeScript for few days. You could simply write ref: foo = 2 as number; or ref: foo = <number>2; Since ref: foo = ref<number>(2); The last approach is useful when defining an optional ref, for example ref: data = ref<string>(); Rather than ref: data = undefined as string | undefined; |
It not always safe. See this example.
If we are forced to use the const foo = ref(2); |
Not always, but in most circumstances it's usable. Of course we could define an const identity = <T>(value: T) => value; But I'd rather use
Ref sugar is not something let us use refs without Compared with ref: bar = computed(() => foo * 2);
Also, in most cases, I won't define refs with explict types since type inference works well. |
This isn't specific to We're using TSX (with |
Yes, the team is working on another proposal of ref sugar. It's still internal but I think it will be public soon. Please wait for a bit longer. |
I noticed that the beta version of 3.2 has removed the Unlike others, I don't think it's hard to understand or learn. vue3 has been out for almost a year, and ref sugar has been proposed for 9 months. I've rolled out ref sugar with the composition API in my team and it's worked so well that we're now using the Composition API exclusively for development. Ref sugar brings a lot of benefits, because ".value" is really boring and it's the biggest difference from the vue2 optional API, with ref sugar it's possible to not write ".value" at the same time. and have the magic of responsiveness, as well as composability. But for me and my team this change is pretty bad, we've already used ref sugar extensively. I don't know if I'm in the minority, but I've certainly been using ref sugar for over half a year now, because it's got really great ide support (thanks @johnsoncodehk ), and there haven't been any bugs with ref sugar in that time, and the object structure, and access to Raw Value are all great. This has been a great improvement to my development experience. The new proposal doesn't seem to be out yet, but the code is already in the master branch, which feels bad.I've looked at the commit and it's no different than the original except that it doesn't use ref sugar, it's just a different "jacket". It also requires the compiler to do magic, because without the ref label it looks more like native js, but not. Accessing Raw Value and object structures has also become more tedious.A lot of new APIs have been added, I don't know if the opponents will accept this skinned version, but it hurts the people who originally supported and used ref sugar. Since 3.1.4 now controls whether or not ref sugar works via option, I would at least like to keep the original version via option. |
You are not taking into account that the API was introduced as experimental precisely to be able to adapt it based on user feedback (a lot of people don't like the label syntax used). It enables users to experiment with the API, which is, in some cases like this one, crucial to get a feeling of the API. When you use an experimental feature, you are accepting the risk to refactor it later to whatever version it becomes or even having to remove it. It's not _"part of Vue" until the API is stabilized and the RFC is merged That being said, you should wait to see the new version to give feedback as you might even prefer it to the existing one. |
Yes, the new proposal seems puzzling, but this is the best we can do without changing the original syntax of JS (because some people always mind this). I agree to keep the new and old at the same time |
Above is my feedback, although it came late, but I actually used it for a long time.
I hope so |
If use volar, you can use Some notes:
|
With the new ref sugar, what should I do with TypeScript types? declare module '*.vue' {
declare global {
function $ref<T>(value: T): T;
function $ref<T = any>(): T | undefined;
}
} But this way, there will be problems when using some functions, such as |
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import vueJsx from '@vitejs/plugin-vue-jsx';
export default defineConfig({
plugins: [
vue({
script: {
refSugar: true,
},
}),
vueJsx(),
],
}); |
so is this only available with Vite now? I cant seem to find a webpack equivalent... |
Please refer to https://vue-loader.vuejs.org/options.html#compileroptions |
I followed the link, but there is absolutely nothing there pertaining to any of this. Nowhere on that website, or on the github repo linked to from their docs... What am I missing here? EDIT: here is where the new option is mentioned, hope this helps someone else |
Update for 3.2.x: Requires enabling in vite.config.js plugins: [
vue({
script: {
refSugar: true,
},
}),
Usage:
But, this doesn't work without adding |
|
Thanks for clarification, @jods4 |
As noted, there are various drawbacks related to the label syntax used in this proposal - specifically how it requires non-trivial tooling support due to the semantics mismatch from standard JS behavior. We are dropping this proposal. Again - keep in mind that features labelled as experimental are shipped for evaluation and collecting feedback. They may change or break at any time. There is no guarantee of API stability unless the feature's corresponding RFC has been merged. The warnings from There is a new version of ref sugar proposed at #369 which does not rely on appropriating the label syntax, and requires no dedicated tooling support. It is currently shipped in 3.2.0-beta and replaces the implementation of this proposal. Again, this is also experimental so everything above applies to the new proposal as well. |
This PR separates the
ref:
sugar into a standalone proposal from #222 so it can be discussed on its own.Summary
Introduce a compiler-based syntax sugar for using refs without
.value
inside<script setup>
(as proposed in #227)Basic example
Compiled Output
Rendered
!!! Please make sure to read the full RFC and prior discussions in #222 before commenting !!!