ryansolid / dom-expressions

A Fine-Grained Runtime for Performant DOM Rendering
MIT License
885 stars 127 forks source link

More ref magic #149

Open edemaine opened 2 years ago

edemaine commented 2 years ago

Inspired by @fabiospampinato's https://github.com/ryansolid/dom-expressions/issues/96#issuecomment-1179727981, here are some ideas for solving some current issues with ref:

Multiple refs on a common element

Currently it's annoying to "merge" two refs together into one ref attribute. For example, if you want props.ref to be called and to set a local ref variable, you need to do something like this:

<div ref={(r) => props.ref(ref = r)}/>`

This is fairly short but not particularly intuitive or readable. Possible solutions:

  1. We could make ref-1={ref} ref-2={props.ref} work, i.e., ref-NUMBER could work just like ref, but they stack like directives do. (Or perhaps ref:1={ref} ref:2={props.ref}, but see other suggestion for that syntax below.)
  2. We could make ref={[ref, props.ref]} work (Fabio's proposal). Only inline arrays would be supported, just like event handlers already have special meaning for inline arrays.

In either case, we'd end up with a ref function like this (inlined for Element creation as in <div/>):

ref(r$) {
  const _ref$ = ref;
  typeof _ref$ === "function" ? _ref$(r$) : ref = r$;
  props.ref(r$);
}

Components that want to provide multiple refs

Sometimes a component wants to expose multiple DOM elements (or other data, e.g. API functionality) to the parent. Currently that needs to be done via callback functions, like so:

<Component apiRef={(r) => apiRef = r} viewRef={(r) => viewRef = r}/>

Instead, we could imagine a ref: prefix that has the same syntactic sugar that ref does:

<Component ref:api={apiRef} ref:view={viewRef}/>

This would compile to:

createComponent(Component, {
  ["ref:api"](r$) {
    const _ref$ = apiRef;
    typeof _ref$ === "function" ? _ref$(r$) : apiRef = r$;
  },
  ["ref:view"](r$) {
    const _ref$ = viewRef;
    typeof _ref$ === "function" ? _ref$(r$) : viewRef = r$;
  },
});

So the component could be defined like this:

function Component(props) {
  props['ref:api']?.({ ... });
  return <div ref={props['ref:view']}>...</div>;
}

If use:___ ends up being syntactic sugar for ref, we could add functionality for multiple refs with syntax like this:

<Component use-api:apiDirective use-view:viewDirective/>