Native Class Constructor Update#337
Conversation
rwjblue
left a comment
There was a problem hiding this comment.
Great write up, thanks for your continued work on this!
I’m definitey in favor...
chriskrycho
left a comment
There was a problem hiding this comment.
😍 with a couple tweaks suggested.
|
|
||
| ### Injections and the `init` hook | ||
|
|
||
| One side effect of this change is that injections will not be available on the |
There was a problem hiding this comment.
It's worth noting that we've done an unofficial survey of some of the largest apps known to be using ES6 classes in production via TypeScript and have found something like 2 instances of the constructor using injected services total across them. I don't think we have enough data to say that in the RFC, but I wanted to bring it up here for the record in the RFC discussion.
| docs have not been written, etc), this RFC proposes that the change would _not_ | ||
| be considered a breaking change _for the purposes of semver_. This would allow | ||
| us to ship the change during the Ember v3 release cycle, and prevent more code | ||
| from being built on top of the previous behavior. |
There was a problem hiding this comment.
It's worth note that the design landed on here does not break the behavior of any of the fallback solutions the community has had to embrace to deal with the pain point.
As such, this is a case where it's a "breaking change" in the strictest sense but is extremely unlikely to break any real-world code, because the real-world code has necessitated the use of one of those workarounds.
| One thing we should make clear is that EmberObject will likely be deprecated | ||
| in the near future, and that ideally for non-Ember classes (things that aren't | ||
| Components, Services, etc.) users should drop EmberObject altogether and use | ||
| native classes only. For the purposes of codemodding, however, this will not be |
There was a problem hiding this comment.
This last sentence is a bit unclear; I actually have no idea what it means. Could you reword?
There was a problem hiding this comment.
Coming back to it, I think it was pretty unclear too so I'm going to drop it. Basically, it's not possible to easy codemod a class which was base on EmberObject to one which is not based on EmberObject, for a number of reasons:
- Common usage of static methods (
createis used everywhere, and tracking down and converting each usage would be very difficult without types in non TS apps) - Mixins. If they're used once in the inheritance tree, we can't convert.
The point is, we shouldn't be recommending that people use EmberObject at all now that we have native classes, and we should guide users toward converting any existing classes.
|
|
||
| ## Drawbacks | ||
|
|
||
| This would be a breaking change that would negatively affect early adopters. |
There was a problem hiding this comment.
I'd suggest "could" rather than "would".
| return this; | ||
| } | ||
|
|
||
| deprecate('using new with EmberObject has been deprecated', true); |
There was a problem hiding this comment.
I'd like to see this fleshed out to include the deprecation timeline, what would the until be here? I'd personally like to see this be considered a private API deprecation, where until would be the next LTS+1...
There was a problem hiding this comment.
I'd also support that timeline.
There was a problem hiding this comment.
I think the sooner we can remove it, the better. Will update the RFC to state that it will be until: '3.5.0'
| let instance = new this(props); | ||
|
|
||
| Object.assign(this, props); | ||
| this.init(); |
There was a problem hiding this comment.
I think you mean
Object.assign(instance, props);
instance.init()There was a problem hiding this comment.
You are correct, updating!
|
I'm excited for native classes and better parity is 👏. Deprecating |
|
Yeah, I hear you there. Not being able to use The only time users currently actually manually use As proposed in #338, the long term goal would be to remove let foo = new Foo();
Object.assign(foo, props);This could work in some cases, but I don't think it could for all of them safely. The idea with this change is that we do this conversion in two parts:
In either case, I believe we should start updating guides, blog posts, addon documentation, etc. to use native classes instead of extending from |
|
Also, another alternative that has come up is potentially not deprecating |
|
I think deprecating with props is better than deprecating I'm ok with something like: Where |
|
From a learning side of things I think we'd need to document the differences in
|
|
I would be OK with it, as long as we made it very, very clear that you should not be extending from let report = new MyReport();
setProperties(report, props); // minor tweakIs essentially a forwards compatible way to do that. |
| other changes would be relatively easy to create a safe codemod for (essentially | ||
| converting `constructor` -> `init` in affected classes), so the impact _should_ | ||
| be minimal. | ||
|
|
There was a problem hiding this comment.
An alternative not mentioned here is to make the change behind a feature flag enabled by an add-on. This would make the new behavior opt in. We are doing this already for making jQuery optional, for example.
There was a problem hiding this comment.
That would be a reasonable alternative, we should definitely add it to the alternatives section.
In practice, I think it would be best to ship the target behavior as the actual/only public API and polyfill its behavior to older versions of Ember (already looking into how we can do that, it should be possible). Unlike a lot of the other optional feature flags, we don't technically have a public API that we need to continue to support.
This does beg the question, when does an RFC spec become public API? The process in intentionally fuzzy here, because details may change in implementation (see Named Blocks).
In this case, we've talked to lots of early adopters and they have so far been very supportive of this late-stage update to the original RFC. However, if there are any users of native class syntax out there who would be very against it, we definitely want to hear from them!
|
I'd like to second the points raised that advising against using // @ember/object`
class EmberObject {
constructor(props) {
// ..class setup things
}
static create(props) {
deprecate('using EmberObject.create() has been deprecated, please `import createWithProps from @ember/object`', true);
return createWithProps(this, props);
}
}
function createWithProps(className, props){
if (typeof props !== 'object') {
throw new Error('createWithProps can only be called with {class}, {object}');
}
const instance = new className;
Object.assign(instance, props);
return instance;
}
export default EmberObject;
export createObject;import createWithProps from @ember/object;
const foo = createWithProps(Foo, {my: 'prop'});This way, it's explicit that creating a new object with properties is explicitly an ember thing,. but regular |
|
To be clear, I would say the advisement is not to not use // Before
let MyClass = EmberObject.extend();
let foo = MyClass.create({ bar: 'baz' });
// After
class MyClass {
constructor(bar) {
this.bar = bar;
}
}
let foo = new MyClass('baz');This approach is the way native classes in Javascript are moving in general and what we want to target but, unfortunately, is not codmoddable. Mass assigning properties to the class via |
Makes total sense. Thanks |
|
Discussed this in todays Ember.js Core Team meeting. Some of the take aways from the convo were:
None of these were blocking concerns, and we are collectively 👍 on moving this into final comment period. |
|
There has been no additional feedback since this RFC entered the final comment period, so its time has come... |
As per [RFC 337](emberjs/rfcs#337), the way classes are initialized has changed. Namely, arguments are no longer available in `constructor` but they are still available in `init`. Because we used `constructor` to set properties from passed in arguments before, we started seeing error on Travis: ``` Error: Property set failed: object in path "unwrappedApi" could not be found. ``` This change switches to using `init` where appropriate and is a backwards compatible change.
As per [RFC 337](emberjs/rfcs#337), the way classes are initialized has changed. Namely, arguments are no longer available in `constructor` but they are still available in `init`. Because we used `constructor` to set properties from passed in arguments before, we started seeing error on Travis: ``` Error: Property set failed: object in path "unwrappedApi" could not be found. ``` This change switches to using `init` where appropriate and is a backwards compatible change.
* Replace `constructor` with `init` As per [RFC 337](emberjs/rfcs#337), the way classes are initialized has changed. Namely, arguments are no longer available in `constructor` but they are still available in `init`. Because we used `constructor` to set properties from passed in arguments before, we started seeing error on Travis: ``` Error: Property set failed: object in path "unwrappedApi" could not be found. ``` This change switches to using `init` where appropriate and is a backwards compatible change. * Move class properties initialisation into `init` An unfortunate side-effect of replacing `constructor` with `init` is that class properties are no longer assigned properly. They used to be assigned after `init`. So all class properties initializers that are not decorated with `@argument` need to be moved in `init`.
|
As it currently stands, there's no place that can be linked to for a quick and easy understanding of what all this proposal entails. Can a TL;DR be added to the top of this RFC with super-clear and concise summaries of what it means for consumers? i.e.,
|
|
Can you update the "How we teach this" section? |
rendered