tc39 / proposal-class-public-fields

Stage 2 proposal for public class fields in ECMAScript
https://tc39.github.io/proposal-class-public-fields/
488 stars 25 forks source link

How would decorators work with fields and get/set? #58

Closed justinbmeyer closed 7 years ago

justinbmeyer commented 7 years ago

This might be outside the current concerns of this proposal, but I'm wondering how decorators will work alongside fields and get/set.

CanJS has a library can-define we use to add rich behavior to a constructor function and its prototype like this:

var Person = function(){ };
define(Person.prototype,{
  name: {
    value: "Beyonce Knowles",
    set: function(value){
      return value.toUpperCase();
    },
    get: function(value){
      return value.toLowerCase();
    }
  },
  age: {type: "number", age: "35"}
});

var p = new Person();
p.name //-> "beyonce knowles"
p.age //-> 35  (notice it was converted to a number)

value is the initial value of the property. Users expected values to go through the setter, example issue: https://github.com/canjs/can-define/issues/87. I'm trying to figure out how I could migrate this sort of rich behavior to class with decorators, fields, and get/set.

Ideally, something like:

class Person {
  @prop
  name= "Beyonce Knowles";
  set name(newVal){
    return value.toUpperCase();
  },
  get name(newVal){
    return value.toLowerCase();
  }
  @type("number")
  age="35"
}

Would @type get called like:

type( Person.prototype, "age", {value: "35"} )

And be able to provide a get / set definition? Would it get called during the class definition or the construction of the instance?

I doubt it will work this way, but I'd really like @prop to be able to be called with the initial field value:

prop( 
  Person.prototype, 
  "age", 
  {value: "Beyonce Knowles", get: getter, set: setter} )

So I could rewrite the getter/setters to use it if set hasn't been called as follows:

prop = function(prototype, name, definition){
  // create the internal store of data ...
  Object.defineProperty(prototype, "_data", {
    get: function(){
      return this._data = {};
    }
  })
  Object.defineProperty(prototype, name, {
    get: function(){
      // get calls the provided `getter` with the internal data
      return definition.get.call(this, this._data.hasOwnProperty(name) ? this._data[name] :  definition.value );
    },
    set: function(value){
      // set the internal data with the result of the provided `setter`
      this._data[name] = definition.set.call(this, value);
    }
  })
}

Thanks for any clarity you can provide!

bakkot commented 7 years ago

You might ask over at the decorators proposal, since it looks like your question is more about decorators. (Remember, decorators aren't yet any more part of the language than public fields are, and are no more settled in their behavior!)

bakkot commented 7 years ago

That said, re:

Users expected values to go through the setter

You might be able to make that happen with a decorator, but without one that comes down to #42. Personally I feel very strongly that public fields should be for defining fields, and if you want to trigger setters and other effects the constructor is the correct place to do it.

bakkot commented 7 years ago

(Sorry, I didn't mean to imply this issue ought to be closed! Someone else might have more information. Just, people working on the decorators proposal might too :) )

justinbmeyer commented 7 years ago

No problem. You'd expect the decorators proposal to consider how it would work with this proposal?

bakkot commented 7 years ago

I know that its champions are considering how it works with this proposal, yes. Both are concerned about working with the other, but most of the machinery you're concerned about would be handled by the decorators proposal.