tc39 / proposal-static-class-features

The static parts of new class features, in a separate proposal
https://arai-a.github.io/ecma262-compare/?pr=1668
127 stars 27 forks source link

Request for use cases for static public fields #27

Closed littledan closed 5 years ago

littledan commented 6 years ago

In the March 2018 TC39 meeting, some committee members expressed skepticism for static public fields. We know that static public fields are heavily used in React's class-based components, but it's not so transparent to the whole committee what other applications JS programmers have in mind for static public fields. What would you like to use static public fields for?

littledan commented 6 years ago

If we add static blocks, then code which would be written as

class C {
  static x = 1;
  static y = 2;
}

could instead be written

class C {
  static {
    this.x = 1;
    this.y = 2;
  }
}

Would that sort of syntax be an adequate replacement for static public fields for you? (My personal impression: It's not a sufficiently ergonomic replacement.)

jridgewell commented 6 years ago

Would that sort of syntax be an adequate replacement for static public fields for you?

This biggest loss here is the declarative nature of class fields. It's certainly an option to put it in a (imperative) statementlist, but doing that is only slightly better that just putting the C.x = 1 after the class declaration.

ljharb commented 6 years ago

In general, public properties on the constructor are conceptually “part of the class”. Having things that are part of the class, but can’t be declared declaratively in the class body, is a loss - and more specifically, is a massive oversight in the design of ES2015 classes.

trotyl commented 6 years ago

Angular is also using static class property for storing compiled template (and other metadata) in its next-generation compiler, like:

class CounterComponent {
  count = 0;

  static ngComponentDef = defineComponent({
    type: CounterComponent,
    selectors: [['counter']],
    template: function(ctx: CounterComponent, cm: boolean) {
      if (cm) { text(0); }
      textBinding(0, bind(ctx.count));
    },
    factory: () => new CounterComponent,
    inputs: {count: 'count'},
  });
}

One of my concern is that, since the stage-3 private class fields is lexical, so using static class fields annotation should allow the private fields usage in template, like:

class CounterComponent {
  #count = 0;

  static ngComponentDef = defineComponent({
    template: function(ctx: CounterComponent, cm: boolean) {
      textBinding(0, bind(ctx.#count));
    }
  });
}

But the assignment should not:

class CounterComponent {
  #count = 0;
}

CounterComponent.ngComponentDef = defineComponent({
  template: function(ctx: CounterComponent, cm: boolean) {
    textBinding(0, bind(ctx.#count)); // Error here
  }
});

So the existence of static public fields should be meaningful for the template accessibility. (The static initialization block should also helped)

fvsch commented 6 years ago

Static class fields are used somewhat elegantly for config in React Navigation (a popular navigation lib for React Native). One example from their doc, showing how a static navigationOptions field on component classes is used to override the base config for a group of components.

Of course it can be achieved in the ES2015 way too:

class MyComponent {
  …
}

MyComponent.navigationOptions = { … }

It’s just not very elegant.

annevk commented 6 years ago

https://searchfox.org/mozilla-central/search?q=static&case=true&regexp=false&path=.webidl (this includes a bit of noise though; not all is web-exposed).

jsg2021 commented 6 years ago

I agree with @ljharb And quite frankly, I don't understand the skepticism... other than maybe a political power play.

Static fields are used in every language I know of where classes are a thing. Even old js frameworks that defined their own class system had a concept of statics. These belong in the language.

Some use cases that come to mind beyond React's static propTypes is static factory methods, or type mapping. I have a library of models and each model statically defines a static MimeType... I don't think the use cases can be adequately enumerated.

bakkot commented 6 years ago

@jsg2021 Most of the skepticism arises because the semantics of static public fields in JavaScript will not match the semantics some of those in some nontrivial faction of cases. (Note: I personally support them; I just want to explain why this is an issue.)

In particular, in Java, this code

class Base {
  static int i = 0;
}
class Derived extends Base {}
Derived.i++;
print(Derived.i, Base.i);

would print (1, 1). (C# behaves similarly, among other languages.)

In JavaScript, the same code (leaving out the int type declaration) would print (1, 0). See discussion in #5, #24, etc.

On the other hand, static public fields in Python classes behave like they are proposed to do in JS with regards to the above inheritance behavior. There's no universal agreement on what the semantics for inheritance should be.

This is somewhat off-topic for this thread, and discussion of these semantics should go in other threads if you want to continue it. Just wanted to respond to your comment.

jsg2021 commented 6 years ago

I see. Thank you for the insight. This is helpful.

I have observed this behavior you described. I felt it was intuitive... kind of a copy-on-write kind of thing. I don't want to derail this thread, so thats all I'll say :)

rwaldron commented 6 years ago

@bakkot

semantics of static public fields in JavaScript will not match the semantics some of those in some nontrivial faction of cases.

This is already the case:

class List extends Array {}
console.log(List.from === Array.from); // true

List.from = "literally anything else";
console.log(List.from === Array.from); // false

If user code wanted those semantics, they could implement them:

let Base;
{
  let i = 0;
  Base = class {
    static get i() {
      return i;
    }
    static set i(value) {
      i = value;
    }
  }
}

class Derived extends Base {}
Derived.i++;
console.log(Derived.i, Base.i);
littledan commented 6 years ago

Let's keep this thread limited to the use cases for static public fields; we can discuss reservations anyone might have about their semantics on other threads in this repo.

blikblum commented 6 years ago

SkateJS uses public static fields to define props.

Below is an example of a SkateJS component class declaration. props field is used by the framework, events by the application:

export default class extends Component {
  static events = ["check", "remove"];
  static props = {
    checked: props.boolean,
    index: props.number
   };
  //..
}

Actual code: https://github.com/vogloblinsky/web-components-benchmark/blob/master/todomvc/skatejs-preact/src/item.js

littledan commented 5 years ago

Thanks for the examples here. Seems like we have plenty of justification for static public fields, and based on this justification, the proposal has re-achieved Stage 3 in TC39.