Closed yyx990803 closed 5 years ago
I think that's a great way to mitigate the confusion around value()
. I personally didn't have a problem with value()
but introducing this distinction between reactive objects and simple bindings makes a lot of sense.
I was also hesitant about the use of 'value' (because it is also used as the reactive property of a now-binding) and 'state' (because it is very commonly used as a variable name), so I think this is a welcome change. Personally I'm really excited about this API.
Beyond that though, I question why there are separate toBindings
and reactive
functions. As an alternative, can this simply be a second argument to reactive
? i.e.
const state = reactive({
count: 0
}, true) // setting to true wraps each member in a binding and allows the object to be spread and retain reactivity
Is there a use-case where you would expose the whole reactive object as a binding as well as its members? i.e. why might someone do this?
return {
state,
...toBindings(state)
}
I can't see the advantage of an extra function other than "just in case".
Another drawback which I've seen raised, which is closely related to this API (i.e. exposing the reactive variables to the render context) is that this is more verbose because of the need to 1) declare the variables and then 2) expose the variables. This is a very small thing, so it's certainly no deal-breaker, but is there a way around this?
I asked this in the other thread actually but it got lost (it relates directly to this API):
A few more questions that aren't clear to me from the RFC:
How "deep" does the reactive()
function actually make the object reactive? e.g. is it just one level deep (the immediate members of the object)
Does the reactive()
function make an array and/or the members of an array reactive (including push, pop, accessing a member, accessing the property of a member if it were an object, etc.)?
@tigregalis With reactive
, your data is already an object (not a primitive), so you don't need xxx.value
when using it in the script.
@tigregalis if you directly create an object of bindings, you'd have to access internal values as state.count.value
. That defeats the purpose.
@akryum I'm not sure what you mean, sorry. My comment was more around the ergonomics of spreading and exposing reactive state to the render context.
Wouldn't toBindings
completely eliminate the need for binding
function and leave us with just reactive
?
Also, I personally find it very frustracting that you have to remember which one is which and always keep that in mind when working with reactive values. React has solved this very elegantly with two exports: getter and a setter. I'd much rather have this, then constantly check if I'm working with a binding or with a reactive object.
const [counter, setCounter] = toBinding(0);
const increment = () => setCounter(++counter);
return { counter, increment };
counter
in that case is an object with a valueOf
property, that is reactive.
@CyberAP
The need for binding()
is already mentioned:
- returning values from computed() or inject(), since they may contain primitive values, so a binding must be used to retain reactivity.
- returning values from composition functions.
- exposing values to the template.
In your example, counter
is a plain value and cannot retain reactivity when returned. This would only work if the whole setup is invoked on every render - which is exactly what this RFC is avoiding.
@yyx990803 I haven't worked with proxies so excuse my ignorance, but is it possible to forward the get
/set
of state.count
to state.count.value
?
@tigregalis spread internally uses get
. So when forwarding you break the spread (and thus disconnect the reactivity)
Yes, I've forgotten to add that counter
is an object with a valueOf
property that provides an actual reactive value. @LinusBorg said that it has been discussed internally but there's been no feedback on that proposal since.
Maybe with a valueOf
we can somewhat mitigate the need to use .value
to access reactive value?
const counter = binding(0);
console.log(counter); // uses valueOf()
const increment = () => counter.value++; // as initially proposed
return { counter };
@CyberAP binding
may also need to contain non-primitive values so I don't think valueOf
would cover all use cases. It's also too implicit - I'm afraid it will lead to more confusions than simplification.
What if this
return {
...toBindings(state), // retains reactivity on mutations made to `state`
double,
increment
}
would automatically be done if you did this:
return {
state, // retains reactivity on mutations made to `state`
double,
increment
}
ie. if you directly set state in the object
@dealloc we did discuss that option internally, but overall we want to avoid "magic keys" and be as explicit as possible.
@yyx990803 could you please elaborate more on the non-primitive values in binding
? What's the usecase for this when we have reactive
? Except for use in an object spread within toBinding
helper.
Not a very mature thought, but since const state = reactive({ a: 0})
is going to be more recommended than const state = { a: binding(0) }
, how about (and is it possible to do) this:
import { reactive, computed, injected, merge } from 'vue'
setup() {
const state = reactive({
a: 0,
b: 1,
})
// computed accepts an object, not function,
// so that the returned computedState doesn't need to be wrapped into `computedState.value`
const computedState = computed({
c: () => state.a + 1,
d: () => computededState.c + state.b,
})
// same for injectedState
const injectedState = injected({
e: ...
})
// { state: {a, b}, computedState: {c, d}, injectedState: {e} }
return { state, computedState, injectedState }
// or
// merged into { a, b, c, d, e }, still reactive, no .value needed.
return merge(state, computedState, injectedState)
}
If this is feasible, I can see two major advantages:
reactive
computed
inject
things together if they like, and at the same time allowing some other people to call reactive
computed
multiple times to group logic in features.@CyberAP as mentioned, anything returned from a computed property / dependency injection has to be a binding to retain reactivity. These bindings may contain any type of values.
@CyberAP Also there is value in using bindigs.
Take those examples:
https://github.com/vuejs/function-api-converter/blob/5ac41d5ad757f1fb23092e33faee12a30608a168/src/components/CodeSandbox.vue#L52-L53 https://github.com/vuejs/function-api-converter/blob/5ac41d5ad757f1fb23092e33faee12a30608a168/src/functions/code.js#L18
It would make such usage more complicated since we would have to pass the entire object to keep reactivity. And also document each time what keys should be used or even provide accessor "callbacks"...
A blind man asking what might be a stupid question......
If the objective is to make both object and primitive (and non-primitive) assignments reactive, couldn't it be just one method for both and have the method....reactive()
(???).....type check what is being offered as an argument and do it's reactive magic accordingly? I think the whole idea of data()
being split up into two different things is the confusing and seemingly unnecessary addition. :smile:
Btw, I love you are trying to make the value and state methods a bit more elegant. Thanks for that!!!
Edit: Oh, and if it is possible, then the toBinding
method could be maybe something like stayReactive
. Ah, naming is one of the hardest things to do in programming. :grin:
Scott
@yyx990803 But if it were possible(?):
The object would be represented by:
// `state`
{
count: {
value: 0
}
}
In the setup()
you could do state.count++
because it would effectively be running state.count.value++
. After setup, the increment()
method would still have a reference to state
.
After spreading the state
object in the return of the setup()
, you break the reactivity of state
in the render context, but its member count
would still be reactive in the render context because it's internally represented by { value: 0 }
and accessed by its value
property.
So in the component template, you could still do count++
because Vue would unwrap it into count.value++
.
Does any of that sound right?
@beeplin this seems to create more API surface (another category of "options" for the return value of setup()
). The user can already do this if desired:
setup() {
const state = reactive({ ... })
const computeds = {
foo: computed(...),
bar: computed(...)
}
const injected = {
baz: inject(...)
}
const methods = {
qux() {}
}
return {
...toBindings(state),
...computeds,
...injected,
...methods
}
}
A merge
helper was also considered, but the only thing that really needs special treatment is reactive state. With merge
it's less obvious as to why we need to merge objects like this (why can't we just spread? why can't we just use Object.assign
?), whereas with toBindings
the intention is clearer.
@tigregalis with getter forwarding, when spread into the render context, count
is no longer an object. It's just a plain number that no longer has anything to do with the original value wrapper.
Put it another way - the render context only receives a plain number (instead of a "binding", which is trackable via the .value
access) so the render process won't be tracking anything.
@yyx990803 Yes I know the 'grouping by type' thing can be done like you said. But more importantly:
- Eliminate the concept of ".value wrapper" at all from the new API. Much simpler.
If we make computed()
and inject()
accept an object rather than a function/bare value, we could just eliminate the need for the 'value wrapper' concept -- every reactive thing must be in an object, and no need to use .value
wrapper to keep reactivity when spreading/passing around.
So, no .value
, no binding()
, no toBinding()
... just one more merge()
.
I don't think I am an expert on proxy or JS reactivity, so I might be wrong.
In that case I'm thinking that reactive
is now more confusing, since most of the time we'll be working with bindings, extracting and sharing logic between components. These will always return bindings and they are actually the core of the new reactivity, not the reactive
. I understand that for those who migrate from 2.x constantly using .value
to get and set values would be irritating, but maybe it's less irritating than getting confused between of those two. The main point of confusion is not that you have to deal with .value
, but with choosing between of those two. I can easily imagine lots of questions about why this doesn't work. So maybe getting rid of reactive
can solve this?
import { reactive } from 'vue';
export default (counter) => {
const data = reactive({ counter });
return { ...data }
}
@beeplin that won't work when you start to extract logic into composition functions. No bindings means you will always be returning objects even in extracted functions - when you merge them you won't see the properties they exposed, and it becomes mixins all over again.
@yyx990803 would it be technically possible (keeping all the reactivity, TS support etc) to create a reactive data sets that contain computeds and methods as well?
const state = reactive({
count: 1,
get doubled() {
return state.count * 2
},
increment: () => state.count++
});
return state;
// or
return {
...toBindings(state),
...injected,
...
}
@jacekkarczmarczyk what problem would that solve though? I feel like I'm missing the point
edit: made wording more neutral
For me it seems more logical to group related things in one object instead of declaring separate variables/methods. Such object could be also used outside of the component scope (including tests)
We still need to use .value
in these situations:
People do will excessively use of composition functions, which be equivalent to excessive use of value
, which we don't want. toBindings
could not help much here, but introduce another fatigue.
setup() {
const state = reactive({
count: 1,
});
const double = computed(() => state.count * 2)
return {
should I use toBindings, both seem to work. help me... ? state : ...toBindings(state),
double,
};
}
@yyx990803 Ah, I see what you're saying. Thanks.
Alternative proposal then. Instead of ...toBindings(state)
, use ...state.toBindings()
. It doesn't seem like much, but it's one less import, for a function that only ever does one thing, on only ever one type of argument. I guess the disadvantages are that it's not tree-shakeable (but given how frequently you're likely to use it, how often would it be omitted?) and less minifiable (can't reduce to ...f(x)
, best case ...x.toBindings()
).
@tigregalis Don't think state.toBindings()
has any advantages. Also your IDE should auto import it!
I'm still not 100% sold on the ...toBindings(state)
story.
I think @yyx990803 might have misunderstood my comment, I did not mean that Vue would automatically unwrap the state
key, I meant that Vue would automatically call toBindings for all keys where needed, hiding the implementation detail of toBindings
to the Vue internals.
@dealloc that would work, but here's something that you could do:
setup() {
const primitiveCounter = 0;
const counter = binding(primitiveCounter);
const primitiveIncrement = () => primitiveCounter++;
const increment = () => counter.value++;
return {
primitiveCounter, // internally made reactive, exposed to the render context, so you could call `primitiveCounter++`
counter, // already reactive
primitiveIncrement // has reference to internal non-reactive primitive value, calling it will update the internal value but not change the reactive value
increment, // has reference to reactive counter object
}
}
Could be a source of confusion.
@dealloc we did discuss that option internally, but overall we want to avoid "magic keys" and be as explicit as possible.
@yyx990803 I don't think automatically handling reactive
objects directly returned from setup()
is any more "magical" than the current data
property on 2.x. You are explicitly creating the object with reactive({})
and you are explicitly returning it, that is explicit enough for me and I would expect for it to remain reactive. The fact that Vue has to call ...toBindings(state)
under the hood is an implementation detail.
Also, I cannot think of a reason why you would want to return a reactive
object from setup()
and have it lose its reactivity. But, if there is such a use case, having to call markNonReactive
is preferred to calling ...toBindings
all the time.
@dealloc
On second reading, those are different ideas...
Your example was
return {
state
}
But I thought you meant
return {
...state
}
You're saying any reactive
objects that are returned from setup
are automatically spread? I don't quite like that.
I think one of the problems is the logical disconnect between the template and the setup function.
<template><button @click="inc">{{ count }}</button></template>
<script>
export default {
setup() {
const counter = useCounter();
return { ...toBindings(counter) };
}
}
</script>
what this actually means is something like this:
export default {
setup() {
const { count, inc } = useCounter();
return COMPILE_TEMPLATE_TO_RENDER_FUNCTION(`<button @click="inc">{{ count }}</button>`);
}
}
the template is a closure inside setup and has access to its local variables.
edit: and here is what actually is produced:
const render = COMPILE_TEMPLATE_TO_RENDER_FUNCTION(`<button @click="inc">{{ count }}</button>`);
export default {
setup() {
const { count, inc } = useCounter();
return render({ count, inc });
}
}
i find the closure variant much more appealing
JSX turns HTML like syntax into a series of functions calls. what we need for vue is a system that turns HTML like syntax into a closure (function).
@jacekkarczmarczyk @dealloc I'm actually in favor of being able to define getters/methods within reactive
. I see it (personally) as a sort of semantic middleground between object api and function api (just posted my thoughts on that), since the way it reads is more declarative. I of course have no clue implementation feasibility or if there are other gotchas.
@dealloc
I don't think automatically handling reactive objects directly returned from setup() is any more "magical" than the current data property on 2.x. I also imagine that in smaller components, tutorials, etc there might only be one anyways (ie, if there's no logical grouping to be done). This is an even closer analogue to 2.x if you can define other things on
reactive
.
@backbone87 I imagine this should be an optional stylistic thing - per the other stuff I've been responding to, there are some cases where you might just be returning one thing, especially if it's possible to throw everything onto one reactive
for a small component.
Makes me wonder if you could define everything on one reactive, whether you could just return a reactive.
@yyx990803
I am concerning that there comes considerably asymmetry among reactive
, computed
and inject
. Conceptually, computed
, inject
are similar to binding
because they all return a binding object with .value
. When people need to access them in other parts of setup()
, it is consistent to remember adding .value
after them. But since you are planning to make reactive
the first-class API prior to binding()
, it feels somehow strange because people have to remember that they can access state.xxx
directly but have to do computedResult.value
. (People can access a computed value as frequently as a state value in setup(), like in other computed functions, watch functions and life cycle hooks.)
IMO, it would be better either using .value
in all cases, or in none.
That's why I consider if there is a way to require people to always wrap their reactive values (state, computed, inject, all composition functions, etc.) in objects in order to get rid of the puzzling .value
binding completely.
The following part might make no sense. ;)
you will always be returning objects even in extracted functions - when you merge them you won't see the properties they exposed, and it becomes mixins all over again.
As far as I know at least in VSCode (both for TS and JS), type inference will be working for functions returning objects.
So we may have some users who prefer use binding()
to achieve consistency with computed()
and inject()
(always using .value
), while some other users might like to write like this (wrap all in objects and use the objects as namespaces) to avoid .value
:
import { computed, createComponent, reactive } from "vue";
function useA() {
const state = reactive({
a: 1
});
const computedState = computed({
b: () => state.a + 1
});
return { state, computedState };
}
const component = createComponent({
setup() {
const { state: stateFromA, computedState: computedStateFromA } = useA();
const state = reactive({
a: 1
});
const computedState = computed({
b: () => state.a + 1
});
return { stateFromA, computedStateFromA, state, computedState };
}
});
So, I wonder if it is desirable to suppport both styles, by making computed
and inject
support two kinds of parameters: when passing an object to them, no need to convert it into a .value
wrapper; when passing a function/non-object value, convert it into a wrapper.
Again, maybe I am making things complicated too much. In short, I prefer keep consistency when deciding whether to use .value
.
EDIT: I am not advocating getting rid of .value
by forcing people to wrap all things in objects. In fact I prefer the original proposal before this amendment - making value
(binding
) the first-class API and let people always use .value
.
@backbone87 Is that how that works? I think it's rather that what you return has access to what gets exposed (returned) from the closure and it is sort of a curated API, and the render function has access to that API but not the closure itself. Otherwise, if the render function had direct access to the closure (i.e. was inside the closure), then based on that approach, you wouldn't need to return anything at all from setup
really.
@tigregalis from what i know, vue compiles a template to a render function, that receives a context (this) and arguments. it has only access to a few known "global" functions, everything else is passed in. what i think will be much more benefitting is actually creating a closure from a template inside setup. this most likely requires a build step (like TS).
export default {
setup() {
const { count, inc } = useCounter();
return () => h('button', { onclick: inc }, count);
}
}
you could replace the h
call with JSX, (because JSX replaces the markup with h calls).
but a much better DX (imho) would be to convert markup into a closure itself: this should compile exactly to the code above.
export default {
setup() {
const { count, inc } = useCounter();
return COMPILE_TEMPLATE_TO_CLOSURE_AT_BUILDTIME(`<button @click="inc">{{ count }}</button>`);
}
}
what happens now is:
const render = COMPILE_TEMPLATE_TO_RENDER_FUNCTION(`<button @click="inc">{{ count }}</button>`);
export default {
setup() {
const { count, inc } = useCounter();
return { count, inc }; // vue passes this to the render function
},
render,
}
I personally feel that having to remember to use toBindings
will add a cognitive burden which is not on par with Vue's mission. My thought process is similar to this:
I create my state with const state = reactive({...});
Then when I'm about to return it from the setup, I just spread my reactive state with
return {
...state
};
Because that just feels right. Suddenly I get bugs about values not updating because I lost reactivity from something my brain thinks it's reactive because I explicitly told that object to be reactive.
As much as I am quite happy with the funcional RFC, this very issue would be a total blocker for me and I would discard it due to the confusion and possible negative impact it has.
Yeah, as much as I like the function-based API, I agree that this is getting confusing.
Actually, I think that at this point it'd make more sense if there only were value wrappers, so you'd always need .value
. (within the JS parts)
This might seem less ergonomic (why do I need the .value?!?1?!), but makes the rule way easier to reason with:
.value
when accessing something within <script>
.value
when accessing something within <template>
That rule is very very similar to what we have right now with the current API:
this.
when accessing something from within <script>
this.
when accessing something from within <template>
Not really sure about all of this though, feels like there just isn't a perfect solution to this.
I agree with @jonaskuske while it might seem annoying at first having to use .value
, it'll be far more consistent which seems better in the long run.
When I was playing around with the functional syntax, the .value
didn't even bother me once I got used to it, I was more annoyed that the name state
was already taken and I could not assign a variable to it
@dealloc
I was more annoyed that the name state was already taken and I could not assign a variable to it
I mean, that at least is solved by renaming state
to reactive
, as is proposed in this amendment.
For now, you could already do
import { state as reactive } from "vue-function-api"
@jonaskuske Indeed, though I'm personally not 100% convinced about the new names.
That might just be that I got myself used to value
and state
, and even if I disagree in the end as you said I could always rename my imports ;)
Besides, I couldn't come up with a better name myself so it doesn't bother me as much
Knowing to use toBindings
just for state (even if in one place) might be even less intuitive than remembering .value
(I know these are completely different, just comparing). At least if I assign a value wrapper to a const
I will get errors when trying to assign to it without .value
, and probably clear runtime errors as well if I forget .value
when trying to use the value. With toBindings
you need to be aware of the helper and understand more about how reactivity works first. Linters can help of course, but it throws no errors otherwise. In any case, it defies what might be expected, so it's actually not a bad place to add some runtime "magic," if possible.
The upside of the helper is that it makes it very clear and explicit what is happening (so if it ends up in the API, I probably wouldn't mind). It's a solution, but it will always feel similar to having to use $vm.set()
for a reactivity corner case.
Some rough ideas/questions
Is there no way to make a Not sure why I thought this would work with object spread ...reactive
object spreadable (where it returns wrapped values) by implementing Symbol.iterator? I know with Proxies this is tricky (perhaps not possible without modifying the Proxy prototype globally).
Not sure if this would work, but another idea I had was when creating reactive
, keep an object with the binding values assigned to some symbol on the object, like Symbol('REACTIVE'). When spread, if the returned object from setup contains that symbol, it takes the values there and spreads them again overwriting the non-reactive values with reactive ones. Downsides: this wouldn't solve returning spread or destructured reactive objects from useXXX function, which could be confusing.
Or if there is no solution there, then does it work to do return Object.assign(data, { ...computed, ...methods })
. i.e. more a of a pattern of assigning everything to reactive state and returning that. If that does work, I'm not sure it's any different than using a helper.
The strange thing is that value
is straightforward, while state
is meant to be ergonomic to use by unwrapping values, but by unwrapping values, state then necessitates a helper to use it the way you'd expect. Almost indicates that it's not worth having state at all. I don't want to advocate for removing state, but if you think about it, the API might actually be less confusing without it. Is there a broader advantage to using state
/ reactive
that I have missed?
Update: the main use case I see for state is actually in creating a Vuex-like store in which case I would write my own helper that exposes everything in the state as readonly computeds. State wouldn’t be exported or spread at any point in that case.
(sorry for the long comment, just trying to think through this new api ...)
EDIT: toBindings()
do it for top-level property and can use toBindings()
in user recursively function
May be use reactiveDeep()
that is recursively converted object to reactive()
for reference properties and binding()
for primitives properties. And use value for all primitives in setup().
For example:
setup() {
const state = reactiveDeep({
count: 0
subObject: {
foo: 'bar'
}
})
// where reactiveDeep() is:
// const state = reactive({
// count: binding(0)
// subObject: reactive({
// foo: binding('bar')
// })
// });
//error:
const double = computed(() => state.count * 2) // work if return {state}, but does not work if return {...state}
//success:
const double = computed(() => state.count.value * 2) // work if return {state} or {...state}
//error:
function increment() {
state.count++ // work if return {state}, but does not work if return {...state}
}
//success:
function increment() {
state.count.value++ // work if return {state} or {...state}
}
return {
state, // works because reactive() was used for object
double,
increment
}
//or
return {
...state, // works because binding() or reactive() was used for every property in object
double,
increment
}
//or
return {
state.subObject,
double,
increment
}
//or
return {
...state.subObject,
double,
increment
}
}
You can replace reactive()
with reactiveDeep()
and add reactiveLazy()
instead of reactive()
.
Did not find a solution for watch(state, value => {...})
if return {...state}
P.S. Sorry for the bad language and if I do not understand the work of this API and the proxy
I could be missing something but what's the problem with auto-detect we are exposing a reactive object to the render context and let Vue unwrap it to avoid nested structures? Example:
setup () {
// reactive() function could simply write a read-only flag for internal uses that
// mark this object as reactive (for example, $$isReactive = true)...
const state = reactive({
count: 0
})
const plusOne = () => state.count++
// ... then, Vue internals, can unwrap the state for us an let us use <div>{{ count }}</div>
// in the template.
return { state, plusOne }
}
Honest question: why this wouldn't work?
Honest question: why this wouldn't work?
Because it's doing something you didn't ask to. What if you have more state objects? What you're actually pretending to use it as <div>{{ state.count }}</div>
?
Well, it's not doing something you didn't ask to. I mean, you are creating a reactive object. Passing that object to render context has its (desired) consequences that IMO you should know. It's not like you don't know what you are doing.
If you would like to pass a non-reactive object, simply don't use the function.
Or, in case you want to pass an object that has reactive properties, you have the binding()
function.
const state = {
count: binding(0)
}
return { state }
This last example would accomplish your last example <div>{{ state.count }}</div>
This is a proposal for an amendment to RFC #42. I'm posting it here separately because the original thread is too long, and I want to collect feedback before updating the original RFC with this.
Please focus on discussing this amendment only. Opposition against the original RFC is out of scope for this issue.
Motivation
This update aims to address the following issues:
value()
is a concept that objectively increases the learning curve compared to 2.x API.value()
in a single-purpose component can be somewhat verbose, and it's easy to forget.value
without a linter or type system.state()
makes it a bit awkward since it feels natural to writeconst state = ...
then accessing stuff asstate.xxx
.Proposed Changes
1. Rename APIs:
state()
->reactive()
(with additional APIs likeisReactive
andmarkNonReactive
)value()
->binding()
(with additional APIs likeisBinding
andtoBindings
)The internal package is also renamed from
@vue/observer
to@vue/reactivity
. The idea behind the rename is thatreactive()
will be used as the introductory API for creating reactive state, as it aligns more with Vue 2.x current behavior, and doesn't have the annoyances ofbinding()
(previouslyvalue()
).With
reactive()
now being the introductory state API,binding()
is conceptually used as a way to retain reactivity when passing state around (hence the rename). These scenarios include when:computed()
orinject()
, since they may contain primitive values, so a binding must be used to retain reactivity.2. Conventions regarding
reactive
vs.binding
To ease the learning curve, introductory examples will use
reactive
:In the template, the user would have to access the count as
{{ state.count }}
. This makes the template a bit more verbose, but also a bit more explicit. More importantly, this avoids the problem discussed below.One might be tempted to do this (I myself posted a wrong example in the comments):
The spread would disconnect the reactivity, and mutations made to
state
won't trigger re-render. We should warn very explicitly about this in the docs and provide a linter rule for it.One may wonder why
binding
is even needed. It is necessary for the following reasons:computed
andinject
may return primitive values. They must be wrapped with a binding to retain reactivity.It is recommended to return bindings from composition functions in most cases.
toBindings
helperThe
toBindings
helper takes an object created fromreactive()
, and returns a plain object where each top-level property of the original reactive object is converted into a binding. This allows us to spread it in the returned object insetup()
:This obviously hinders the UX, but can be useful when: