ylem icon indicating copy to clipboard operation
ylem copied to clipboard

Updating props in ylem components

Open mikedane opened this issue 6 years ago • 0 comments

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.

mikedane avatar Sep 17 '18 17:09 mikedane