Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add deprecation guide for array prototype extensions for V5 #1192

Merged
merged 22 commits into from
Jul 12, 2024

Conversation

smilland
Copy link
Contributor

@smilland smilland commented Sep 1, 2022

RFC: Deprecate array prototype extensions

Rendered content

It's ready for review, though only can be merge after the RFC is approved.

@smilland smilland changed the title Add deprecate guide for array prototype extensions for V5 Add deprecation guide for array prototype extensions for V5 Sep 1, 2022
@netlify
Copy link

netlify bot commented Sep 1, 2022

Deploy Preview for ember-deprecations ready!

Name Link
🔨 Latest commit 25d831d
🔍 Latest deploy log https://app.netlify.com/sites/ember-deprecations/deploys/66917b07f057110008f621f4
😎 Deploy Preview https://deploy-preview-1192--ember-deprecations.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

content/ember/v5/deprecate-array-prototype-extensions.md Outdated Show resolved Hide resolved
content/ember/v5/deprecate-array-prototype-extensions.md Outdated Show resolved Hide resolved
content/ember/v5/deprecate-array-prototype-extensions.md Outdated Show resolved Hide resolved
? a.food?.localCompare(b.food)
: a.isFruit - b.isFruit;
}); // [{ food: 'apple', isFruit: true }, { food: 'beans', isFruit: false }]
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, I think that it would have to be stated here that this implementation is not as robust as Ember Array's .sortBy. It has field names hardcoded and can't take an arbitrary number of field names.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could also give people a replacement function they can paste into their own code. This is adapted from ember's own implementation:

import { compare } from '@ember/utils';
function sortBy<T>(array: T[], ...sortKeys: string[]): T[] {
  return this.toSorted((a: T, b: T) => {
    for (let i = 0; i < sortKeys.length; i++) {
      let key = sortKeys[i];
      let propA = a[key];
      let propB = b[key];
      let compareValue = compare(propA, propB);
      if (compareValue) {
        return compareValue;
      }
    }
    return 0;
  });
}

Comment on lines +232 to +242
Before:
```js
const someArray = [{ food: 'apple', isFruit: true }, { food: 'beans', isFruit: false }];
someArray.toArray(); // [{ food: 'apple', isFruit: true }, { food: 'beans', isFruit: false }]
```

After:
```js
const someArray = [{ food: 'apple', isFruit: true }, { food: 'beans', isFruit: false }];
[...someArray] // [{ food: 'apple', isFruit: true }, { food: 'beans', isFruit: false }]
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I never used this method but the docs are saying:

Simply converts the object into a genuine array. The order is not guaranteed. Corresponds to the method implemented by Prototype.

So I guess an example should show how to convert an object into an array. I'd have to check how the original function works but would this be expected?

const person = {
    firstName: 'John',
    lastName: 'Doe'
};

Object.entries(person); // [ [ 'firstName', 'John' ], [ 'lastName', 'Doe' ] ]

🤔

smilland and others added 4 commits March 30, 2023 17:39
Co-authored-by: Tomek Nieżurawski <tommaqs@gmail.com>
Co-authored-by: Tomek Nieżurawski <tommaqs@gmail.com>
Co-authored-by: Tomek Nieżurawski <tommaqs@gmail.com>
Co-authored-by: Tomek Nieżurawski <tommaqs@gmail.com>
@smilland
Copy link
Contributor Author

thank you so much for reviewing @tniezurawski !


@action
pushObject(value) {
this.abc = [...this.abc, value];

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a little confused by the difference between this example and the pushObject example above which uses TrackedArray. Is it a requirement to assign to this.abc when using @tracked but not when using TrackedArray? Or is the different method shown to indicate that both approaches are valid?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, @tracked will only trigger the reactivity when a new value gets assigned. When the existing native array is internally mutated, like calling .push(), then this will not be reactive. In opposite to TrackedArray, which does this kind of deep tracking. So I think the example is right.


@action
pushObjects(values) {
this.abc.splice(this.abc.length, 0, ...values);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this.abc.push(...values) work here? It would be simpler and match the first after example for pushObject above?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, i think so!

Copy link
Contributor

@simonihmig simonihmig left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lots of details in that migration guide, great work @smilland! I know this has been quite a whole since you worked on this, so would you still be available to look into and work on the provided suggestions?

The RFC was accepted long ago, so I think we can and should get this merged asap, as I would like to also get the actual deprecation implemented before we miss the time window for the v6 release!

}
}

[new Person('Tom'), new Person('Joe')].map(person => person['greet']?.('Hi')); // ['Hi Tom', 'Hi Joe']
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
[new Person('Tom'), new Person('Joe')].map(person => person['greet']?.('Hi')); // ['Hi Tom', 'Hi Joe']
[new Person('Tom'), new Person('Joe')].forEach(person => person.greet('Hi')); // ['Hi Tom', 'Hi Joe']

I think forEach is more appropriate than map here, since we don't use the value returned by map. Also the method call can be simplified as suggested I believe? invoke might do that optional chaining internally, but if people were to rewrite that code to native, I think this is what they would/should write.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

invoke returns a value like map, so even though this example doesn't motivate using the return value I think it's correct to tell people to use map.

: a.isFruit - b.isFruit;
}); // [{ food: 'apple', isFruit: true }, { food: 'beans', isFruit: false }]
```
#### `toArray`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this example even needed? I think you would use toArray an an array-like object like A(), to turn it into a native array. But here we are only dealing with native arrays, so what's the point of toArray() usage then?

content/ember/v5/deprecate-array-prototype-extensions.md Outdated Show resolved Hide resolved
content/ember/v5/deprecate-array-prototype-extensions.md Outdated Show resolved Hide resolved
content/ember/v5/deprecate-array-prototype-extensions.md Outdated Show resolved Hide resolved

@action
pushObject(value) {
this.abc = [...this.abc, value];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, @tracked will only trigger the reactivity when a new value gets assigned. When the existing native array is internally mutated, like calling .push(), then this will not be reactive. In opposite to TrackedArray, which does this kind of deep tracking. So I think the example is right.


@action
pushObjects(values) {
this.abc.splice(this.abc.length, 0, ...values);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, i think so!

content/ember/v5/deprecate-array-prototype-extensions.md Outdated Show resolved Hide resolved
content/ember/v5/deprecate-array-prototype-extensions.md Outdated Show resolved Hide resolved
ef4 and others added 2 commits June 14, 2024 14:56
Co-authored-by: Simon Ihmig <simon.ihmig@gmail.com>
Co-authored-by: Tomek Nieżurawski <tommaqs@gmail.com>
Copy link
Contributor

@ef4 ef4 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two other general notes that would be nice to include in this guide:

  1. When the array prototype extensions are off, passing a native array to import { A } from '@ember/array' mutates the native array to re-add the prototype extensions. This can cause surprising behavior. (If somebody is excited to also deprecate A it's worth doing too.)
  2. When the array prototype extensions are off, if you take an array-like value from ember-data and pass it to A, it breaks.

Other than these suggestions, this looks good to go. 👍

After:
```js
const someArray = [{ food: 'apple', isFruit: true }, { food: 'beans', isFruit: false }];
[...someArray].sort((a, b) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

toSorted is new and it's a non-mutating version of sort:

Suggested change
[...someArray].sort((a, b) => {
someArray.toSorted((a, b) => {

? a.food?.localCompare(b.food)
: a.isFruit - b.isFruit;
}); // [{ food: 'apple', isFruit: true }, { food: 'beans', isFruit: false }]
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could also give people a replacement function they can paste into their own code. This is adapted from ember's own implementation:

import { compare } from '@ember/utils';
function sortBy<T>(array: T[], ...sortKeys: string[]): T[] {
  return this.toSorted((a: T, b: T) => {
    for (let i = 0; i < sortKeys.length; i++) {
      let key = sortKeys[i];
      let propA = a[key];
      let propB = b[key];
      let compareValue = compare(propA, propB);
      if (compareValue) {
        return compareValue;
      }
    }
    return 0;
  });
}

@ef4
Copy link
Contributor

ef4 commented Jun 28, 2024

This deprecation is activated in ember beta 5.10, so it would be great to get this landed. I think only a small amount of minor fixes are needed here to get this green and mergeable.

ef4 and others added 4 commits June 28, 2024 14:56
Co-authored-by: Jared Galanis <jaredgalanis@users.noreply.github.com>
Co-authored-by: Jared Galanis <jaredgalanis@users.noreply.github.com>
Co-authored-by: Simon Ihmig <simon.ihmig@gmail.com>
Co-authored-by: Simon Ihmig <simon.ihmig@gmail.com>
@ef4
Copy link
Contributor

ef4 commented Jul 12, 2024

I don't have permissions to do the Percy approval but it passes a visual inspection. Merging, because this deprecation is already released and we need a non-broken link.

@ef4 ef4 merged commit 9f8ac01 into ember-learn:main Jul 12, 2024
3 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants