Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings
/rfcsPublic

[⚠️ Dropped] Reactivity Transform#369

yyx990803 started this conversation inRFC Discussions
Aug 4, 2021· 160 comments· 489 replies
Discussion options

This Proposal has been Dropped

Seereasoning here.

Summary

Introduce a set of compiler transforms to improve ergonomics when using Vue's reactivity APIs, specifically to be able to use refs without.value.

Basic example

// declaring a reactive variable backed by an underlying refletcount=$ref(1)// log count, and automatically log again whenever it changes.// no need for .value anymore!watchEffect(()=>console.log(count))functioninc(){// assignments are reactivecount++}
Compiled Output
import{watchEffect,ref}from'vue'constcount=ref(1)watchEffect(()=>console.log(count.value))functioninc(){count.value++}

Motivation

Ever since the introduction of the Composition API, one of the primary unresolved questions is the use of refs vs. reactive objects. It can be cumbersome to use.value everywhere, and it is easy to miss if not using a type system. Some users specifically lean towards usingreactive() exclusively so that they don't have to deal with refs.

This proposal aims to improve the ergonomics of refs with a set of compile-time macros.

Detailed design

Overview

  • Every ref-creating API has a$-prefixed macro version, e.g.$ref(), that createsreactive variables instead of refs.
  • Destructure objects or convert existing refs to reactive variables with$()
  • DestructuringdefineProps() in<script setup> also creates reactive variables.
  • Retrieve the underlying refs from reactive variables with$$()

Reactive Variables

To understand the issue we are trying solve, let's start with an example. Here's a normal ref, created by the originalref() API:

import{ref,watchEffect}from'vue'constcount=ref(0)watchEffect(()=>{// access value (track)console.log(count.value)})// mutate value (trigger)count.value=1

The above code works without any compilation, but is constrained by how JavaScript works: we need to use the.value property, so that Vue can intercept its get/set operations in order to perform dependency tracking and effect triggering.

Now let's look at a version usingreactive variables:

import{watchEffect}from'vue'letcount=$ref(0)watchEffect(()=>{// access value (track)// compiles to `console.log(count.value)`console.log(count)})// mutate value (trigger)// compiles to `count.value = 1`count=1

This version behaves exactly the same as the original version, but notice that we no longer need to use.value. In fact, this makes our JS/TS code work the same way as in Vue templates where root-level refs are automatically unwrapped.

The$ref() function is acompile-time macro that creates areactive variable. It serves as a hint to the compiler: whenever the compiler encounters thecount variable, it will automatically append.value for us! Under the hood, the reactive variable version is compiled into the normal ref version.

Every reactivity API that returns refs will have a$-prefixed macro equivalent. These APIs include:

  • ref ->$ref
  • computed ->$computed
  • shallowRef ->$shallowRef
  • customRef ->$customRef
  • toRef ->$toRef

Optional Import

Because$ref() is a macro and not a runtime API, it doesn't need to be imported fromvue. However, if you want to be more explicit, you can import it fromvue/macros:

import{$ref}from'vue/macros'letcount=$ref(0)

Bind existing ref as reactive variables with$()

In some cases we may have wrapped functions that also return refs. However, the Vue compiler won't be able to know ahead of time that a function is going to return a ref. Therefore, we also provide a more generic$() macro that can convert any existing refs into reactive variables:

functionmyCreateRef(){returnref(0)}letcount=$(myCreateRef())

Destructuring objects of refs with$()

It is common for a composition function to return an object of refs, and use destructuring to retrieve these refs.$() can also be used in this case:

import{useMouse}from'@vueuse/core'const{ x, y}=$(useMouse())console.log(x,y)

Compiled output:

import{toRef}from'vue'import{useMouse}from'@vueuse/core'const__temp=useMouse(),x=toRef(__temp,'x'),y=toRef(__temp,'y')console.log(x.value,y.value)

Note that ifx is already a ref,toRef(__temp, 'x') will simply return it as-is and no additional ref will be created. If a destructured value is not a ref (e.g. a function), it will still work - the value will be wrapped into a ref so the rest of the code work as expected.

$() destructure works on both reactive objects AND plain objects containing refs.

Reactive props destructure

There are two pain points with the currentdefineProps() usage in<script setup>:

  1. Similar to.value, you need to always access props asprops.x in order to retain reactivity. This means you cannot destructuredefineProps because the resulting destructured variables are not reactive and will not update.

  2. When using thetype-only props declaration, there is no easy way to declare default values for the props. We introduced thewithDefaults() API for this exact purpose, but it's still clunky to use.

We can address these issues by applying the same logic for reactive variables destructure todefineProps:

<scriptsetuplang="ts">interfaceProps{    msg:stringcount?:numberfoo?:string}const{    msg,// default value just works    count=1,// local aliasing also just works// here we are aliasing `props.foo` to `bar`foo:bar}=defineProps<Props>()watchEffect(()=>{// will log whenever the props changeconsole.log(msg,count,bar)})</script>

the above will be compiled into the following runtime declaration equivalent:

exportdefault{props:{msg:{type:String,required:true},count:{type:Number,default:1},foo:String},setup(props){watchEffect(()=>{console.log(props.msg,props.count,props.foo)})}}

Retaining reactivity across function boundaries with$$()

While reactive variables relieve us from having to use.value everywhere, it creates an issue of "reactivity loss" when we pass reactive variables across function boundaries. This can happen in two cases:

Passing into function as argument

Given a function that expects a ref object as argument, e.g.:

functiontrackChange(x:Ref<number>){watch(x,(x)=>{console.log('x changed!')})}letcount=$ref(0)trackChange(count)// doesn't work!

The above case will not work as expected because it compiles to:

letcount=ref(0)trackChange(count.value)

Herecount.value is passed as a number wheretrackChange expects an actual ref. This can be fixed by wrappingcount with$$() before passing it:

let count = $ref(0)- trackChange(count)+ trackChange($$(count))

The above compiles to:

import{ref}from'vue'letcount=ref(0)trackChange(count)

As we can see,$$() is a macro that serves as anescape hint: reactive variables inside$$() will not get.value appended.

Returning inside function scope

Reactivity can also be lost if reactive variables are used directly in a returned expression:

functionuseMouse(){letx=$ref(0)lety=$ref(0)// listen to mousemove...// doesn't work!return{    x,    y}}

The above return statement compiles to:

return{x:x.value,y:y.value}

In order to retain reactivity, we should be returning the actual refs, not the current value at return time.

Again, we can use$$() to fix this. In this case,$$() can be used directly on the returned object - any reference to reactive variables inside the$$() call will be retained as reference to their underlying refs:

functionuseMouse(){letx=$ref(0)lety=$ref(0)// listen to mousemove...// fixedreturn$$({    x,    y})}

$$() Usage on destructured props

$$() works on destructured props since they are reactive variables as well. The compiler will convert it withtoRef for efficiency:

const{ count}=defineProps<{count:number}>()passAsRef($$(count))

compiles to:

setup(props){const__props_count=toRef(props,'count')passAsRef(__props_count)}

TypeScript & Tooling Integration

Vue will provide typings for these macros (available globally) and all types will work as expected. There are no incompatibilities with standard TypeScript semantics so the syntax would work with all existing tooling.

This also means the macros can work in any files where valid JS/TS are allowed - not just inside Vue SFCs.

Since the macros are available globally, their types need to be explicitly referenced (e.g. in aenv.d.ts file):

/// <reference types="vue/macros-global" />

When explicitly importing the macros fromvue/macros, the type will work without declaring the globals.

Implementation Status

You can try the transform in theVue SFC Playground (works in both.vue and.(js|ts) files).

Vue 3.2.25+ ships an implementation of this RFC as an experimental feature under the package@vue/reactivity-transform. The package can be used standalone as a low-level library. It is also integrated (with its APIs re-exported) in@vue/compiler-sfc so most userland projects won't need to explicitly install it.

Higher-level tools like@vitejs/plugin-vue andvue-loader can be configured to apply the transform to vue, js(x) and ts(x) files. SeeAppendix for how to enable the transform in specific tools.

Experimental features are unstable and may change between any release types (including patch releases). By explicitly enabling an experimental feature, you are taking on the risk of potentially having to refactor into updated syntax, or even refactor away from the usage if the feature ends up being removed.

Unresolved Questions

defineProps destructure details

  1. ShoulddefineProps destructure require additional hints?

    Some may have the concern that reactive destructure ofdefineProps isn't obvious enough because it doesn't have the$() indication, which may confuse new users.

    An alternative of making it more explicit would be requiring$() to enable the reactive behavior:

    const{ foo}=$(defineProps(['foo']))

    However, the only benefit of this is for a new user to more easily notice thatfoo is reactive. If this change lands, the documentation would mention the destructure reactivity when introducingdefineProps. Assuming all users learn about this on inital onboarding, the extra wrapping doesn't really serve any real purpose (similar to$(ref(0))).

  2. The proposed reactive destructure fordefineProps is technically a small breaking change, because previously the same syntax also worked, just without the reactivity. This could technically break the case where the user intentionally destructures the props object to get a non-reactive initial value of a prop:

    const{ foo}=defineProps(['foo'])

    However, this should be extremely rare because without reactive destructure, doing so meantall props retrieved this way are non-reactive. A more realistic example would be:

    constprops=defineProps(['foo','bar'])const{ foo}=propsprops.bar// reactive access to `bar`

    A simple workaround after this RFC:

    const{ foo, bar}=defineProps(['foo','bar'])constinitialFoo=foo

Alternatives

Other related proposals

This RFC is a revised version of#368 which also includes feedback from discussions in#413 and#394.

Earlier proposals

The whole discussion traces all the way back to the first draft of ref sugar, but most are outdated now. They are listed here for the records.

Adoption strategy

This feature is opt-in. Existing code is unaffected.

Appendix

Enabling the Macros

  • All setups requirevue@^3.2.25

Vite

  • Requires@vitejs/plugin-vue@^2.0.0
  • Applies to SFCs and js(x)/ts(x) files. A fast usage check is performed on files before applying the transform so there should be no performance cost for files not using the macros.
  • NoterefTransform is now a plugin root-level option instead of nested asscript.refSugar, since it affects not just SFCs.
// vite.config.jsexportdefault{plugins:[vue({reactivityTransform:true})]}

vue-cli

  • Currently only affects SFCs
  • requiresvue-loader@^17.0.0
// vue.config.jsmodule.exports={chainWebpack:(config)=>{config.module.rule('vue').use('vue-loader').tap((options)=>{return{          ...options,reactivityTransform:true}})}}

Plainwebpack +vue-loader

  • Currently only affects SFCs
  • requiresvue-loader@^17.0.0
// webpack.config.jsmodule.exports={module:{rules:[{test:/\.vue$/,loader:'vue-loader',options:{reactivityTransform:true}}]}}
You must be logged in to vote

Replies: 160 comments 489 replies

Comment options

I really like the switch to a regular JS syntax in this proposal, this reads so much better now, good job! I also like the ergonomics of$ref and$computed, it looks as close to a regular setup as possible.

But I would also like to question the$fromRefs ergonomics. Using composables in Composition API is the most frequent thing and having to wrap each and every composable into$fromRefs is a bummer. How this can be solved without the wrapper function? I was thinking of detecting the composable notation (everything that is a function and starts withuse, same thing as with events) but that might lead to some confusion why composables that are named differently do not work as expected (in that case theyshould use that wrapper function). What do you think?

You must be logged in to vote
2 replies
@yyx990803
Comment options

yyx990803Aug 4, 2021
Maintainer Author

I don't think there is a way around this - you needsome form of compiler hint when you intend to treat destructured variables as refs, and it also needs to account for proper type inference (which the wrapper function does).

@zhuangState
Comment options

我认为没有办法解决这个问题 - 当您打算将解构变量视为 refs 时,您需要某种形式的编译器提示,并且它还需要考虑正确的类型推断(包装器函数就是这样做的)。

Can we unify standards, such as $defineProps

Comment options

In order for this to work,@vue/compiler-sfc needs to be added topackage.jsondependencies, right? This isn't explicitly mentioned.
Would you consider adding@vue/compiler-sfc tovue's dependencies, so$ref works out of the box with vue?

You must be logged in to vote
1 reply
@yyx990803
Comment options

yyx990803Aug 4, 2021
Maintainer Author

Any build setup that can compile Vue SFCs already should include@vue/compiler-sfc in its dev dependencies. How to setup a project that can compile Vue SFCs isn't within this RFC's scope.

This comment has been hidden.

@yyx990803

This comment has been hidden.

@yyx990803

This comment has been hidden.

This comment has been hidden.

@muzaisimao

This comment has been hidden.

Comment options

let must be used only with$ref() or with$computed() too?

You must be logged in to vote
4 replies
@yyx990803
Comment options

yyx990803Aug 8, 2021
Maintainer Author

$computed doesn't have restrictions onlet orconst.

@Akryum
Comment options

AkryumAug 8, 2021
Collaborator

What about writable computed?

@liyuanquan0617
Comment options

How to use$ref() in theTop-level await?

@SnowingFox
Comment options

But if i used it like this?

letval=$ref(0)constcomputedVal=$computed({get(){returnval+1},set(payload){val=payload}})

Obviously, if i try to change thecomputedVal, the IDE will throw an error

This comment has been hidden.

Comment options

这写法简直太棒了,我喜欢这种写法, 比ref: count = 0 ,更香

You must be logged in to vote
3 replies
@zhenhappy
Comment options

ref:这种写法IDE识别会报错,毕竟不是声明变量的正确方式,换了这种之后IDE不会报红色的波浪线了

@LovesLolita
Comment options

这种TS怎么写啊

@yangzhenfeng1994
Comment options

新版本已经删掉了这个特性,不建议使用了,如果确定需要使用的话,可以通过vue-macros这个包来使用,ts可以在全局.d.ts文件内引用类型定义 ///

Comment options

我发现我犯了一个错误:refcomputed并不完全等同,所以我重新编辑了:

I found that I made a mistake:ref andcomputed are not exactly the same, so I edited it again:

我们可以沿用JS/TS的语法和语义,定义一种简单的编译模式:只需要对变量声明语句做出一些特殊处理:let变量的初始化表达式编译为ref包装的表达式,const变量的初始化表达式编译为unref包装的表达式。例如:

We can follow the syntax and semantics of JS / TS and define a simple compilation mode: we only need to make some special processing for variable declaration statements:let variable initialization expression is compiled intoref wrapped expression, andconst variable initialization expression is compiled intounref wrapped expression. For example:

<script lang="ts">"use ref"let a:number=1const b=2let c=a+bconst d=a+b+clet e=$computed(()=>a+b)const f=$computed(()=>a+b)</script>
Compiled output
<script>consta=ref(1)constb=unref(2)constc=ref(a.value+ b)constd=unref(a.value+ b+c.value)conste=ref(computed(()=>a.value+ b))constf=unref(computed(()=>a.value+ b))</script>

也可以应用于SFC外部的JS/TS源文件:

It can be supported outside of SFCs.

functionuseMouse(){"use ref"letx=0lety=0functionupdate(e){x=e.pageXy=e.pageY}onMounted(()=>window.addEventListener('mousemove',update))onUnmounted(()=>window.removeEventListener('mousemove',update))return{x:$ref(x),y:$ref(y),}}
Compiled output
functionuseMouse(){constx=ref(0)consty=ref(0)functionupdate(e){x.value=e.pageXy.value=e.pageY}onMounted(()=>window.addEventListener('mousemove',update))onUnmounted(()=>window.removeEventListener('mousemove',update))return{x:x,y:y,}}

支持解构语法:

Destruction syntax supported:

"use ref"let{ x, y}=useMouse()const[count,setCount]=useState(0)
Compiled output
const__result__1=useMouse()constx=ref(__result__1.x)consty=ref(__result__1.y)const__result__2=useState(0)cosnt__iterator__1=__result__2[Symbol.iterator]()constcount=unref(__iterator__1.next().value)constsetCount=unref(__iterator__1.next().value)
You must be logged in to vote
0 replies
Comment options

How about variable name syntax sugar?

- let count = $ref(0)+ let $count = 0- const plusOne = $computed(() => count + 1)+ const $plusOne = count + 1
You must be logged in to vote
10 replies
@yyx990803
Comment options

yyx990803Aug 6, 2021
Maintainer Author

Then it would be a mess because people would be using different syntaxes and even mixing them. It also creates confusion for learning. It's better to stick to one design that can solve all cases.

@zhenzhenChange
Comment options

Then how do you know that$count isref and$plusOne iscomputed?

@max-hk
Comment options

@zhangenming we should know that at compile time as$plusOne's value is calculated from a ref dependency ($count)

@max-hk
Comment options

Someone else also proposed variable name syntax sugar. Maybe it is worth to be re-evaluated.

In order to avoid confusion, we can rename$ref() to$refFrom(), and throw compile-time error if non-ref augments are passed to the function.

@lxsmnsyc
Comment options

we should know that at compile time as $plusOne's value is calculated from a ref dependency ($count)

But what if you're just trying to construct aref with an initial value that is derived from another? How would you convert this then?

letx=ref(y.value+1);
Comment options

To avoid "too much magic", could this be opt in instead of the default behaviour? Something like<script setup auto-unref> (actual attribute name clearly debatable)

You must be logged in to vote
4 replies
@jods4
Comment options

Isn't it opt-in simply because if you don't want those macros you simply don't use them?
Adding too many local flags (as opposed to enabling globally in config) really increases the ceremony of writing components.
I feel like<script setup auto-unref lang="ts"> is pushing it a bit for me.

@floroz
Comment options

I agree that at best should be an opt-in feature, but via compiler options, not granular access on a function/script basis.

@Fuzzyma
Comment options

Doesn't really work if you import components from other packages that are not already transpiled and still vue files. You need to keep it on the component level somehow

@floroz
Comment options

Doesn't really work if you import components from other packages that are not already transpiled and still vue files. You need to keep it on the component level somehow

Good point, thanks!

Comment options

$ref It looks weird
ref.auto how about this

You must be logged in to vote
4 replies
@shineZz
Comment options

不怎么样

@Tofu-Xx
Comment options

不怎么样

@zhangenming
Comment options

咦 我啥时候评论的这个 看不懂我想表达啥了

@Tofu-Xx
Comment options

咦 我啥时候评论的这个 看不懂我想表达啥了

哈哈哈,应该是想用ref.auto代替$ref吧

This comment was marked as off-topic.

@caikan
Comment options

These are just sugars, and the compiled runtime API is still consistent with the original API. I think a runtime exception should be thrown when the sugars are called in non-sugar mode.
To take a step back, we can adjust the naming to avoid ambiguity. In fact, I have considered such a group of names before:

import{$ref,shallow,custom,compute,unref,trigger}from'vue'
@yyx990803
Comment options

yyx990803Aug 8, 2021
Maintainer Author

I have actually thought of the same idea before, but the problem is that the variable declaration code will look indistinguishable from code that isnot using the sugar - theonly hint is the import site. I think the sugar needs to be moreexplicit at the variable declaration sites rather than implicit.

@yyx990803
Comment options

yyx990803Aug 8, 2021
Maintainer Author

In addition, I understand that you have been experimenting in this space, but please only use this thread for providing feedback for what is being proposed and avoid pitching your own proposal (you can use a separate thread for that and link to it here).

@caikan
Comment options

These ideas are based on my assumption: in the scenario where ref sugar needs to be used, there are very few cases where non ref variables need to be declared (if any,const declaration orunref wrapping can be used explicitly). So I deliberately want to make the code look the same as that is not using the sugar. I think this can reduce some boilerplate code and mental overhead.
I've also considered not using implicit ref variable declarations, but I find it impossible to write destructuring code more concisely. I don't like$formRefs, which will add some bloated boilerplate code.

@caikan
Comment options

In addition, I understand that you have been experimenting in this space, but please only use this thread for providing feedback for what is being proposed and avoid pitching your own proposal (you can use a separate thread for that and link to it here).

Sorry, I had some ideas based on the current proposal at the beginning, so I want to share them here as soon as possible. I'll try to open a new thread later.

Comment options

Has anyone already proposed this? My half-baked thought is it would be better if $x just meant x.value and let $x = 0 was short for const x = ref(0). People are used to using $ from jQuery days to signify values that are special to a framework. If you need a dollar variable for legacy reasons, there can be a magic comment or label for raw mode, but it’s unusual to use dollar for normal template variables, so I don’t think it will be used often.

You must be logged in to vote
5 replies
@earthboundkid
Comment options

It could work for computed too.

let$n=0function$double(){return$n*2}// $double is now computed(double).value
@max-hk
Comment options

See my comment#369 (comment)

@jods4
Comment options

There's a lot to flesh out in such a proposal:

  • How do you get the underlying ref? A magic$raw function? If so, did we really win much over the current proposal?
  • How do you wrap a shallow ref, custom ref, a writable computed? Seems to me you'd need a magic function again. If you have magic functions, let's be consistent and go all in, not halfway.
  • A problem with your computed proposal is that for Typescript (or Flow),$double is a function. So you'd have to invoke it to read its value:$double(). Now you have some$ magic that are read as variable, some that are read as functions. It's a mess, especially since computed areRef as well and should be usable interchangeably.
  • Notice that$ prefix is something that is already much used. Vuelidate uses it for example. This is gonna create a lot of confusion, if not bugs induced by the compiler assuming anything with$ should be magically modified.

there can be a magic comment or label for raw mode

I think this is exactly what created much of the pushback on the initial proposal: the abuse of existing JS syntax in twisted ways.

@earthboundkid
Comment options

How do you get the underlying ref? A magic $raw function? If so, did we really win much over the current proposal?

You refer to the variable name without the dollar sign. x is the ref; $x is the value.

You could do setters with

let$double={get:set:}

For shallow refs and read only, I dunno, you could use labels I guess, like the old proposal/Svelte.

@yyx990803
Comment options

yyx990803Aug 8, 2021
Maintainer Author

Then it has all the problems with the previous proposal: TypeScript doesn't know the implicit relationship between$double anddouble (it doesn't even knowdouble exist). This essentially means you will need special hacks around TypeScript to make type inference work. Labels also require TypeScript hacks. This is essentially the entire reason we dropped the previous proposal.

Comment options

❤️ very happy to see this new proposal!

When writing components, here we used a lot of reactive objects for state, that were simply returned fromsetup and we didn't need refs as much.
Now that script-setup is not experimental anymore, we migrated to it and it's really nice how it removes the ceremony and makes components much shorter -- not to mention the nicedefineProperties for TS users.
One "downside", if I can even call it that, is that script-setup doesn't have a returned state object, so it really encourages ref usage over reactive objects. Anything we'd have put on our reactive state object is now a local ref variable.

This proposal will greatly improve writing script setup by making refs work like any normal variables! 👍
The power of reactivity combined with the simplicity of plain JS code, Vue 3 is shaping into something super-awesome.

About usage outside SFC components

A few thoughts:

  • SFC components are a sweet spot that will prob. cover 80% of all needs. I suppose devs who write functional components or composable functions may like to use the same refs macros outside SFC.
  • If the core team decides not to ship this outside SFC, I'd be surprised nobody in the community does it. As myvite-auto-ref loader demonstrated, this can completely be done in userland.
  • If non-standard JS and build performances are a concern, maybe it's only a matter of convention. For instance, a general.js loader could skip all files, except.vue.js files. In this way: (1) the extension.vue.js signals that this code is a gonna be transformed and is a little bit "magic" (although IMHO, not that much as JS semantics fully apply); (2) the loader would only process files that needs it: you pay for play.
You must be logged in to vote
3 replies
@boonyasukd
Comment options

Opting-in to Ref Sugar by convention via file suffix.vue.js looks quite intuitive to me. In this manner, it'll be easy for people to incrementally migrate the project from one syntax to another as well.

IMO, with how React community blatantly claims that JSX is "just Javascript", I honestly don't feel that introducing Ref Sugar to Vue ecosystem is any worse offender to "standard Javacript": we just need to look at our own DSL with the same mentality. Usage of Ref Sugar outside SFCs shouldn't be frown upon.

@leopiccionia
Comment options

Back in the day, when SFC was a novel concept and its tooling support was subpar, some Vue users used Webpack, Rollup, etc. loaders to build a virtual*.vue file from a*.vue.js, a*.vue.html and a*.vue.css file (or*.vue.ts,*.vue.scss, etc.). One example:https://github.com/pksunkara/vue-builder-webpack-plugin

I like the extension idea, but it could clash with aforementioned usage. I particularly see no problem, as it never took off and solving the clash is just a question of remapping file names to some other convention.

@jods4
Comment options

Interesting! I did not know that.
I suppose it could be a matter of picking a different extension, e.g..refs.js?

In fact when it comes to loaders, the extension you process (or not) could be chosen in your build configuration.

Comment options

It would be better ifreactive also has syntactic sugar.

code:

<scriptsetup>  const foo = $({a:1})</script>

compiled out:

<scriptsetup>  import{reactive} from 'vue'  const foo = reactive({a:1})</script>
You must be logged in to vote
3 replies
@yyx990803
Comment options

yyx990803Aug 8, 2021
Maintainer Author

The primary goal of the sugar is to remove the need for.value - not requiring imports is just a side benefit.reactive doesn't have the.value problem, and when you have$ref sugar you don't even needreactive that often.

@edison1105
Comment options

make sense. 👍

@SnowingFox
Comment options

Have you considered using jQuery?

Comment options

I spent a few more hours playing around and getting a better feeling for the proposal.

From a design perspective, there isn't much that I could complain about besidesdefineProps doing compile-time magic without any$ prefix to signal the special behaviour.

What I liked

The Reactive Variable$ref declaration feels extremely good and with some additional convention (such as prefixing$ to the variable name to signal reactivity) provides very clean syntax within thescript setup itself.

let$count=$ref(0)// I mutate state!$count++

The problem

As soon as I started creating multiple modules and composable, the limitations of thefunction boundaries makes the initial syntax gains immediately decay.

// Let's pretend each of these functions comes from a different moduleexportfunctionexpectsRef(r:Ref<any>):void{// ..}exportfunctionexpectsValue(v:any):void{// ..}exportfunctionuseFooBar(){returnreactive({a:'',b:'',})}exportfunctionuseBarBaz(){constc=ref('')constd=ref('')return$$({    c,    d,})}

As a developer navigating these files on my first onboarding on a codebase, if I don't have previous experience with Vue, I am immediately overwhelmed by the number of possibilities I have to pick from, to declare a reactive state.

<script setup>constform=reactive({  email:'',  password:''})const$count=$ref(0)const {a,b }=$(useFooBar())const {c,d }=$(useBarBaz())construn= ()=> {expectsRef($$($count))expectsValue($count)}</script>

The above is indeed a very stretched example, but it's valid and supported syntax with correct usage of all those helpers and macros.

If I were to work on such a codebase, I have to switch my mental model as if I were the compiler, wrapping/unwrapping refs line by line to invoke the correct macros and understand all the transformations that are happening.

Now let's repeat this mental overhead for every file that I worked with, and I have immediately lost all the advantages of not having to use.value when working withref() invocations.

Conclusions

Given the current constraints of JS language and to provide backward compatibility, I struggle to see how the current design could be further improved.

However, if we do a cost/benefits analysis of this feature (removing the verbosity of.value) versus the additional cost paid by the developers, it quickly shows that the costs are largely outweighing the benefits.

The main issue of this proposal is the introduction ofyet another way of doing things in Vue and a further step into the fragmentation of the reactivity system and adopted conventions.

If we add to the mix the struggle the framework has gone through in the migration from Options API to Composition API, we have a deadly combination of multiple variations to achieve the same thing.

As I onboard a less experienced Software Engineer, or with no previous experience in Vue, I have to go through the following steps to successfully onboard them:

  • explain Options API
  • explain the Setup function
  • explain thescript setup
  • explainref and the presence of.value
  • explainreactive and the reactivity variable limitations
  • explainref vs$ref and the compiler optimization
  • explain wrapping$ and unwrapping$$
  • explain hidden compiler optimisation (defineProps,defineEmits)

The current proposal introduces additional complexity with a discrepancy between compile-time/run-time, which leaves the developer the responsibility of picking the correct solution.

The ultimate goal of a framework should be to abstract away from the developer that choice, and gracefully lead them towards theright way.
Unfortunately, this proposal brings us far away from that.

Suggestion

I understand that after the Vue 2 -> Vue 3 migration, considering yet another breaking change could be detrimental to the community and businesses using Vue, but I can't help but think that theref vsreactive design has led us to this situation where we need to think of odd ways to patch the issue using compiler workaround.

I like Vue due to its simplicity and elegance, and I would like the framework to continue building on those aspects.

Perhaps, what Vue needs, is to go back to the drawing board and consider a Vue 4 where bothref andreactive could be replaced/deprecated in favour of a new, consistent way to declare reactive variables.

In the meanwhile, simply accessingref.value seems a very small inconvenience to deal with.

You must be logged in to vote
7 replies
@jaireth
Comment options

It's funny because I subscribed to this thread for the same reason as everyone else - to find a better way. But after all this time, I've come to think that .value really is the lesser of all evils - and I can't help but think that that is why it was done this way in the first place.

@leopiccionia
Comment options

As soon as I started creating multiple modules and composable, the limitations of thefunction boundaries makes the initial syntax gains immediately decay.

I think an emerging pattern for authors of Vue composables is writing functions that can receive both Vue refs and plain values (and, sometimes, even a getter functions) as argument. VueUse, perhaps the largest publisher of composables in the Vue community,do that everywhere; it's also been mentioned on talks by Vue team members likeAnthony Fu andThorsten Lünborg.

Although it makes the life harder for authors of composables, it makes the life much easier for consumers of composables.

@jods4
Comment options

I think an emerging pattern for authors of Vue composables is writing functions that can receive both Vue refs and plain values

It should be pointed out that these are not equivalent alternatives!

If the composable only wants to read a value without taking dependencies (i.e. outside computeds/watches/effects) and doesn't want to write back into the parameter, then I think it shouldonly accept plain values, as normal JS functions do.

Assuming it wants reactivity, it should acceptRef orRef<T> | T but the latter isnot working the same way as the former.

So if you were gonna pass$$(x) to a composable,it is not equivalent to passx because that composable accepts both.

@floroz
Comment options

It's funny because I subscribed to this thread for the same reason as everyone else - to find a better way. But after all this time, I've come to think that .value really is the lesser of all evils - and I can't help but think that that is why it was done this way in the first place.

I really like the SolidJS approach to thecreateSignal, although it goes back to the approach of exposing agetter and asetter rather than working with a single variable that accepts direct mutations and can track changes.

// current approachconstcount=ref(0)count.value++constdoubleCount=computed(()=>count.value*2)// getter and setterconst[count,setCount]=createRef(0);setCount(c=>c()+1);constdoubleCount=computed(()=>c()*2);
@leopiccionia
Comment options

If the composable only wants to read a value without taking dependencies (i.e. outside computeds/watches/effects) and doesn't want to write back into the parameter, then I think it should only accept plain values, as normal JS functions do.

Apart from plain JS functions prefixed withuse* for aesthetic purposes, what's the use case? Indirectly callingref/reactive (for default values, etc.)? ReusingonMounted callbacks?

Another problem I didn't mention in my original comment: usually passing a ref is more useful than not, so it doesn't match the most intuitive usage, without$$(...).

Comment options

How about instead of the$$() function each$ref() or$computed() has a.ref to specify when you want to pass the ref instead of the value.

You must be logged in to vote
1 reply
@leopiccionia
Comment options

I think it could cause TS issues, likeT & { ref: Ref<T> } expected, T received or similar.

Comment options

Can we unify standards, such as $defineProps

You must be logged in to vote
0 replies
Comment options

Just heard@yyx990803 announcement at Vue.js Nation 2023.

Reactivity Transform will be dropped (but will be made available via a separate package to support compatibility for those who want to still use it).

It's great to see that the Core Team listened carefully to the feedback from this RFC 🥳 .

I believe it's the right call. The risk of further fragmentation with the release of this feature was simply too high.

You must be logged in to vote
3 replies
@rabbiveesh
Comment options

link to an article/video?

@floroz
Comment options

image

@gautemo
Comment options

Video link:https://youtu.be/OrT0tHGXyqE?t=844

Comment options

https://vuejsnation.com/You can join the conference and look for Evan's speech...Em qua., 25 de jan. de 2023 às 16:08, Veesh Goldman <***@***.***> escreveu:
link to an article/video? — Reply to this email directly, view it on GitHub <#369 (reply in thread)>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/AB5B65MRKDPJCZZON4OFLO3WUF24LANCNFSM5BR7T3KA> . You are receiving this because you commented.Message ID: ***@***.***>
You must be logged in to vote
0 replies
Comment options

Hey, I'm just one of those people who quietly started using this feature ever since it was introduced and I was hoping eventually it'd land in mainline Vue, but to my disappointment it seems like the opposite is happening. I probably won't be the single person saying this, but I found this feature made my Vue DX tenfold better than what it was, and not needing to remember to put.value everywhere saved me from hours of back and forth between the code editor and my projects. And it just looks so much cleaner and easy-to-read.

I'm not sure about the difficulties behind maintaining it, but writing code with Reactivity Transform to me felt natural and just resembled normal JS. I shipped many projects with it and all of them worked flawlessly, never encountered a single issue that was directly caused by Reactivity Transform.

So my opinion here is that removing it from Vue and dropping support wouldn't be too great. Especially since if you look at the npm downloads for the Reactivity Transform package it's been only going up ever since the release of this feature, reaching the 2 million threshold.

But that's just my 5 cents.

You must be logged in to vote
9 replies
@rodrigocfd
Comment options

some surpassing ~10k lines

Large projects don't mean complex projects. I can make you a 20k lines "hello world".

I shipped countless big projects with it

This is my very issue with Vue, you can never trust new stuff appearing because throwing it behind an experimental label makes it about 99% that will be removed in the future.

Sorry, but shipping experimental stuff in production is not a very responsible thing to do.

The fault is yours.

@TibixDev
Comment options

some surpassing ~10k lines

Large projects don't mean complex projects. I can make you a 20k lines "hello world".

I shipped countless big projects with it
This is my very issue with Vue, you can never trust new stuff appearing because throwing it behind an experimental label makes it about 99% that will be removed in the future.

Sorry, but shipping experimental stuff in production is not a very responsible thing to do.

The fault is yours.

I feel like your manner of speech is kind of accusatory and rude, it's not a good look. I'm just trying to take advantage of the good things Vue provides. I think the projects were complex enough, considering we are a team of 20 devs who were working over 6 months on it, having a bunch of external libraries, state management, complex transaction logic, and a whole bunch of other stuff. But this comment really isn't about me proving my point, if you choose to not believe it then that's on you, I was just trying to make my voice heard on this topic, and that's about the extent of it. I've worked with Vue for about 3 years now and I thought you people wanted to hear opinions that people have on your RFC-s.

@zam157
Comment options

You can tryVue Macros

@TibixDev
Comment options

You can tryVue Macros

Thanks, will probably keep using this if this really gets removed from Core, I guess at least there's a solution.

@yyx990803
Comment options

yyx990803Feb 21, 2023
Maintainer Author

This is my very issue with Vue, you can never trust new stuff appearing because throwing it behind an experimental label makes it about 99% that will be removed in the future.

There have been a total of four explicitly experimental features ever since the beginning of Vue:

  • <script setup>, which ended up going stable;
  • v-bind in<style>, which ended up going stable;
  • Suspense (still experimental)
  • Reactivity Transform (intention to drop)

Out of the four, two already became stable, and this is the very first time we are dropping an experimental feature. The point of the experimental label is exactly what it means: these are features that we need to ship and test in actual apps in order to further evaluate them. If it works, we keep them; if it doesn't, we drop them. In this specific case, it will be moved to a separate package like VueMacros, which is also maintained by a core team member.

You make it sound like you've experienced experimental features dropped before (which we factually know never happened) and the 99% exaggeration is just unnecessary FUD.

Comment options

You must be logged in to vote
3 replies
@phoenix-ru
Comment options

It is an experimental feature. I do not see why you consider migrating away from Vue a better solution instead of using the stable API and adapting your workflow to it.

@leopiccionia
Comment options

Sure,start here.

No need for being harsh. OP has a problem and is looking for a solution, not blame.

Anyway, this RFC may be likely supported inside theVue Macros project.

@tintin10q
Comment options

Yeah there will 100% be a package that still gives you what was proposed here so its fine.

Comment options

At this point, I start questioning Vue's perspective around Javascript labels to create reactivity like Svelte. ($:)

I remember that the main reason for not using it is the compatibility and lack of support with Typescript and other tools around the ecosystem.

But Vue's DX currently is heavily balanced around not native tools like VSCode with Volar. Or Vue Macros (currently in the early stage but with a promising perspective).@antfu 's repos shaped the standard for the next years, and with tools likeInspect orNuxt Dev Tools it is clear that intermediate states are more relevant and easier to debug every day. It seems to me that DX is leaning toward UI-based inspectors and IDE Integrations. Volar seems to be leading this adventure.

Seeing this thread, it's clear that a lot of jiggling needs to be made to provide a one-fits-all solution.

I believe an approach closer to compiling time will satisfy a lot of people that use Vue to create user-facing apps. Because:

  1. Vue already has a compiling pipeline around pre and post processors. The plugin system is already there.
  2. in the end, people that make component libraries or Integrations with other compilers will still use Vue's low-level API for performance reasons. I don't do these, if anyone do, please correct me if I'm wrong.

It doesn't have to be JS labels, but it seems that is the most native and friendly solution out there. Typescript macros are a good option too.

You must be logged in to vote
2 replies
@jods4
Comment options

"Closer to compiling time"? You might be misled by the JS-compatible syntax:$,$$,$ref and co.are compile-time transformations -- aka macros.

The main reason cited by the team for dropping this, AFAIK, is that it creates confusion at boundaries where state needs to be passed as a ref, or converted from a ref.

It seems to me you're debating the syntax: maybe labels$: would be nicer? but I don't quite see how a change in the declaration syntax is going to fundamentally change the confusion at function calls?

@davidboom95
Comment options

Yep, you are right, I misunderstood the problem.

Comment options

yyx990803
Feb 21, 2023
Maintainer Author

As many of you are already aware, we are officially dropping this RFC with consensus from the team.

The Rationale

The original goal of Reactivity Transform was to improve the developer experience by providing more succinct syntax when working with reactive state. We shipped it as experimental to gather feedback from real world usage. Despite the proposed benefits, we discovered the following issues:

  1. Losing.value makes it harder to tell what is being tracked and which line is triggering a reactive effect. This problem is not so noticeable inside small SFCs, but the mental overhead becomes much more noticeable in large codebases, especially if the syntax is also usedoutside of SFCs.

  2. Because of (1), some users opted to only use Reactivity Transform inside SFCs, which creates inconsistency and cost of context-shifting between different mental models. So, the dilemma is that using it only inside SFCs leads to inconsistency, but using it outside SFCs hurts maintainability.

  3. Since there will still be external functions that expect to work with raw refs, the conversion between Reactive Variables and raw refs is inevitable. This ends up adding one more thing to learn and extra mental burden, and we noticed this confuses beginners more than plain Composition API.

And most importantly, the potential risk of fragmentation. Despite this being clearly opt-in, some users have expressed strong objections against the proposal, the reason being that they fear that they would have to work with different codebases where some opted to use it while some not. This is a valid concern because Reactivity Transform entails a different mental model that distorts JavaScript semantics (variable assignments being able to trigger reactive effects).

All things considered, we believe landing it as a stable feature would result in more issues than benefits, and thus not a good trade-off.

Migration Plans

  • You can use thiscommand line tool to automatically revert the syntax to normal Composition API.
  • The feature is already supported in the form of an external package viaVue Macros.
  • 3.3: the feature will be marked as deprecated. It will continue to work, but you should migrate to Vue Macros during this period.
  • 3.4: the feature will be removed from core and will no longer work unless Vue Macros is used.
You must be logged in to vote
7 replies
@ArcherGu
Comment options

Although I enjoyed the convenience of this feature, I did find this potential fragmentation problem in actual use. Removing this feature in a future release may be reluctant, but engineers should be serious. 🙂

@sqal
Comment options

Are you removing all features or just the part with theref.value transformation? What aboutReactive Props Destructure, will it stay?

@Mondotosz
Comment options

I love this feature but I also understand why it needed to be removed from the official package.
Just migrated to Vue Macros I encountered some issues which I can't tell if I missed a change earlier or if it's a bug but I thought was worth mentioning here.

<script setup lang="ts">interfaceProps {    type?:'button'|'submit'|'reset';}const {type : propType='submit' }=defineProps<Props>();</script><template>    <button:type="propType">        <slot />    </button></template>

this snippet was valid when using Reactivity Transform before migrating but since using Vue Macros, I had to directly use type as the variable name in the template.
Before I was renaming my "type" prop to "propType" because I thought type was an invalid variable name.

@Subwaytime
Comment options

For everyone wondering what happens with the Reactive Props Destructure, Evan specifically mentions it in the Vue Nation Video here, that it will be seperated into its own Feature!
https://youtu.be/OrT0tHGXyqE?t=1098

@mon-jai
Comment options

For everyone wondering what happens with the Reactive Props Destructure, Evan specifically mentions it in the Vue Nation Video here, that it will be seperated into its own Feature!https://youtu.be/OrT0tHGXyqE?t=1098

Any news?

Edit: found it#502

Comment options

Hi all, I just stumbled across this and was surprised/disappointed to find that this proposal is being dropped. I've been using it for a moderately sized ecommerce site with no issues. I understand the rationale behind removing it, but in practice I found it to really be quite an improvement. So really my question is: "What now?"

I think the missing piece of this discussion is clarifying where we are at with the original motivation. Personally, I like to do things the "recommended" way, so I'd actually rather remove usages of the reactivity transform proposal from my project (which hasn't grown to the point where such a change is unfeasible—part of the reason I was comfortable with using an experimental feature to begin with) rather than starting to depend on an external package which exists solely to provide a deprecated feature. So I'm curious about what the recommendation is now, particularly in the context of the original motivation:

Ever since the introduction of the Composition API, one of the primary unresolved questions is the use of refs vs. reactive objects. It can be cumbersome to use .value everywhere, and it is easy to miss if not using a type system. Some users specifically lean towards using reactive() exclusively so that they don't have to deal with refs.

I agree completely with this. Are we now concluding that there is no good solution to this problem? Is the recommendation for those who hate.value now to just avoidref() if possible and usereactive(), as they did before? I'm not asking to start a fight, I'm just genuinely curious about where we are at with the original problem after all this.

You must be logged in to vote
5 replies
@zhenzhenChange
Comment options

.value is a necessary complexity.

Just like any other responsive libraryxxx.set().

@kiss758
Comment options

what's meaning

@CapitaineToinon
Comment options

It should be pretty easy to create a package that reverse all the reactivity transform code? Having this as a migration away from it would be really nice. I agree with Aaron here, I also like doing things the recommanded way.

@simmongod
Comment options

你可以在macro里继续使用它

@Xiatian-leo
Comment options

I like what@mcrapts said.

Don't let perfect be the enemy of good.

Comment options

Seems like the same complexity arguments apply to the in-template automatic transform. Will that also be going away?

You must be logged in to vote
16 replies
@tintin10q
Comment options

It really doesn't make sense to have the.value also in templates because can not really think of any reason why you wouldnt want the value of the ref in the template.

Also,.value in templates will never happen because it was not like that in the beginning and things will break.

@ahku
Comment options

It really doesn't make sense to have the.value also in templates because can not really think of any reason why you wouldnt want the value of the ref in the template.

Also,.value in templates will never happen because it was not like that in the beginning and things will break.

It makes sense for a new user who wasn't there at the beginning. So hard to explain "You have to access the value with .value, cuz that's how refs work! Wait why are you adding .value in the template, that's not what you're supposed to do! Refs are auto-unwrapped there, cuz it makes no sense to not use the value. Why Vue doesn't auto unwrap in the script tag as well? Well, we had this deprecated feature called Reactivity transform but..."

@floroz
Comment options

It makes sense for a new user who wasn't there at the beginning. So hard to explain "You have to access the value with .value, cuz that's how refs work! Wait why are you adding .value in the template, that's not what you're supposed to do! Refs are auto-unwrapped there, cuz it makes no sense to not use the value. Why Vue doesn't auto unwrap in the script tag as well? Well, we had this deprecated feature called Reactivity transform but..."

It is not hard at all given that there is exhaustive documentation material onReactivity Fundamentals where this concept is explained clearly and simply.

You should expect any new user toat least read the documentation of the tool they intend to use.

@ahku
Comment options

It makes sense for a new user who wasn't there at the beginning. So hard to explain "You have to access the value with .value, cuz that's how refs work! Wait why are you adding .value in the template, that's not what you're supposed to do! Refs are auto-unwrapped there, cuz it makes no sense to not use the value. Why Vue doesn't auto unwrap in the script tag as well? Well, we had this deprecated feature called Reactivity transform but..."

It is not hard at all given that there is exhaustive documentation material onReactivity Fundamentals where this concept is explained clearly and simply.

You should expect any new user toat least read the documentation of the tool they intend to use.

Why are you angry? I've read through the documentation multiple times and am a seasoned Vue user 😆 But I won't pretend that this isn't one of the beginner issues of Vue 3 that didn't exist in Vue 2, not saying that Vue 2 wasn't without its fair amount of caveats. You can't expect beginner devs to know everything or learn everything at the same pace. I love reading docs, but others reflexively turn to tutorials first or video lessons and avoid docs like cats avoid water.

@alivedise
Comment options

It is not hard at all given that there is exhaustive documentation material onReactivity Fundamentals where this concept is explained clearly and simply.

You should expect any new user toat least read the documentation of the tool they intend to use.

From your sentence, figure out a new API which is not.valueis not hard. either. You are talking about _Existence is reasonable_; "because the document writes it, so it is best and no any problem". I don't think this is correct.

Comment options

@yyx990803 what about reactive props destructure:https://vuejs.org/guide/extras/reactivity-transform.html#reactive-props-destructure ? Would it remain in the core?

You must be logged in to vote
2 replies
@jods4
Comment options

He said during the conf that this specific part will be kept in core.

@emosheeep
Comment options

Really? I didn't watch the vue conf. but I'm searching for it lately, I want to useprop destructure feature in my project because it's really convenient, and I found the official vue plugin has built-in it:

vue({script:{propsDestructure:true,},}),
Comment options

You must be logged in to vote
2 replies
@martinszeltins
Comment options

How can we distinguish whether an API is experimental or not? I did not see any indication that the 'reactive' API was experimental in the official documentation, but one day, I suddenly saw in the community that this API was experimental and had been deprecated.

No, it wasn't theReactivity API that was experimental but theReactivity Transform. It showed both in the CLI when the dev server was run ("Reactivity Transform is an experimental feature that might change in the future...") and also in thedocumentation.

@zuibunan
Comment options

Sorry, I got it wrong. I have deleted the comment

Comment options

If you don't want to use Reactivity Transform anymore, here is a tool that helps you drop reactivity transform in seconds.
https://github.com/edison1105/drop-reactivity-transform

You must be logged in to vote
0 replies
Comment options

Having to constantly deal with.value in Vue is the biggest nuisance of this framework. And that's coming from someone who's worked with composition API for several years now. Giving up on trying to solve this problem, especially with the upcoming Vue 4, was a mistake.

You must be logged in to vote
4 replies
@Fuzzyma
Comment options

Thats your opinion. I dont mind it at all. It gives me a clear sign where something is coming from. Hence I like it

@rodrigocfd
Comment options

I agree. That's why I avoidref at all costs, usingreactive instead:

conststate=reactive({names:[]asstring[];});

Then we don't need.value:

state.names.push('Joe');

That's said, this reactivity macro was not good. I'm glad it was dropped.

@jacekkarczmarczyk
Comment options

I don't see howstate.names is better thannames.value (not to mention that in the template you'd still need to writestate.names and with ref it would be justnames)

@snakeshift
Comment options

flutter_hooks, the most used state management package in flutter, is similar in principle to react hooks, but has a policy of updating by .value.
https://pub.dev/packages/flutter_hooks#how-to-create-a-hook

flutter also has signal, which is also based on .value.
https://pub.dev/packages/signals

I feel that libraries that update with setState() are rather rare nowadays.

Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Labels
sfcSingle File ComponentsRFC
176 participants
@yyx990803@tslocke@transtone@comfuture@earthboundkid@Doeke@glen-84@Meesayen@chanon@cexbrayat@manigandham@alivedise@patak-dev@sevein@posva@ennioVisco@alexcroox@ElMassimo@kktos@bitsnaps@bodograumannand others

[8]ページ先頭

©2009-2025 Movatter.jp