Skip to content
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

Closed
wants to merge 1 commit into from
Closed

Ref sugar #228

wants to merge 1 commit into from

Conversation

yyx990803
Copy link
Member

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

<script setup>
// declaring a variable that compiles to a ref
ref: count = 1

function inc() {
  // the variable can be used like a plain value
  count++
}

// access the raw ref object by prefixing with $
console.log($count.value)
</script>

<template>
  <button @click="inc">{{ count }}</button>
</template>
Compiled Output
<script setup>
import { ref } from 'vue'

export default {
  setup() {
    const count = ref(1)

    function inc() {
      count.value++
    }

    console.log(count.value)

    return {
      count,
      inc
    }
  }
}
</script>

<template>
  <button @click="inc">{{ count }}</button>
</template>

Rendered


!!! Please make sure to read the full RFC and prior discussions in #222 before commenting !!!

@yyx990803 yyx990803 added the sfc Single File Components label Nov 9, 2020
@thelinuxlich
Copy link

I hope we can do the same shortcut for computed functions!

@iNerV
Copy link

iNerV commented Nov 9, 2020

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

@tochoromero
Copy link

I would like to see more discussion about using the new :ref syntaxI love the DX you get with :ref and it is consistent with how we already use them in templates regardless.

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 :ref but receiving a raw ref as a parameter and having to call .value on that single one. This also makes unwrapping a value returned from computed transparent. I believe it will be a better DX because it will be consistent everywhere.

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 :ref at all, the compiler will know that if we have a ref(....) it needs to automatically unwrap it. Maybe there is an extra prop we can add to the <script> tag to let it know we want automatic unwrapping, but I don't know how we will indicate a composition function to do the same, I guess here is where decorators would come in handy, but who knows if those will ever get passed stage 2.

@ycmjason
Copy link

ycmjason commented Nov 9, 2020

Since this is no longer javascript, I suggest give this a name, say "vuescript", and the feature is only enabled if you put lang="vs"?

@ycmjason
Copy link

ycmjason commented Nov 9, 2020

Adding to my previous suggestion about lang="vs":

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.

@babarizbak
Copy link

babarizbak commented Nov 9, 2020

