-
-
Notifications
You must be signed in to change notification settings - Fork 8.3k
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
readonly() breaks reactivity of Map #1772
Comments
Items from a readonly map will be wrapped with and in https://composition-api.vuejs.org/api.html#readonly it says
so it's not a bug I think. |
@unbyte I know that const {
readonly,
reactive,
watchEffect
} = Vue;
const map = reactive(new Map())
const roMap = readonly(map);
// Sync flush for debug purposes
const watchEffectSync = (callback) => watchEffect(callback, { flush: 'sync' })
// Try with default object
{
console.log('--- Default object')
map.set(0, { foo: 'bar' });
const item = map.get(0);
const roItem = roMap.get(0);
watchEffectSync(() => {
console.log('Watch effect item:', item);
})
watchEffectSync(() => {
console.log('Watch effect ro item:', roItem);
})
console.log('Mutating item...');
item.foo = 'Updated value';
console.log('Ro item (not in watchEffect):', roItem);
}
// Try with reactive object
{
console.log('--- Reactive object')
map.set(1, reactive({ foo: 'bar' }));
const item = map.get(1);
const roItem = roMap.get(1);
watchEffectSync(() => {
console.log('Watch effect item:', item);
})
watchEffectSync(() => {
console.log('Watch effect ro item:', roItem);
})
console.log('Mutating item...');
item.foo = 'Updated value';
console.log('Ro item (not in watchEffect):', roItem);
} Console output: "--- Default object"
"Watch effect item:" Object {
foo: "bar"
}
"Watch effect ro item:" Object {
foo: "bar"
}
"Mutating item..."
"Watch effect item:" Object {
foo: "Updated value"
}
"Ro item (not in watchEffect):" Object {
foo: "Updated value"
}
"--- Reactive object"
"Watch effect item:" Object {
foo: "bar"
}
"Watch effect ro item:" Object {
foo: "bar"
}
"Mutating item..."
"Watch effect item:" Object {
foo: "Updated value"
}
"Ro item (not in watchEffect):" Object {
foo: "Updated value"
} As you can see, the The point is not how Also, const map = reactive(new Map())
map.set(0, reactive({ foo: 'bar' }))
console.log(isReactive(readonly(map).get(0))) // => false |
Do you mean that, there are one reactive map and one readonly map pointing to the same raw map, from which you get `item` and `roItem` pointing to the same object, and what you expect is that when you edit `item`, the watcher callback for `roItem` should be triggered (because you can't edit readonly `roItem` directly)?
If so, this result is as expected I think, and itt has nothing to do with whether it's in a readonly map.
here is an example.
```javascript
const {
readonly,
reactive,
watchEffect,
isReactive
} = Vue
const test = {
some: 'value'
}
const reactiveTest = reactive(test)
const readonlyTest = readonly(test)
watchEffect(() => {
console.log('reactive changed:', reactiveTest.some)
}, {
flush: 'sync'
})
watchEffect(() => {
console.log('readonly changed', readonlyTest.some)
}, {
flush: 'sync'
})
reactiveTest.some = 'new value'
```
I hope I haven't misunderstood what you mean.
|
@unbyte I think you did not quite understand me. In your example, it goes like this: // Create some raw object
const test = { some: 'value' };
// Create reactive wrapper of RAW test
const reactiveTest = reactive(test)
// Create readonly wrapper of RAW test
const readonlyTest = readonly(test)
// Mutating object via reactive wrapper
reactiveTest.some = 'new value' And in my example, it works a little differently: // Create some raw object
const test = { some: 'value' };
// Create reactive wrapper of RAW test
const reactiveTest = reactive(test)
// Create readonly wrapper of REACTIVE test
const readonlyTest = readonly(reactiveTest)
// ^^^^^^^^^^^^
// Mutating object via reactive wrapper
reactiveTest.some = 'new value' I am making watchEffect(() => console.log('Value of reactive:', reactiveTest.some));
watchEffect(() => console.log('Value of readonly(reactive):', readonlyTest.some));
// mutating...
reactiveTest.some = 'new value'; Output: "Value of reactive:" "value"
"Value of readonly(reactive):" "value"
"Value of reactive:" "new value"
"Value of readonly(reactive):" "new value" As you can see, the reactivity of a plain object is not lost when wrapping it in |
Finally I understand what you mean. I'm so foolish. But the items got from maps are not like this const reactiveTest = reactive(test)
const readonlyTest = readonly(reactiveTest) Instead, they are more like this const reactiveTest = reactive(test)
const readonlyTest = readonly(test) because and of course const {
readonly,
reactive,
watchEffect
} = Vue
const map = reactive(new Map())
const roMap = readonly(map)
const watchEffectSync = (callback) => watchEffect(callback, { flush: 'sync' })
map.set(0, { foo: 'bar' })
const item = map.get(0)
const roItem = readonly(item)
// ^^^^^^^^^^^^^
watchEffectSync(() => {
console.log('Watch effect item:', item.foo, item)
})
watchEffectSync(() => {
console.log('Watch effect ro item:', roItem.foo, roItem)
})
console.log('Mutating item...')
item.foo = 'Updated value'
console.log('Ro item (not in watchEffect):', roItem)
// both watcher callbacks are triggered Go back to the problem that you really want to solve (I'm so sorry, I seem to have taken the focus of the problem off center), how about using function deepClone(obj = {}) {
if (typeof obj !== 'object' || obj == null)
return obj
const result = obj instanceof Array ? [] : {}
for (const key in obj)
if (obj.hasOwnProperty(key))
result[key] = obj[key]
return result
}
const {
readonly,
reactive,
watchEffect
} = Vue
const map = reactive(new Map())
const watchEffectSync = (callback) => watchEffect(callback, { flush: 'sync' })
map.set(0, { foo: 'bar' })
function useMap() {
// and return size, entries ...
return {
getMapItem(key) {
return deepClone(map.get(key))
}
}
}
some()
function some() {
const { getMapItem } = useMap()
watchEffectSync(() => {
const value = getMapItem(0)
console.log(value)
value.foo = 'another'
})
}
map.get(0).foo = 'hello' |
@unbyte Hmm. I will try to describe my case more precisely. Here's another example on CodePen. In short, it has the following functions for creating a repository: function useUnsafeStore() {
const state = reactive({
users: new Map([
['default', { name: 'initial value' }]
])
});
// `reactive` for computed refs unwrapping
const getters = reactive({
usersIds: computed(() => [...state.users.keys()])
})
const mutations = {
updateUserName(id, value) {
state.users.get(id).name = value;
}
}
return {
state,
getters,
mutations
}
}
function useReadonlyStore() {
return readonly(useUnsafeStore());
} The Next, I create two different applications: one with unsecured storage, the other with readonly, and, as you can see from the example, reactivity in the second case is lost, although the element itself inside the Map does change, as can be seen after applying |
I have created a pull request to solve this problem. 😀 |
Version
3.0.0-rc.5
Reproduction link
https://codepen.io/liquid-solid/pen/ZEWzqpX?editors=0012
Steps to reproduce
Open CodePen and see console logs, or you can see an example below:
This code outputs:
What is expected?
I expect that objects from a reactive
Map
will always be reactive, even if theMap
isreadonly
.What is actually happening?
When I get an item from readonly reactive
Map
it loses reactivity.Is it a bug? If not, i don't understand how to work with maps, sets and other structs with new reactivity system. So far, plain objects still seem to be the best solution for storing data as an associative array due to the intricacies of the reactivity system.
The text was updated successfully, but these errors were encountered: