130 lines
3.6 KiB
JavaScript
130 lines
3.6 KiB
JavaScript
// Component capable of saving & restoring state during render
|
|
// (c) Vitaliy Filippov 2019+
|
|
// Version: 2021-08-07
|
|
// License: Dual-license MPL 1.1+ or GNU LGPL 3.0+
|
|
|
|
// NOTE: Child components of the same class should have unique `key`s
|
|
|
|
import React from 'react';
|
|
|
|
export default class SSRComponent extends React.PureComponent
|
|
{
|
|
constructor(props)
|
|
{
|
|
super(props);
|
|
if (props.store && props.store.state)
|
|
{
|
|
this.unserializeState(props.store.state);
|
|
delete props.store.state;
|
|
}
|
|
else
|
|
{
|
|
this.state = {};
|
|
this.init(props);
|
|
}
|
|
}
|
|
|
|
init(props)
|
|
{
|
|
}
|
|
|
|
static serializeStore(store)
|
|
{
|
|
store = { ...store };
|
|
if (store.instance)
|
|
{
|
|
store.state = store.instance.serializeState();
|
|
delete store.instance;
|
|
}
|
|
if (store.children)
|
|
{
|
|
store.children = { ...store.children };
|
|
for (const className in store.children)
|
|
{
|
|
store.children[className] = { ...store.children[className] };
|
|
for (const key in store.children[className])
|
|
{
|
|
store.children[className][key] = SSRComponent.serializeStore(store.children[className][key]);
|
|
}
|
|
}
|
|
}
|
|
return store;
|
|
}
|
|
|
|
serializeState()
|
|
{
|
|
return { ...this.state };
|
|
}
|
|
|
|
unserializeState(state)
|
|
{
|
|
this.state = state;
|
|
}
|
|
|
|
passStore(children)
|
|
{
|
|
return React.Children.map(children, (child) =>
|
|
{
|
|
if (child && (child.type instanceof Object) && (child.type.prototype instanceof SSRComponent))
|
|
{
|
|
const className = child.type.name;
|
|
this.props.store.idx[className] ||= { num: 0, keys: {} };
|
|
let key;
|
|
if (child.props.key)
|
|
key = ':'+child.props.key;
|
|
else
|
|
{
|
|
this.props.store.idx[className].num++;
|
|
key = '['+this.props.store.idx[className].num;
|
|
}
|
|
this.props.store.idx[className].keys[key] = true;
|
|
let chstore = (this.props.store.children ||= {});
|
|
chstore = (chstore[className] ||= {});
|
|
chstore = (chstore[key] ||= {});
|
|
return React.cloneElement(child, {
|
|
store: chstore,
|
|
children: child.props && child.props.children ? this.passStore(child.props.children) : undefined,
|
|
});
|
|
}
|
|
else if (child && child.props && child.props.children)
|
|
{
|
|
return React.cloneElement(child, {
|
|
children: this.passStore(child.props.children),
|
|
});
|
|
}
|
|
return child;
|
|
});
|
|
}
|
|
|
|
render()
|
|
{
|
|
if (this.props.store)
|
|
{
|
|
this.props.store.instance = this;
|
|
this.props.store.idx = {};
|
|
}
|
|
let children = this.doRender();
|
|
if (this.props.store)
|
|
{
|
|
children = this.passStore(children);
|
|
// Clear unused keys
|
|
for (let className in this.props.store.idx)
|
|
{
|
|
for (let key in this.props.store.children[className])
|
|
{
|
|
if (!this.props.store.idx[className].keys[key])
|
|
{
|
|
delete this.props.store.children[className][key];
|
|
}
|
|
}
|
|
}
|
|
delete this.props.store.idx;
|
|
}
|
|
return children;
|
|
}
|
|
|
|
doRender()
|
|
{
|
|
}
|
|
}
|