For what I understand, this proposal has nothing to do with <script setup> (as described in #227), and shouldn't be bound to it.

This way I kinda like the idea of @ycmjason : add a lang=vs attribute, which could be added to any <script> tag when you need it (in SFCs or outside). And the lang=vs would have a great benefit: as being explicitly non-regular JS, it would allow for any (reasonable) improvements of the JS syntax. It could allow great DX improvements in the future, but totally optional.

@fajuchem
Copy link

fajuchem commented Nov 9, 2020

Since this is no longer javascript, I suggest give this a name, say "vuescript", and the feature is only enabled if you put lang="vs"?

There no need to vuescript. The proposed syntax is valid javascript as stated in the RFC or here.

@jacekkarczmarczyk
Copy link

The proposed syntax is valid javascript

@fajuchem valid syntactically, but with different semantics

@aztalbot
Copy link

aztalbot commented Nov 9, 2020

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 ref: and $variable semantics. I think the proposal's drawbacks section underestimates the potential for confusing beginners (or even developers who are not full time working in javascript and just assume the language changes all the time). Attempting to use ref: outside SFCs will either fail silently or with a variable declaration error. And without prior knowledge of SFCs, $variable looks wrong and could confuse beginners who have just learned that you need to explicitly declare variables (in strict JS). Adding an extra step, like setting a different language, or adding an attribute to the script block, would ensure beginners are not using these non-standard semantics by default and are consciously opting in to these features. In general, I think any JavaScript one writes that is not intended to run on any existing or forthcoming JavaScript engine implementation should not be called (or be implied to be) JavaScript.

@jods4
Copy link

jods4 commented Nov 9, 2020

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:

  • Semantically invalid JS (multiple identical labels)
  • Misused JS features (labels, undeclared variables)
  • Magical $xy undeclared variables
  • Needs heavy IDE plugin support for completion, type analysis, warning removal, etc. Same for linters.
  • Does not work outside SFC

That last one is IMHO a really important one.
Writing reactive code outside of SFC files is common.
Worse: one day you will want to copy/paste code from an SFC file to a JS file.

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.
(You can bikeshed the function names, or even find another syntax that would be semantically correct.)

@aztalbot
Copy link

aztalbot commented Nov 9, 2020

The reliance on seemingly undeclared variables could be avoided in this proposal if instead we transformed ref calls that receive ref: declared variables into just the variable name. Meaning ref: count = 0; watch(ref(count), () => {}) becomes const count = ref(0); watch(count, () => {}). I don't think this breaks too much with our understanding of Vue reactivity, because even though watching a newly created ref based on a primitive value would usually have no effect, in this case ref: has clearly marked that primitive value as a ref, so I think it does convey that you are watching that same ref and not a new one.

@yyx990803
Copy link
Member Author

@aztalbot that's an interesting idea. It works naturally with type inference as well.

@shawn-yee
Copy link

For label syntax, if javascript user won't get any syntax autocompletion ?
How do I solve this problem by writing typescript.

@aztalbot
Copy link

aztalbot commented Nov 9, 2020

@yyx990803 Combining the comment syntax alternative from the proposal (// @ref), and using ref calls instead of relying on $variable, might solve much of the worries and objections that have been brought up.

I hesitate to recommend that, though, because I like the succinctness of ref: more than comments. I'm not sure magic comments are that much less problematic than magic labels. Perhaps from a tooling perspective, one has the advantage, though.


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()

@ryansolid
Copy link

ryansolid commented Nov 9, 2020

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. let blank and assign blank. You aren't being asked to think is this a ref or is this reactive. But this definitely limits what you can do with Svelte and actually puts some constraints on ability to optimize in nested data cases etc.

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 $ for that. New concept, new symbol. Vue needs to load up those concepts up front. Which is good. I'm a big fan of being explicit here. At this point though it isn't a cognitive reduction, it's just that you've saved me from writing an import statement. I know refs have .value. I'm still thinking this way and need to recognize where I need and don't need it. It almost takes more consideration.

Ok, let's backpedal. This is the first time I've seen Vue now. So I will see the ref: label immediately and it will need to be explained to me when to use it or not and what it does. However, I do see that this syntax prevents needing an explaination of how it works. We can say "compiler makes it reactive" rather than "we are using a getter so that we can track property access so that we know where it is being used so we can optimally update".

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 computed.

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 computed(() =>...) (Ie.. They ask "why do we need to wrap it in a function?") but still really nice first impression. I mean you can argue that it's an expression that executes multiple times so it needs to be wrapped. But this is sort of the zone that I'm thinking about here. How progressive can we make this transition?

@patak-dev
Copy link
Member

@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:

const { current, undo, redo } = useRefHistory( ref(counter) )

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 toRef(counter) doesn't have this issue for example, or a totally new name.

@LinusBorg
Copy link
Member

@griest024 Please read the RFC.

@patak-dev
Copy link
Member

@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:

ref: counter = 2
computed: double = () => counter * 2

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:

ref: counter = 2
computed: double = counter * 2

In the same way as with ref:, a computed declared using computed: will auto unwrap in <script setup>.
We cold also support writable computed refs using comma separated expressions:

computed: double = (val => { counter *= 2 }, counter*2 )

Typing should work correctly in this case. If we already have to explain how ref: works, it looks like it is not that a big jump to also add computed:. I discarded this idea at the beginning, but I think it could be interesting to explore.

@caikan
Copy link

caikan commented Nov 10, 2020

The reliance on seemingly undeclared variables could be avoided in this proposal if instead we transformed ref calls that receive ref: declared variables into just the variable name. Meaning ref: count = 0; watch(ref(count), () => {}) becomes const count = ref(0); watch(count, () => {}). I don't think this breaks too much with our understanding of Vue reactivity, because even though watching a newly created ref based on a primitive value would usually have no effect, in this case ref: has clearly marked that primitive value as a ref, so I think it does convey that you are watching that same ref and not a new one.

@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 to a common variable. So do we need to treat them differently according to the type of variable assigned?

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>?
For example, how should the following code be compiled?

<script setup>
import { ref } from 'vue'

ref: count = 1
const copy = count
const foo = ref(count)
const bar = ref(copy)
</script>

@mmis1000
Copy link

mmis1000 commented Nov 10, 2020

I think the $something may be safer than ref(something)

Because when try to copy the code contain it to normal js code.
It will always result in a hard fail no matter you are write it with ide(eslint) or notepad.
(Access undefined variable in js always crash it instantly, while Write do not)

make ref(something) always works but in different way in different context looks dangerous to me.

@csmikle
Copy link

csmikle commented Nov 10, 2020

const { x: __x, y: __x } = useMouse()
const x = ref(__x)
const y = toRef(__y)

I think this is confusing, as no '__y' is declared, unless 'y: __x' is a typo.
I don't understand how x and y are equals in the destructuring yet one is compiled as ref() and the other as toRef()

@Epic-Pony
Copy link

Since this is no longer javascript, I suggest give this a name, say "vuescript", and the feature is only enabled if you put lang="vs"?
so imaginative

@HunorMarton
Copy link

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 reactive

From 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 values

And 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 postfix

And 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.

@jacekkarczmarczyk
Copy link

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.

@HunorMarton #222 (comment)

johnsoncodehk added a commit to vuejs/language-tools that referenced this pull request Nov 10, 2020
@cereschen
Copy link

I like the suggestion of $something, because it doesn't put extra burden on js/ts files, and it's clear at a glance
However, it's not a good idea to use $as a prefix, which is not conducive to fast input variables
My suggestion is something$, the code editor can help you complete the rest.
This looks good, but it's not elegant enough. Do we spend too much energy on distinguishing between ref variables and ordinary variables? In fact, code editor plugins can easily mark ref variables, but it's really difficult to leave the code editor, but who can do without the code editor?

@vuejs vuejs deleted a comment from ssslambda Nov 10, 2020
@ryansolid
Copy link

@HendrikJan so treat everything as the reactive primitive under the hood. I mean could go either way on the compilation really. It's more of deciding if syntax is sensical. Assigning to a magic data isn't that different than assigning to a magic this. I'm not sure that actually simplifies anything though. Still need to differentiate value from reference. There are a ton of ways to implement it.

But syntax is hard.

@cawa-93
Copy link

cawa-93 commented Mar 16, 2021

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 <script setup> is considered.

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, <script setup> or what ever.

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.

  • On request "How to do something with Vue" you can get 3-4 articles or videos, and in each case the way to solve your problem may be different. Or worse, all solutions will use the syntax you see for the first time.

  • Or, by asking a question on one of the forums or chats, you can be answered using an API that you are not familiar with, or that you can not (or do not want to) use.

  • In all cases, you may lack the initial knowledge. You have to have enough qualifications to be able to read a syntax unfamiliar to you and be able to adapt it to your project and to another syntaxist.

  • Or you will want to explore how the implemented third-party component (due to some bug or just for educational purposes). If the component is written in a way that you can understand, you will be able to study it carefully, find the problem, and possibly suggest a pull request to fix it. Otherwise, all you can do is send an issue and hope that the author will have time for it.

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.

  • I don't really like the API composition, but I like the ability to share some logic between the components.

  • I don't like <script setup>. I don't see any scenarios when it might be appropriate. It only makes the work of the component less obvious. But it can confuse anyone who is unfamiliar with it.

  • I definitely don't like "Ref sugar". When I showed a sample of such a component to my student, she didn't even realize that it was a Vue component. And I agree with it, it's a bit like even JavaScript anymore. I remember the shock when switching from AngularJS -> Angular v2 (That's when I fell in love with Vue by the way).

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 😅

@johnsoncodehk
Copy link
Member

I have some opinions after using ref sugar for a long time.

About Label Syntax

The main factor affecting DX is Destructuring.

  • As far as people I know, almost always get stuck in missing () codes when using it for the first time.
  • After I am familiar with the syntax, () is still a problem for me, because almost all my projects use functions like vueuse, so I need There are more cases of () than not needed. Although the trouble of .value is solved, the minor trouble of () is introduced.

Without () problem, the label syntax is perfect. Because it is based on legal JS, you can directly get some language support in IDEs that cannot recognize ref sugar. This choice is really smart!

About <script setup>

This is another major issue. Ref sugar can only be used in <script setup>, but the general logic is written in *.ts (this is the most .value place). If <script> also supports ref sugar, the general logic can be written in <script> of *.vue.

So I hope we can support ref sugar in normal <script> block.

@MinatoHikari
Copy link

MinatoHikari commented Apr 29, 2021

@yyx990803
关于ref糖, 我表示很支持。
只是关于编译的结果我有些疑问:
编译前的代码:

ref: newCount = computed(()=>{
    return props.count + 1
})

这是编译后的代码:

const newCount = _ref(computed(()=>{
    return props.count + 1
}))

而 computed 本身会生成 ref 格式的数据,但是编译后又会在computed外再包一层_ref,是否是多余的呢?

In this case you precisely const newCount as a Ref<ComputedRef<number>>
This is something like

const newCount = Promise.resolve().then(()=> new Promise((res,rej)=>{})

Your code:

ref: newCount = computed(()=>{
     return props.count + 1
 })

eq

const newCount = ref(computed(()=>{
     return props.count + 1
 }))

That means you do unnecessary operation yourself, Vue just honestly do what you want it to do.

@lovetingyuan
Copy link

lovetingyuan commented May 20, 2021

Is it possible that we do not need change the code and still be able to transform all ref variables to .value form?
We just track the ref type variable.

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()

@soylomass
Copy link

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.

@lujjjh
Copy link

lujjjh commented Jul 3, 2021

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 is idempotent, you may also write

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;

@cawa-93
Copy link

cawa-93 commented Jul 3, 2021

@lujjjh

ref: foo = 2 as number;

It not always safe. See this example.

ref: foo = ref<number>(2);

If we are forced to use the ref function, then there is no point in ref sugar:

const foo = ref(2);

@lujjjh
Copy link

lujjjh commented Jul 3, 2021

@cawa-93

It not always safe. See this example.

Not always, but in most circumstances it's usable.

Of course we could define an identity function or something

const identity = <T>(value: T) => value;

But I'd rather use ref function in this case.

If we are forced to use the ref function, then there is no point in ref sugar:

Ref sugar is not something let us use refs without ref function, but without .value.

Compared with

ref: bar = computed(() => foo * 2);

computed function also returns a Ref. Using ref function is just the same.

Also, in most cases, I won't define refs with explict types since type inference works well.

@bbugh
Copy link

bbugh commented Jul 4, 2021

This isn't specific to <script setup>, but it looks like ref unwrapping is combined into this proposal. I was directed here from vitejs/vite#4103, hopefully it's the right place since there's 56 people about to get a notification. 😅

We're using TSX (with vite-plugin-jsx-vue) and it would be helpful for values to automatically unwrap Ref and ComputedRef in the component render functions of defineComponent also.

@ghost
Copy link

ghost commented Jul 17, 2021

Syntax has changed since 3.2.0-beta.1?

<script setup>
// ref: count = 0
let count = $ref(0)

function inc() {
  count++
}
</script>

<template>
  <button @click="inc">{{ count }}</button>
</template>

How can we enable it?

2021-07-17_081955

@antfu
Copy link
Member

antfu commented Jul 17, 2021

Syntax has changed since 3.2.0-beta.1? How can we enable it?

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.

@odex21
Copy link

odex21 commented Jul 17, 2021

I noticed that the beta version of 3.2 has removed the original ref sugar support. I'm very disappointed. I've been using ref sugar for more than half a year and it's part of vue3 as far as I know.

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, $ref,$computed, $fromRefs, $raw, and I wonder if there will be $shallowRef, or $watch? Originally I only needed the ref label and $+my variable, or an extra pair of parentheses to handle the whole scene.

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.

@posva
Copy link
Member

posva commented Jul 17, 2021

I don't know if the opponents will accept this skinned version, but it hurts the people who originally supported and used ref sugar.

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.

@cereschen
Copy link

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

@odex21
Copy link

odex21 commented Jul 17, 2021

Above is my feedback, although it came late, but I actually used it for a long time.

That being said, you should wait to see the new version to give feedback as you might even prefer it to the existing one.

I hope so

@johnsoncodehk
Copy link
Member

If use volar, you can use Remove All Ref Sugar in Project command to clear ref sugar label syntax in all vue scripts from workspace project.

Some notes:

  • You may need to disable editor.formatOnSave, because when this operation is performed on a large number of files at the same time, vscode will incorrectly apply edits.
  • Make sure to manually check the modification of each file, because volar may incorrectly convert some edge cases, but you should be able to make corrections easily.

@sxzz
Copy link
Member

sxzz commented Jul 20, 2021

With the new ref sugar, what should I do with TypeScript types?
My solution is

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 useDebounce(https://vueuse.org/shared/usedebounce/). useDebounce needs a Ref parameter.

@andylix
Copy link

andylix commented Jul 20, 2021

Syntax has changed since 3.2.0-beta.1?

<script setup>
// ref: count = 0
let count = $ref(0)

function inc() {
  count++
}
</script>

<template>
  <button @click="inc">{{ count }}</button>
</template>

How can we enable it?

2021-07-17_081955

I'm also wondering this. How can I enable this?

@sxzz
Copy link
Member

sxzz commented Jul 21, 2021

@andylix

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(),
  ],
});

@ub3rb3457
Copy link

ub3rb3457 commented Jul 29, 2021

@andylix

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...

@sxzz
Copy link
Member

sxzz commented Jul 29, 2021

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

@ub3rb3457
Copy link

ub3rb3457 commented Jul 30, 2021

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

@mariusa
Copy link

mariusa commented Aug 4, 2021

Update for 3.2.x:

Requires enabling in vite.config.js

 plugins: [
        vue({
            script: {
                refSugar: true,
            },
        }),
 

Usage:

let sidebarCollapsed = $ref(false)

But, this doesn't work without adding @vue/compiler-sfc to devDependencies
Why isn't @vue/compiler-sfc included in vue's devDependencies?

@jods4
Copy link

jods4 commented Aug 4, 2021

Why isn't @vue/compiler-sfc included in vue's devDependencies?

devDependencies are only installed for the project you run npm i for.
devDependencies of your dependencies are not installed, so I'm not sure how that would help?

@mariusa
Copy link

mariusa commented Aug 4, 2021

Thanks for clarification, @jods4
To have ref sugar working out of the box with vue 3.2.x, should then @vue/compiler-sfc be included in vue's dependencies?

@yyx990803
Copy link
Member Author

yyx990803 commented Aug 4, 2021

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 @vue/compiler-sfc when you use an experimental feature should have made this quite clear. By opting into experimental features you are acknowledging that you are willing to refactor your code when the feature changes or is dropped.

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.

@yyx990803 yyx990803 closed this Aug 4, 2021
@vuejs vuejs locked as resolved and limited conversation to collaborators Aug 4, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
sfc Single File Components
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet