rricard / proposal-refcollection

ECMAScript proposal for the RefCollection.
3 stars 1 forks source link

Give more use cases and examples #3

Open littledan opened 4 years ago

littledan commented 4 years ago

I like how the examples seem to relate to real applications, but giving examples at a bit more length to clarify both why it's needed and how it's used would be nice. For example, it'd be good to show:

sebmarkbage commented 4 years ago

The example in the readme is a good example of a real world use case:

refc.deref(vdom.children[0].props.onClick).call();

However, it's not very ergonomic. I don't see that anyone would actually want to use this. So it actually serves as a counter-example.

jridgewell commented 4 years ago

but giving examples at a bit more length to clarify both why it's needed and how it's used would be nice.

I've been designing a new JSX implementation that would directly take advantage of this. My design actually really closely aligns with what was presented. You can see an example of what I'm currently doing at https://astexplorer.net/#/gist/fdff7c0bb6d96d588fe0560378ec0e57/d72e6dda5323ebe9c0fba22cadb18f7b3ed9eb4a.

I'm still finishing the diffing library that uses this JSX format, so the best I can link you to currently is the compatibility code for React's current JSX format. This particular function diffs an Element JSX node (something like { type: "div", props: { class: 'foo', children: […] }). Importantly, this function doesn't care what depth into a JSX tree it is, it just has a previous render (a Fiber) and the current render (this is important for @sebmarkbage's comment). Because I don't know what can change in this tree, I must descend into every single element and it's children and their children. This can be expensive for deep trees.

Now, instead imagine that you JSX was represented as a Record with a RefCollection holding the mutable data. Now at the beginning of the diff that this entire tree is equivalent to what was represented before. All that's changed is the data in the RefCollection. During the initial render for this template, we'll have traversed the entire Record (to make the DOM nodes). During this process, it'd be trivial to store each Ref into an array to store on the root Fiber along with info about whether it's a prop/event-listener/child-node/etc. So when re-rendering, all I need to do is iterate this array, deref the symbol to get the new data, and then diff the dynamic data.

This could be a trivial as:

if (previousTemplate === newRenderTemplate) { // Record equality check
  for (const previousData of dynamicDatas) {
    const newRenderValue = newRefCollection.deref(previousData.ref);
    // do targeted diff
  }
}

How you could use a single RefMap across the lifecycle of a whole program, depending on its native GC properties

With the way I've written JSX2, you wouldn't reuse the same RefCollection forever. Each collection should be used with an individual instance of a record. If you make a new record (even if it's the same structure), you'd create a new collection with it.

How you could use multiple RefMaps if you want, locally, even providing that second symbol argument in a template-like way

I don't understand this question.


However, it's not very ergonomic. I don't see that anyone would actually want to use this.

I think naive examples will look like this, but not real programs. Trees are naturally recursive, so you'd only be referencing something like rc.deref(node.props.onClick), not multiple levels deep (node.children[0].props.onClick).

See also lit-html, which does also stores dynamic data into an out-of-bound array. Updates are a simple for-loop.

sebmarkbage commented 4 years ago

The issue is that in React the "props" are a large variety of user space objects that also benefit from being records or nested records. These are accessed from arbitrary code and not templates. They can't be easily compiled to something wrapped with rc.deref.

I could see how you could build a system that way but it's not the direction we're going to go with React.

I think the core Records proposal needs to get to a point where you can just write node.props.onClick() and it just works.

Then you could just add a WeakMap around it to accomplish what you want here.

jridgewell commented 4 years ago

The issue is that in React the "props" are a large variety of user space objects that also benefit from being records or nested records.

I think you're conflating two aspects, both called props. The props used by the reconciler are essentially static templates, and perfectly representable with this system (this is the idea for jsx2). The props passed to a component via the parameter are the user-space objects. It should be easy to construct this object out of the rendering props.

So there's no need for the component to be aware of the refs collection at all. It just receives the same props object like always. If the user wanted to pass a record as a single prop, they're free to do so. They'll receive it just as the passed it.

I think the core Records proposal needs to get to a point where you can just write node.props.onClick() and it just works.

This is also one of the directions we had discussed. I thought we'd be allowed to represent mutable data directly in "normal" records, with a special syntax form (I suggested tagged records, but this just an idea) that would automatically pull the dynamic expressions out of the overall record structure and into a refs collection. So it's possible we can get both.

sebmarkbage commented 4 years ago

Even if we had syntax for it, then there would still be a separate collection to keep track of which I’m very concerned about.

I think that your approach could be useful to experiment with but I don’t find that direction for a UI framework very compelling so I’d rather want the primitive functionality that doesn’t need a collection to keep track of.

E.g. SwiftUI is an example of a framework that keeps track of mutable boxes inside of value type structs but they’re owned by the value type itself and not through an indirection.

One approach allows for either but the current proposal only allows for one approach. In general JS allows for plurality of approaches and not locked in rigidity.

littledan commented 4 years ago

Some things which might help ergonomics in practice: