New syntax for the "as" proposal

This is another proposal for rebinding destructured properties to an object.

The goal is to provide an alternative syntax for the "as" proposal, based on the "operator restructuring" proposal syntax.

This proposal can also be related to this other one.

Destructuring assignement

const obj = { a: 1, b: 2, c: 3 };

const newObj.{ a, b: newB } = obj;

console.log(a); // 1
console.log(newB); // 2
console.log(newObj); // { a: 1, newB: 2 }

Function parameter destructuring

const obj = { a: 1, b: 2 };

function doSomething (obj.{ a, b: newB, c = 3 }) {
  console.log(a); // 1
  console.log(newB); // 2
  console.log(c); // 3
  console.log(obj); // { a: 1, newB: 2, c: 3 }
}
doSomething(obj);

Using with imports

import obj.{ a, b as newB } from './obj.js';

console.log(a); // 1
console.log(newB); // 2
console.log(obj); // { a: 1, newB: 2 }

Pros and Cons

pros:

  • object name placed before its properties
  • no conflict with Typescript as operator

cons:

  • It might not be clear at first glance what we're doing with this syntax. Maybe because we are not used to it yet? I'd need your feedback on this point! :)

Array syntax

Also, we need to see if we can (and should) extend this proposal for arrays.

Array destructured property rebinding seems less useful compared to objects.
Here is how it would look like:

/* Array destructuring */

const arr = [1, 2, 3];
const newArr.[a, b] = arr;

console.log(a); // 1
console.log(b); // 2
console.log(newArr); // [1, 2]
/* Function parameter destructuring */

const arr = [1, 2];

function doSomething (arr.[a, b, c = 3]) {
  console.log(a); // 1
  console.log(b); // 2
  console.log(c); // 3
  console.log(obj); // [1, 2, 3]
}
doSomething(arr);
/* No use case for imports */

First, I'd like to point out that the syntax .[] is very close to the optional chaining operator ?.[] syntax. This could potentially create confusion among programmers.

Also, I can't really think of a pertinent use case for using this syntax on arrays. But maybe others will :)

Feel free to share your thoughts about this proposal, especially about array destructured property rebinding!

(also if you can come with a better name than "destructured property rebinding" that would be great :smiley:)

1 Like

I think "destructured property rebinding" will still be useful. An example use case:

async function doSomething(...args.[a, b, c]) {
  if (busy) {
    setTimeout(() => doSomething(...args), 1000)
  } else {
    busy = true
    await someTask(anotherTask(a, b), c)
    busy = false
  }
}

(yes, in the case of function arguments, the "arguments" variable could be used instead, but that variable has some problems, and the syntax is useful in other places as well)

I'd also like to bring up how this syntax might work with nested destructuring.

Instead of writing this:

const { x: x.{ y } } = obj

What if we could write this?

const { x.{ y } } = obj
1 Like

While I'm all in favor of having some sort of pick notation, and this seems to be a kind of reverse pick, what I'm not a fan of is also having to include those picked properties as identifiers in the current scope. I don't think I've ever had a use case where I've wanted both.

In fact I would rather have the object in this syntax to be the original value in full. I'm more often faced with situations like:

function Component(props) {
  const { text } = props
  return <div>
    {text}
    <SubComponent {...props} />
  </div>
}

where I would liked to have instead used something along the lines of

function Component(props.{text}) {
  return <div>
    {text}
    <SubComponent {...props} />
  </div>
}

where props would pass through all values of the original props to SubComponent, not just those destructured for use in Component.

1 Like

Ah, whoops. I should have looked closer at your examples @Clemdz - I had thought that when you did obj.{ a, b } = anotherObj, that obj was just getting bound to anotherObj, not that it was getting restructured. (meaning, I thought it was literally just a different syntax for the as proposal) I see more similarity now with the restructuring syntax I was proposing in the object-restructuring thread.

I think @senocular has a good point though, where I'm not sure how valuable it would be to restructure and destructure the object at the same time, and maybe it would be good to treat this more like an alternative syntax for the "as" proposal.

I can see I may not have been very clear about what I wanted to achieve with this proposal!

Also yes, I realize that I started to think about a replacement for the "as proposal" syntax, but then shifted away to arrive to this proposal, and now the version I'm sharing here does is not related so much to the former. So I'm sorry for the confusion here.

So actually yes, there are two possible ways this could work:

  • only getting properties as identifiers for the current scope
  • getting properties as identifiers for the current scope while rebinding them

Only getting properties as identifiers

The first one comes very handy for function, as we keep all passed arguments, while creating identifiers that can be used directly:

const obj = { a: 1, b: 2, c: 3 };

function doSomething (obj.{ a }) {
  a; // 1
  obj; // { a: 1, b: 2, c: 3 }
}
doSomething(obj);

This could also be used along the spread operator really well as you mentioned @theScottyJam.

Rebinding properties

On the other hand, when using object destructuring assignment or importing from libraries, the second option seems a lot better.

For object destructuring, is make more sense to rebind destructured properties to a completely new object, because you already have the object containing all your properties in the first place

const obj = { a: 1, b: 2, c: 3 };
const newObj.{ a, b } = obj; // { a: 1, b: 2 }
// Would be useless that newObj === obj
// because in that case you could simply do
// const { a, b} = obj

Also, when importing, the second solution is better, as this would keep tree shaking optimization:

import obj.{ a, b } from './library'

obj; // { a, b }

// we don't want obj to be: { a, b, c, anotherPackageVariable, ... }
// because that would ruin he purpose of tree shaking

As an example, I've been testing FontAwesome component library recently, and this illustrates very well what I mean:

// Importing icons
import { faUser, faMapMarkerAlt, faEnvelope, faPhone, faGlobe, faCog, faTags } from '@fortawesome/free-solid-svg-icons'
// Adding them to the library so that we can use them
library.add(faUser, faMapMarkerAlt, faEnvelope, faPhone, faGlobe, faCog, faTags);

/*
 * In that case, restructuring could be very handy
 * so that we can add to library all imported icons at once
 * without importing the whole library in the first place
 */

import icons.{ faUser, faMapMarkerAlt, faEnvelope, faPhone, faGlobe, faCog, faTags } from '@fortawesome/free-solid-svg-icons'
library.add(...Object.values(icons));

What to choose?

At the end, I went for the second solution, as it comes most useful for both object destructuring assignment and imports, and still useful for functions parameter destructuring. On the other hand, the first solution would be very useful for functions parameter destructuring, while being totally useless for both object destructuring assignment and imports.

However, now that I see how useful the first solution could be for function parameter destructuring, I'm no longer sure that the second solution should be the one way to go.

But also, I'm not sure either that the two solutions could be used together, as it could be confusing to have a syntax behaving differently depending on where it is used.


I can feel that this syntax can have great potential, but we still need to find what's the best way to express it.
1 Like