ylem
ylem copied to clipboard
Updating props in ylem components
Ylem is trying to make rendering react components more efficient and solve the following rendering problem:
When rendering an ylem component, you might have props of { foo: 'bar', bam: { foo: 'foo', bar: 'bar' } }
. Your main component might use foo, then pass all of bam down to the child (which is also an ylem component), ala <div>{ foo }<Child data={bam} /></div>
. If the parent is then updated to { foo: 'bar', bam: { foo: 'foo2', bar: 'bar' } }
, only the child component should re-render, as neither foo
nor bam
changed (though bam
’s child info did). If we just merged keys over, it would cause extra re-renders
Currently, in order to handle situations like this a complex deep diff is performed of the new and old props inside the updateObservableWithProps
function.
The new strategy will involve moving all diffs into the deriveUpate
functions inside ./derivers.js
, or inside a custom derive update function the user passes into connect
. Those deriveUpdate
functions will now return patches (resulting from the diff), not objects.
// ./derivers.js
function controlled(nextProps) {
// always use the latest props, overwiting existing data
return [
...canDiff.map(lastProps || {}, nextProps).filter(({ type }) => type === 'delete'),
...canDiff.map({}, nextProps),
];
}
function uncontrolled(nextProps, lastProps) {
// use props on initial render (when lastProps is null)
if (lastProps === null) {
return canDiff.map({}, nextProps);;
}
// otherwise do nothing
return null;
}
function changes(nextProps, lastProps) {
// use props on initial render (when lastProps is null)
return canDiff.map(lastProps, nextProps);
}
Then, instead of doing a canDiff.deep
diff inside the updateObservableWithProps
function, that function will instead simply apply the patches that the deriveUpdate
function produced.
// ./lib/observable-component.js
function updateObservableWithProps(observable, patches, observer) { // pass in "patches" instead of newProps
...
// remove deep dif
for (const { key, type, ...values } of patches) {
if (type === 'delete') {
// do the delete
}
if (type === 'splice') {
canReflect.splice(prop, values.index, values.deleteCount, values.insert);
}
if (type === 'add' || type === 'set') {
if (typeof values.value === 'object' && !React.isValidElement(values.value) && !Object.isExtensible(values.value)) {
if (canReflect.isMoreListLikeThanMapLike(values.value)) {
values.value = [ ...values.value ];
}
else {
values.value = { ...values.value };
}
}
canKey.set(observable, key, values.value);
}
}
...
}
Now all the responsibility for deciding what needs to be updated is left to the deriveUpdate
functions.