-
-
Notifications
You must be signed in to change notification settings - Fork 8.4k
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
Stable mutation of reactive arrays containing refs #737
Comments
Curious what is the use case that led you to this? Why would you want to know if the underlying value is a ref or not? |
I was playing around with the composition API, building a todo list where you can add items created by other sources (like const { x, y } = useMousePosition();
const list = ref([x, y, 'hello', 'world']); Or in this case, where one item in the list is updated from a |
I'm starting to think that Arrays should not automatically unwrap refs it contains. With ref unwrapping it becomes very complicated to ensure expected behavior for all the built-in Array methods. For example, the following could be ambiguous: const n = ref(2)
const arr = [1, n, 3]
const mapped = arr.map(v => {
// should v be the unwrapped value or the ref?
}) In addition, even if we special case these methods internally, it would still break when the user attempts to use external utility libs like lodash on a reactive array containing refs. Currently, native collections like I think in practice the cases where you actually want to mix refs in an array with normal values seem to be quite rare, so maybe ref unwrapping should be an object-only trait. The upside is that all Array methods (internal or external) will work as expected; The downside is when you want to display the array you'll need too create a computed property for it: const displayArray = computed(() => sourceArray.map(e => isRef(e) ? e.value : e)) |
/cc @jods4 would be interesting to hear your thoughts on this. |
This is tricky stuff, there are no perfect answer for all cases. I would set some design principles for a successful API:
Magically unwrapping -- even with objects (which is gonna be more common than arrays IHMO) -- is a tricky proposal. Am I passing the value or the reactive reference around? It's gonna depend on the case, so no answer is always right. Which one is more common? How easy is it to access the non-default one? Special casing built-in methods is a terrible idea and this I'm sure of. It strongly violates 1. Why am I getting refs when doing I've taken some time to think about it and here's what core apis I would do: No magic, never auto-unwrap in my own code, i.e. both arrays and objects. Better perf and keeps the mental model predictable and simple (what you do is what you get). Manipulating a value that might or might not be reactive (i.e. a ref) is a fairly common thing, esp. in generic (library) code. So It is fairly common to return from (Unsure about this one:) it might(?) be handy to have access to That would be my core, you can offer more convenience functions if you'd like: for example an auto-unwrapping object/array, so that you can merge refs (maybe from mixins), into an easy to use state object. |
BREAKING CHANGE: reactive arrays no longer unwraps contained refs When reactive arrays contain refs, especially a mix of refs and plain values, Array prototype methods will fail to function properly - e.g. sort() or reverse() will overwrite the ref's value instead of moving it (see #737). Ensuring correct behavior for all possible Array methods while retaining the ref unwrapping behavior is exceedinly complicated; In addition, even if Vue handles the built-in methods internally, it would still break when the user attempts to use a 3rd party utility functioon (e.g. lodash) on a reactive array containing refs. After this commit, similar to other collection types like Map and Set, Arrays will no longer automatically unwrap contained refs. The usage of mixed refs and plain values in Arrays should be rare in practice. In cases where this is necessary, the user can create a computed property that performs the unwrapping.
Arrays will no longer unwrap refs after 775a7c2 |
I'm currently using const testObj = { f: ref(0) };
const testArr = ref<any[]>([]);
testArr.push(testOj);
console.log(isRef(testObj.f)) // true
console.log(testArr.value[0] === testObj) // false
console.log(isRef(testArr.value[0].f)) // false I don't expect this behaviour. I only want to watch for new items in array. If I'm doing something wrong, I would be very thankful for pointing it out! |
100% agree with @jods4 I think even the <template>
<div>
{{ name }}
</div>
<div>
{{ person.name.value }} <- value ??
</div>
</template>
<script>
export default {
setup() {
return {
name: ref(''),
person: {
name: ref('')
}
}
}
}
</script> really threw me off when I came across it. What I liked about Vue in the first place is that things are very intuitive. Having to memorize that one case works one way and the other differently is the opposite of that and seriously risks the success of the composition api imo. Same goes for objects, please don't unwrap them automatically either. Types get unnecessarily complicated really fast too with these const field = <T>(value: T) => {
const v = ref(value); // type of v here is Ref<UnwrapRef<T>> what??
return {
v,
touched: ref(false)
}
}; and if I want to type the return type of this function (which I do) I don't want to have to type |
@yyx990803 This doesn't quite work with object arrays btw. Here's what I mean: const item = {
prop: ref('')
};
const array = ref([ item ]);
array.value.push(item); // <- the type checker complains |
Refs do not unwrap in a reactive array or nested array of a reactive object since v3.0.0-alpha.6 version. See issue: vuejs/core#737
Refs do not unwrap in a reactive array or nested array of a reactive object since 3.0.0-alpha.6 version. Hence, there are not limitations. See issue: vuejs/core#737
Refs do not unwrap in a reactive array or nested array of a reactive object since 3.0.0-alpha.6 version. Hence, there are not limitations. See issue: vuejs/core#737
Refs do not unwrap in a reactive array or nested array of a reactive object since 3.0.0-alpha.6 version. Hence, there are not limitations. See issue: vuejs/core#737
Refs do not unwrap in a reactive array or nested array of a reactive object since 3.0.0-alpha.6 version. Hence, there are not limitations. See issue: vuejs/core#737
Refs do not unwrap in a reactive array or nested array of a reactive object since 3.0.0-alpha.6 version. Hence, there are not limitations. See issue: vuejs/core#737
What problem does this feature solve?
Any mutation in a reactive array that changes the position of a ref does not work as expected. Instead of moving the entire ref object only the values are moved.
For example, if we start with this array:
Calling
reverse
on this object will swap the values, but leave the ref wrapper at index2
:The solution is to first use
toRaw
to get the underlying object. This doesn't seem like a great user experience, and it isn't obvious at all that this is the solution. Documenting it as a best practice could work, but I don't think it would be that difficult to do this automatically for the user.If we add all (or most?) Array methods that cause a mutation to this base handler I think we can avoid this issue altogether. (Although it appears that
toRaw
might be a lot slower?)I put together a live demo as well: https://codesandbox.io/s/recursing-wood-2ovm5
I ran into this issue after only a couple hours playing with Vue 3, so that makes me think this is a common enough use case to justify fixing.
What does the proposed API look like?
Currently:
toRaw(list).reverse()
Proposed:
list.reverse()
I would want to go through all
Array
methods and determine which would need to be included, as well as writing extensive tests for each. It's possible to just use the raw array for allArray
methods, but I'm not sure that's all that helpful.I've only spent a couple hours in this codebase so I'm not sure if there are other implications that a change like this could cause.
Before moving forward with this I wanted to confirm that this is a good direction to move in. I feel this is pretty in keeping with Vue's spirit of making things as simple as possible.
The text was updated successfully, but these errors were encountered: