nuxt
nuxt copied to clipboard
Use flatted instead of devalue to be able to revive objects
What problem does this feature solve?
It is sometimes useful to save complex data (i.e. not primitive types nor POJOs) in the store.
Store data is passed from server to client in serialized form using devalue. Using flatted
instead of devalue
, together with some configuration mechanism, would allow reviving objects that would then be usable client-side.
For instance developers would add revivers to the store and these would be called client-side to revive serialized values.
What does the proposed changes look like?
I created an example repo which consists of two npx create-nuxt-app
: https://github.com/vhf/nuxt-flatted-feature/commits/master . It's a dumb example because I wanted to keep it minimal, in a real-world use case I'm storing things like RDF dataset objects ( https://github.com/rdf-ext/rdf-dataset-indexed ) (initialized during serverIniti based on server data/request data).
In this example I'm storing an object based on:
export default class Bar {
constructor (id = Math.random()) {
this.id = id
}
transform () {
return this.id * 2
}
}
and the goal is to do someBarInstance.transform()
in the frontend. The two apps have the same index.vue
, the basic one shows an error because .transform
is not a function on the devalue
d object, the other one uses flatted to serialize and revive the object, properly displaying .transform()
's result.
I was about to open a bug report, because the current behaviour with devalue
is not obvious and can lead to serious issues in a strictly typed environment. A major problem tackled here is using data models that are then mapped as props to a component. If I wanted to check these props with the instanceof
operator, it may work at one point but fail silently deeper down.
It is unexpected that what is passed in as a data model instance then comes out as an object (essentially only a dictionary mimicking the model) within the component.
As far as I've understood this, Nuxt has forked devalue
. The original project does not use toJSON
but the fork discussed here already does, though it only works one way. A proper integration with TypeScript includes the ability to use instanceof
and preserve types. Either devalue
is extended even further to allow for a fromJSON(…)
or @vhf has already provided the right direction.
Agreed that it would be really nice to have some ability to reconstitute an object. At a bare minimum having some ability to declare some kind of fromJSON
would be really helpful.
something to consider. flatted seems 2.5x slower than devalue. On 120kb i got this results devalue x 1,655 ops/sec ±0.48% (94 runs sampled) flatted x 635 ops/sec ±1.45% (91 runs sampled)
+1 Please add some way to hydrate class instances with methods after data injection from the server
I've been working on this problem over the past couple of days. I have it working for my project but want to see what you guys think of my solution.
- I had to make an additional fix in nuxt-contrib/devalue, I have a PR for this https://github.com/nuxt-contrib/devalue/pull/14
- then I changed my model base class to implement toJSON. In my case, I add additional data such as "@className" to let me know what class instance I need to hydrate
- finally, in my mixin I have I have the following code.
created()
{
const isSsrHydration = (vm) => vm.$vnode && vm.$vnode.elm && vm.$vnode.elm.dataset && vm.$vnode.elm.dataset.fetchKey
if (isSsrHydration(this))
{
const nuxtState = window.__NUXT__
// Hydrate component
this._fetchKey = this.$vnode.elm.dataset.fetchKey
const data = nuxtState.fetch[this._fetchKey]
// If fetch error
if (data && data._error)
{
this.$fetchState.error = data._error
return
}
// Merge data
for (const key in data)
{
let value = data[key];
if (Array.isArray(value))
{
for (let i = 0; i < value.length; i++)
{
const valueElement = value[i];
//if it's not an array of models then skip it.
if (typeof valueElement !== 'object' || !valueElement["@class"])
{
break;
}
let model = this.$getModel(valueElement["@class"], valueElement._id);
model.fromJSON(valueElement);
value[i] = model;
}
}
else if (typeof value === 'object' && value["@class"])
{
let model = this.$getModel(value["@class"], value._id);
model.fromJSON(value);
data[key] = model;
}
}
}
Let's track in https://github.com/nuxt/nuxt/issues/12831.