Skip to content

Instantly share code, notes, and snippets.

@Rich-Harris
Created March 29, 2018 23:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Rich-Harris/4c7784e9c305b9c67019317714ec3fd0 to your computer and use it in GitHub Desktop.
Save Rich-Harris/4c7784e9c305b9c67019317714ec3fd0 to your computer and use it in GitHub Desktop.
Multiple spread attributes, in order, without unnecessary changes

This is based off work by @mrkishi, and is an attempt to do the following:

  • Multiple spread props on elements/components
  • Later props override earlier ones
  • Updates are conservative — we only set things that have actually changed
  • As much code in the shared library as possible

Given a situation like this...

<Child a={{a}} {{...b_props}} c={{c}} d={{d}} {{...e_props}} />

...it should be possible to change any of a, b_props, c, d or e_props without triggering observers in Child for anything else. If e_props had (say) a d property, and no longer has said property, the d={{d}} should take its place in the update — essentially, it should be as though we were setting Object.assign({}, {a}, b_props, {c}, {d}, e_props).

I think this gets us there:

// this is a shared helper
function get_update(levels, updates) {
	var update = {};

	var to_null_out = {};
	var accounted_for = {};

	var i = levels.length;
	while (i--) {
		var o = levels[i];
		var n = updates[i];

		if (n) {
			Object.keys(o).forEach(key => {
				if (!(key in n)) to_null_out[key] = 1;
			});

			Object.keys(n).forEach(key => {
				if (!accounted_for[key]) {
					update[key] = n[key];
					accounted_for[key] = 1;
				}
			});
			assign(update, n);
			levels[i] = n;
		} else {
			Object.keys(o).forEach(key => {
				accounted_for[key] = 1;
			});
		}
	}

	Object.keys(to_null_out).forEach(key => {
		if (!(key in update)) update[key] = undefined;
	});

	return update;
}


function create_main_fragment(state, component) {
	var child_levels = [
		state.e_props,
		{ d: state.d },
		{ c: state.c },
		state.b_props,
		{ a: state.a }
	];

	var child_data = {};
	var i = child_levels.length;
	while (i--) child_data = assign(child_data, child_levels[i]);

	var child = new Child({
		root: component.root,
		data: child_data
	});

	return {
		c: function create() {
			child._fragment.c();
		},

		m: function mount(target, anchor) {
			child._mount(target, anchor);
		},

		p: function update(changed, state) {
			var child_update = get_update(child_levels, [
				changed.e_props && state.e_props,
				changed.d && { d: state.d },
				changed.c && { c: state.c },
				changed.b_props && state.b_props,
				changed.a && { a: state.a }
			]);

			child._set(child_update);
		},

		u: function unmount() {
			nested._unmount();
		},

		d: function destroy() {
			child.destroy(false);
		}
	};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment