Vue-next: Cannot clone reactive objects using the structured clone algorithm

Created on 26 Aug 2020  路  11Comments  路  Source: vuejs/vue-next

Version

3.0.0-rc.8

Reproduction link

https://codesandbox.io/s/bold-violet-rsjis

Steps to reproduce

Run the structured clone algorithm on a reactive object.

What is expected?

The object is cloned (losing the proxy in the process).

What is actually happening?

A DataCloneError is thrown


Originally encountered this bug in an Electron application, where the structured clone algorithm is used for any data passed between windows.

has workaround

Most helpful comment

Technically deepToRaw is not possible, because in the example of

const s = reactive({
  foo: reactive({})
})

The raw version of s does contain foo as a reactive object. If you want s.foo to be a raw object as well, you must clone raw s - but that is no longer the actual raw s. Which means it's essentially a deep clone. In this case, you probably want to just run it through a helper like _.cloneDeep (which is loosely based on SCA).

All 11 comments

Create a copy instead, HistoryState does not accept all kind of values

The whatwg#structuredserializeinternal specification says:

if IsCallable(value) is true, then throw a "DataCloneError" DOMException.

The Proxy Object contains [[call]] as an internal slot, so I don鈥檛 think it鈥檚 Vue鈥檚 issue.

Create a copy instead

Since the structured clone algorithm is a standardised algorithm to copy Javascript objects, which is more powerful than JSON.parse(JSON.stringify(...)), I don't quite understand how I would simply "create a copy" (with more complex objects like a Map).

Again, my usecase isn't really HistoryState, but rather a large Electron application where all data going to and from the Node backend have to go through SCA. In the frontend, most of that data ends up in vuex, so it all gets turned into reactive objects.

 The Proxy Object contains [[call]] as an internal slot, so I don鈥檛 think it鈥檚 Vue鈥檚 issue.

If Proxies just can't be made to work with the SCA, then I think Vue should provide a deep variant of toRaw. Otherwise, as far as I can tell, there's currently no way to get a plain JS object back from a reactive object containing other reactive objects.

I don't quite understand how I would simply "create a copy" (with more complex objects like a Map).

You cannot pass a Map to HistoryState, you will have to serialize it yourself anyway

If Proxies just can't be made to work with the SCA, then I think Vue should provide a deep variant of toRaw. Otherwise, as far as I can tell, there's currently no way to get a plain JS object back from a reactive object containing other reactive objects.

Yes, there is:

const s = reactive({})

history.pushState({ ...s })

If you have any non-serializable data like a Map, you will have to serialize it first

You cannot pass a Map to HistoryState, you will have to serialize it yourself anyway

As stated a couple of times, this is not about HistoryState (although that does to use SCA and support Maps, at least in some modern browsers: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm)

Yes, there is:

I don't see how that works for nested objects.

If you have any non-serializable data like a Map, you will have to serialize it first

Not to JSON, but with the SCA, they can be serialised.

I don't see how that works for nested objects.

Just use toRaw():

history.pushState(toRaw(s))

Just use toRaw():

I tried that, it only seems be a shallow toRaw: https://codesandbox.io/s/distracted-keldysh-00h8c

A deepToRaw would be a good fix/workaround IMHO :) (or probably for consistency, toRaw and shallowToRaw)

@posva Maybe we need to reopen this? I think it's a valid use case 馃.

Technically deepToRaw is not possible, because in the example of

const s = reactive({
  foo: reactive({})
})

The raw version of s does contain foo as a reactive object. If you want s.foo to be a raw object as well, you must clone raw s - but that is no longer the actual raw s. Which means it's essentially a deep clone. In this case, you probably want to just run it through a helper like _.cloneDeep (which is loosely based on SCA).

Just re-read the spec and in item 23 of the SCA it explicitly states SCA does not accept Proxy objects. So yes a deep clone is pretty much required in this case. It actually is not related to the [[Call]] slot.

Ah, see it~

Was this page helpful?
0 / 5 - 0 ratings

Related issues

NMFES picture NMFES  路  3Comments

mika76 picture mika76  路  3Comments

crutchcorn picture crutchcorn  路  3Comments

corkt picture corkt  路  4Comments

cexbrayat picture cexbrayat  路  4Comments