-
-
Notifications
You must be signed in to change notification settings - Fork 71
Add Array deprecation guides #1406
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
Draft
wagenet
wants to merge
2
commits into
ember-learn:main
Choose a base branch
from
wagenet:ember-array
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
--- | ||
title: Deprecation of @ember/array Foundation APIs | ||
until: "6.0" | ||
since: "5.8" | ||
displayId: array.foundations | ||
--- | ||
|
||
The foundational APIs of `@ember/array`, including the `A()` function and the core mixins (`EmberArray`, `MutableArray`, `NativeArray`), are deprecated. These were used to create and extend arrays with Ember's observability. The modern approach is to use native JavaScript arrays, and `TrackedArray` from `tracked-built-ins` when reactivity is needed. | ||
|
||
> Warning: If you have Computed Properties that rely on EmberArray values, you may find their behavior affected when you migrate. Please consider migrating these Computed Properties to use native getters before beginning the EmberArray migration. | ||
|
||
### `A()` Function and Core Mixins | ||
|
||
The `A()` function would wrap a native array, making it an `EmberArray`. The `EmberArray` and `MutableArray` mixins could be used to build custom array-like classes. | ||
|
||
**Before** | ||
```javascript | ||
import { A } from '@ember/array'; | ||
import EmberObject from '@ember/object'; | ||
import { MutableArray } from '@ember/array'; | ||
|
||
let emberArr = A([1, 2, 3]); | ||
emberArr.pushObject(4); | ||
|
||
const MyArray = EmberObject.extend(MutableArray, { | ||
// ... implementation ... | ||
}); | ||
``` | ||
|
||
**After** | ||
Use native arrays for standard array operations. For arrays that need to be tracked for reactivity in components and other classes, use `TrackedArray` from the `tracked-built-ins` addon. | ||
|
||
```javascript | ||
// For a standard array | ||
let nativeArr = [1, 2, 3]; | ||
nativeArr.push(4); | ||
|
||
// For a tracked array | ||
import { TrackedArray } from 'tracked-built-ins'; | ||
let trackedArr = new TrackedArray([1, 2, 3]); | ||
trackedArr.push(4); // This mutation is tracked | ||
``` | ||
|
||
### Utility Functions: `isArray` and `makeArray` | ||
|
||
These functions helped create and check for arrays. | ||
|
||
**Before** | ||
```javascript | ||
import { isArray, makeArray } from '@ember/array'; | ||
let isArr = isArray([]); | ||
let ensuredArr = makeArray('hello'); | ||
``` | ||
|
||
**After** | ||
Use native JavaScript equivalents. | ||
|
||
```javascript | ||
// isArray() -> Array.isArray() | ||
let isArr = Array.isArray([]); | ||
|
||
// makeArray() -> custom helper or ensure data is correct | ||
function ensureArray(value) { | ||
if (value === null || value === undefined) return []; | ||
return Array.isArray(value) ? value : [value]; | ||
} | ||
let ensuredArr = ensureArray('hello'); | ||
``` | ||
|
||
### Creating Custom Arrays with Proxies | ||
|
||
For advanced use cases where you might have created a custom class based on `MutableArray` to add special behaviors to your array, the modern JavaScript equivalent is to use a `Proxy`. A `Proxy` object allows you to intercept and redefine fundamental operations for a target object (like an array), enabling you to create powerful custom wrappers. | ||
|
||
**Example: A Logging Array** | ||
|
||
Imagine you want to log every time an item is pushed to an array. | ||
|
||
**Before**, you might have done this with `MutableArray`: | ||
|
||
```javascript | ||
import EmberObject from '@ember/object'; | ||
import { MutableArray } from '@ember/array'; | ||
|
||
const LoggingArray = EmberObject.extend(MutableArray, { | ||
// Internal content array | ||
_content: null, | ||
|
||
init() { | ||
this._super(...arguments); | ||
this._content = this._content || []; | ||
}, | ||
|
||
// Required primitives | ||
objectAt(idx) { return this._content[idx]; }, | ||
get length() { return this._content.length; }, | ||
|
||
// Override replace to add logging | ||
replace(idx, amt, objects) { | ||
if (amt === 0) { | ||
console.log(`Adding items: ${objects.join(', ')}`); | ||
} | ||
this._content.splice(idx, amt, ...objects); | ||
this.arrayContentDidChange(idx, amt, objects.length); | ||
} | ||
}); | ||
|
||
let arr = LoggingArray.create({ _content: [1, 2] }); | ||
arr.pushObject(3); // Logs: "Adding items: 3" | ||
``` | ||
|
||
**After**, you can achieve the same result more cleanly by wrapping a `TrackedArray` in a `Proxy`. This allows you to add custom behavior while preserving the reactivity provided by `TrackedArray`. | ||
|
||
```javascript | ||
import { TrackedArray } from 'tracked-built-ins'; | ||
|
||
function createTrackedLoggingArray(initialItems) { | ||
// Start with a TrackedArray instance | ||
const trackedArr = new TrackedArray(initialItems); | ||
|
||
return new Proxy(trackedArr, { | ||
get(target, prop, receiver) { | ||
// Intercept the 'push' method | ||
if (prop === 'push') { | ||
return function(...args) { | ||
console.log(`Adding items via push: ${args.join(', ')}`); | ||
// Call the original push method on the TrackedArray | ||
// This will trigger reactivity automatically. | ||
return target.push(...args); | ||
} | ||
} | ||
|
||
// Forward all other property access and method calls to the TrackedArray | ||
const value = Reflect.get(target, prop, receiver); | ||
return typeof value === 'function' ? value.bind(target) : value; | ||
}, | ||
|
||
set(target, prop, value, receiver) { | ||
// Intercept direct index assignment | ||
if (!isNaN(parseInt(prop, 10))) { | ||
console.log(`Setting index ${prop} to ${value}`); | ||
} | ||
// Forward the set operation to the TrackedArray to trigger reactivity | ||
return Reflect.set(target, prop, value, receiver); | ||
} | ||
}); | ||
} | ||
|
||
// In a component: | ||
class MyComponent { | ||
loggingArray = createTrackedLoggingArray([1, 2]); | ||
|
||
addItem() { | ||
this.loggingArray.push(3); // Logs and triggers an update | ||
} | ||
|
||
updateItem() { | ||
this.loggingArray[0] = 'new value'; // Logs and triggers an update | ||
} | ||
} | ||
``` | ||
|
||
This `Proxy` approach is very powerful. By wrapping a `TrackedArray`, you can layer in custom logic while letting it handle the complexities of reactivity. This is the recommended pattern for creating advanced, observable array-like objects in modern Ember. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
--- | ||
title: Deprecation of @ember/array Read Methods | ||
until: "6.0" | ||
since: "5.8" | ||
displayId: array.read-methods | ||
--- | ||
|
||
All read-only methods and computed properties from `@ember/array` are deprecated. You should use native JavaScript array methods and properties instead. This guide covers the common read-only APIs and their native equivalents. | ||
|
||
### `firstObject` and `lastObject` | ||
|
||
These computed properties provided safe access to the first and last elements of an array. | ||
|
||
**Before** | ||
```javascript | ||
import { A } from '@ember/array'; | ||
let arr = A(['a', 'b', 'c']); | ||
let first = arr.get('firstObject'); // 'a' | ||
let last = arr.get('lastObject'); // 'c' | ||
``` | ||
|
||
**After** | ||
Use native array bracket notation, or the `at()` method for accessing elements from the end of the array. | ||
|
||
```javascript | ||
let arr = ['a', 'b', 'c']; | ||
let first = arr[0]; | ||
let last = arr.at(-1); | ||
``` | ||
|
||
### `objectAt` and `objectsAt` | ||
|
||
These methods provided safe, index-based access to array elements. | ||
|
||
**Before** | ||
```javascript | ||
import { A } from '@ember/array'; | ||
let arr = A(['a', 'b', 'c']); | ||
let middle = arr.objectAt(1); // 'b' | ||
let some = arr.objectsAt([0, 2]); // ['a', 'c'] | ||
``` | ||
|
||
**After** | ||
Use native array bracket notation for `objectAt`. For `objectsAt`, you can use `map`. | ||
|
||
```javascript | ||
let arr = ['a', 'b', 'c']; | ||
let middle = arr[1]; | ||
let some = [0, 2].map(index => arr[index]); | ||
``` | ||
|
||
### `mapBy`, `filterBy`, `rejectBy`, `findBy` | ||
|
||
These methods were shortcuts for common mapping and filtering operations on arrays of objects. | ||
|
||
**Before** | ||
```javascript | ||
import { A } from '@ember/array'; | ||
let users = A([ | ||
{ name: 'John', isActive: true }, | ||
{ name: 'Jane', isActive: false }, | ||
]); | ||
let names = users.mapBy('name'); | ||
let active = users.filterBy('isActive', true); | ||
let john = users.findBy('name', 'John'); | ||
``` | ||
|
||
**After** | ||
Use the native `map`, `filter`, and `find` methods with arrow functions. | ||
|
||
```javascript | ||
let users = [ | ||
{ name: 'John', isActive: true }, | ||
{ name: 'Jane', isActive: false }, | ||
]; | ||
let names = users.map(user => user.name); | ||
let active = users.filter(user => user.isActive === true); | ||
let john = users.find(user => user.name === 'John'); | ||
``` | ||
|
||
### `uniqBy` | ||
|
||
`uniqBy` created a new array with unique elements based on a property. | ||
|
||
**Before** | ||
```javascript | ||
import { uniqBy } from '@ember/array'; | ||
let users = [{ id: 1 }, { id: 2 }, { id: 1 }]; | ||
let unique = uniqBy(users, 'id'); | ||
``` | ||
|
||
**After** | ||
Use a `Map` to efficiently create a unique list. | ||
|
||
```javascript | ||
let users = [{ id: 1 }, { id: 2 }, { id: 1 }]; | ||
let unique = Array.from( | ||
users.reduce((map, user) => map.set(user.id, user), new Map()).values() | ||
); | ||
``` | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
--- | ||
title: Deprecation of @ember/array Write Methods | ||
until: "6.0" | ||
since: "5.8" | ||
displayId: array.write-methods | ||
--- | ||
|
||
All methods from `@ember/array` that mutate or "write" to an array are deprecated. This includes observable methods like `pushObject`. | ||
|
||
The modern approach is to use `TrackedArray` from the [`tracked-built-ins`](https://github.com/tracked-tools/tracked-built-ins) addon. This class provides a tracked version of the native JavaScript `Array` that can be mutated directly, and these mutations will be tracked automatically. | ||
|
||
First, install the addon: | ||
|
||
```bash | ||
ember install tracked-built-ins | ||
``` | ||
|
||
### Observable Write Methods | ||
|
||
Methods like `pushObject`, `popObject`, `removeObject`, `insertAt`, and `removeAt` were used to modify arrays in a way that Ember's classic observability system could track. | ||
|
||
**Before** | ||
```javascript | ||
import { A } from '@ember/array'; | ||
let arr = A([1, 2, 3]); | ||
|
||
arr.pushObject(4); | ||
arr.removeAt(1, 1); // remove 1 item at index 1 | ||
``` | ||
|
||
**After** | ||
Use `TrackedArray` and mutate it directly with standard JavaScript array methods. Using `TrackedArray` provides an ergonomic API that is nearly identical to working with plain JavaScript arrays, while providing the reactivity needed for your application's UI to update automatically. | ||
|
||
```javascript | ||
import { TrackedArray } from 'tracked-built-ins'; | ||
|
||
// In a component or class | ||
class MyComponent { | ||
myArray = new TrackedArray([1, 2, 3]); | ||
|
||
addItem() { | ||
// pushObject -> push | ||
this.myArray.push(4); | ||
} | ||
|
||
removeItem() { | ||
// removeAt -> splice | ||
this.myArray.splice(1, 1); | ||
} | ||
|
||
clearItems() { | ||
// clear -> set length to 0 | ||
this.myArray.length = 0; | ||
} | ||
} | ||
``` | ||
|
||
This pattern applies to all mutation methods. Here is a brief mapping for the most common methods: | ||
|
||
| `@ember/array` Method | Native `TrackedArray` Method | | ||
|-----------------------|------------------------------| | ||
| `pushObject(s)` | `push` / `...` (spread) | | ||
| `popObject()` | `pop` | | ||
| `shiftObject()` | `shift` | | ||
| `unshiftObject(s)` | `unshift` | | ||
| `insertAt(idx, obj)` | `splice(idx, 0, obj)` | | ||
| `removeAt(idx, len)` | `splice(idx, len)` | | ||
| `clear()` | `length = 0` | | ||
| `replace()` | `splice` | | ||
|
||
### Handling Uniqueness and Specific Objects | ||
|
||
For methods like `addObject` and `removeObject` that deal with specific object instances or uniqueness, you need a bit more logic. | ||
|
||
```javascript | ||
// In your component class with `myArray = new TrackedArray([...])` | ||
|
||
// removeObject replacement | ||
removeItem(item) { | ||
const index = this.myArray.indexOf(item); | ||
if (index > -1) { | ||
this.myArray.splice(index, 1); | ||
} | ||
} | ||
|
||
// addObject replacement | ||
addUniqueItem(item) { | ||
if (!this.myArray.includes(item)) { | ||
this.myArray.push(item); | ||
} | ||
} | ||
``` | ||
|
||
Alternatively, if you are working with a list that must be unique, consider using a `Set` or `TrackedSet` from `tracked-built-ins`, as they handle uniqueness automatically. |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I remember correctly, the
A()
function actually mutates its argument by enhancing its prototype. This description ofA()
should probably say that. The failure mode is that someone removes anA()
in one part of their app and a far-away part breaks because that part was subtly relying on the prototype extensions.