解构深层属性

74

我最近开始使用ES6的解构赋值语法,并且已经开始熟悉这个概念。我想知道是否可以使用相同的语法提取嵌套属性。

例如,假设我有以下代码:

let cagingIt = {
  foo: {
    bar: 'Nick Cage'
  }
};

我知道可以通过以下方式将foo提取为一个变量:

// where foo = { bar: "Nick Cage" }
let { foo } = cagingIt;

然而,是否有可能提取一个嵌套深层次的属性,例如 bar。或许像下面这样:

然而,如何提取一个嵌套非常深的属性,例如bar?也许可以尝试以下方法:

// where bar = "Nick Cage"
let { foo[bar] } = cagingIt;

我已尝试查找相关文档,但未能获得任何帮助。非常感谢您的帮助。谢谢!


1
{ bar } = cagingIt.foo; 这样写是否不够严谨? - Rob Foley
2
@RobFoley 绝对是一个不错的方法,但我更好奇的是语法是否支持深度嵌套属性的解构。 - Dom
5
你确实很喜欢尼古拉斯·凯奇。 - Esteban
同意@Dom的观点。我希望在Mongo中存在像foo.$.bar这样的解决方案。 - somnathbm
2
为什么我总是忘记这个,不得不一遍又一遍地回到这篇文章 :/ - Ska
4个回答

147

使用这种语法可以处理嵌套的对象和数组。鉴于上述问题,解决方案如下:

let cagingIt = {
      foo: {
        bar: 'Nick Cage'
      }
    };
let { foo: {bar: name} } = cagingIt;

console.log(name); // "Nick Cage"
在这个例子中,foo 是指属性名 "foo"。在冒号后面,我们使用 bar 表示属性 "bar"。最后,name 作为存储值的变量。 至于数组解构,你可以像这样处理:
let cagingIt = {
      foo: {
        bar: 'Nick Cage',
        counts: [1, 2, 3]
      }
    };

let { foo: {counts: [ ct1, ct2, ct3 ]} } = cagingIt;
console.log(ct2); // prints 2

它遵循与对象相同的概念,只是您可以使用数组解构并存储这些值。


1
整洁,我在 Babel repl 上试了一下,但没有尝试过那个语法。 - Rob Foley
1
类似的示例可以在 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Nested_object_and_array_destructuring 中找到。 - juvian
1
当您在深度解构中使用计算属性名称时,情况会变得非常复杂。 - Rob Foley
1
另一种奇怪的方式是使用 let { ['foo']: {['bar']: name} } = cagingIt; - juvian
3
请考虑到解构并不是空值安全的,因此使用这种方法存在风险。 - pakman
显示剩余4条评论

3
你可以将一个属性解构后,用其他名称重新命名:

在解构时使用 "as" 关键字:

const { foo: myFoo } = { foo: 'bar' }  // myFoo == 'bar'

这里将 foo 解构为 myFoo。你还可以将一个对象解构 "as" 一个解构后的对象。

const { foo: { bar } } = { foo: { bar: 'baz' } } // bar == 'baz'

每个情况下只定义了一个变量,分别是myFoobar,你可以看到它们在类似的位置,除了bar周围有{ }

你可以对嵌套层数进行任意数量的操作,但是如果不小心深入太多级,就会出现旧的"Cannot read properties of undefined(reading 'foo')"错误。

// Here's an example that doesn't work:
const foo = { bar: { notBaz: 1 } };
const { 
    bar: { 
        baz: { // baz is undefined in foo, so by trying to destructure it we're trying to access a property of 'undefined'
            qux
        }
    }
} = foo;
// throws Uncaught TypeError: Cannot read properties of undefined (reading 'baz')
// because baz is 'undefined'

// Won't run due to error above
console.log(qux); 

在这种情况下,很明显我们不应该尝试解构它,因为我们可以在前一行看到foo的定义并没有定义属性baz。然而,如果对象来自API,你无法保证期望结果的每个嵌套属性都是非null或不是undefined的,你无法预先知道。
你可以通过添加= {}来为解构对象设置默认值:
const { foo: myFoo = 'bar' } = { baz: 'qux' };  // myFoo == 'bar'
const { bar: { baz } = {} } = { qux: 'quuz' }   // baz == undefined 
// baz is destructured from the object that was set as the default for foo, which is undefined
// this would throw without the default object, as were trying to destructure from 'undefined'

你可以用深度解构来实现这个:

// Here's an example that works:
const foo = { bar: { notBaz: 1 } };
const { 
    bar: { 
        baz: {
            qux    // you can default the final item to anything you like as well including null or undefined, but it will be undefined in this case
        } = {}     // if 'baz' undefined, return {}
    } = {}         // if 'bar' undefined, return {}
} = foo;

console.log(qux); // logs 'undefined'

如果沿途有任何属性为nullundefined,它将导致返回空对象的级联,其下一级需要解构的属性将仅为undefined。但是在更深层次的对象中,这种格式可能需要很多行代码,使得情况变得非常复杂。这里有另一个选项,可以完成完全相同的操作。
const foo = { bar: { notBaz: 1 } };
const {qux} = foo?.bar?.baz ?? {}; // optional chaining and nullish coalescing

如果在整个过程中,foobarbaz有任何一个为null、undefined或nullish,它将返回一个空对象,你可以在??之后解构这个空对象。
如果你只需要提取一个属性,使用解构语法来解构{ qux }可能并没有太多意义,因为这样还需要添加nullish coalesced值?? {}。下面的写法可能更好。
const foo = { bar: { notBaz: 1 } };
const { qux, quux, quuz } = foo?.bar?.baz ?? {}; // this method is better for multiple properties
const quxFromBaz = foo?.bar?.baz?.qux;          // this method is better for single properties

对我个人而言,我认为在所有可选链接问题标记都包含在内时,它看起来有点凌乱,但这比在每个级别上进行嵌套解构和默认值处理的替代方案要好。并且它可以用于数组。
const foo = { 
    bar: [
        { a: 1 }, 
        { b: 2 }
    ] 
}
const c = foo?.bar?.[2]?.c   // c == undefined
// bar[2] will be undefined, so trying to access property 'c' would normally throw an error

2
如果您已经安装了lodash,您可以使用以下之一:

_.get

var object = { 'a': [{ 'b': { 'c': 3 } }] };

_.get(object, 'a[0].b.c');
// => 3

或者如果您需要多个键。

_.at

var object = { 'a': [{ 'b': { 'c': 3 } }, 4] };

_.at(object, ['a[0].b.c', 'a[1]']);
// => [3, 4]

您还可以安全地将_.at()与数组解构配对使用。非常适用于JSON响应。

[title, artist, release, artwork] = _.at(object, [
  'items[0].recording.title',
  'items[0].recording.artists[0].name',
  'items[0].recording.releases[0].title',
  'items[0].recording.releases[0].artwork[0].url'
]);

0

三层深度

如果有人需要帮助,这里有一些代码可以展示如何解构三层深度。在这个例子中,我使用一个数组上的find()方法。

const id = someId
array.find(({ data: { document: { docId }, }, }) => docId == id)

以上,数组数据的结构如下(数组中的每个对象具有相同的形状):

[{ 
   isSuccess: true, 
   isLoading: false,
   data: {
      foo: bar,
      ...,
      document: {
         docId: '123',
         ...
      },
}}]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接