首页
Preview

在JavaScript中安全地访问深度嵌套的值

简介

这是一篇简短的文章,旨在展示在JavaScript中安全地访问深度嵌套值的许多不同方法。以下示例都做同样的事情,虽然它们的方法可能不同,但它们都解决了同样的问题。

在开始之前,让我们更详细地了解一下我们实际上要解决的问题。

const props = {
  user: {
    posts: [
      { title: 'Foo', comments: [ 'Good one!', 'Interesting...' ] },
      { title: 'Bar', comments: [ 'Ok' ] },
      { title: 'Baz', comments: [] },
    ]
  }
}

想象一下我们有一个名为_props_的对象,它可能看起来像上面的例子。现在假设我们想获得用户第一篇帖子的评论。我们如何使用常规的JavaScript来实现这一点?

// access deeply nested values...
props.user &&
props.user.posts &&
props.user.posts[0] &&
props.user.posts[0].comments

这可能看起来像我们可能会遇到的问题,并且有道理,我们要确保在尝试访问它之前存在键或索引。进一步思考,假设我们还想仅读取第一条评论。为了解决这个问题,我们可能会更新我们之前的示例,以在访问第一个项目之前也检查评论是否实际存在。

// updating the previous example...
props.user &&
props.user.posts &&
props.user.posts[0] &&
props.user.posts[0].comments &&
props.user.posts[0].comments[0]

因此,每当我们想访问任何深度嵌套的数据时,我们都必须明确进行手动检查。 为了说明为什么这很麻烦,想象一下我们不想检查用户的帖子,而是想知道用户最后一条评论的博客标题。我们将无法建立在之前的示例之上。

// accessing user's comments
props.user &&
props.user.comments &&
props.user.comments[0] &&
props.user.comments[0].blog.title

示例可能有些夸张,但你懂的。我们需要逐级检查整个结构,直到达到我们正在搜索的值

好的,既然我们更好地了解了我们实际上要解决的问题,让我们看看我们可以采用的不同方法。示例从仅使用JavaScript开始,然后是Ramda,最后是Ramda与Folktale。虽然你可能不需要更高级的示例,但这应该是有趣的。特别是考虑到在访问深度嵌套的值时采用安全方法所获得的收益。

简化的JavaScript

为了开始,我们不想手动检查可空或未定义值,而是可以部署自己的小型但紧凑的函数,并且可以灵活处理任何提供的输入数据。

const get = (p, o) =>
  p.reduce((xs, x) => (xs && xs[x]) ? xs[x] : null, o)// let's pass in our props object...console.log(get(['user', 'posts', 0, 'comments'], props))
// [ 'Good one!', 'Interesting...' ]console.log(get(['user', 'post', 0, 'comments'], props))
// null

让我们分解一下我们的get函数。

const get = (p, o) =>
  p.reduce((xs, x) =>
    (xs && xs[x]) ? xs[x] : null, o)

我们将一个path定义作为第一个参数传递,将要从中检索值的对象作为第二个参数传递。

关于第二个参数是对象的事实,你可能会问自己:我们从中获得了什么? 定义一个通用函数,该函数知道特定路径并期望任何可能或可能没有给定路径的对象。

通过选择这种方法,我们可以使用我们以前的_props_对象或任何其他对象调用_getUserComments_。这还意味着我们必须像这样对我们的get函数进行编译。

const get = p => o =>
  p.reduce((xs, x) =>
    (xs && xs[x]) ? xs[x] : null, o)

最后,我们可以记录结果并验证是否按预期工作。

console.log(getUserComments(props))
// [ 'Good one!', 'Interesting...' ]console.log(getUserComments({user:{posts: []}}))
// null

我们的_get_函数本质上是提供的路径上的减少。

p.reduce((xs, x) =>
  (xs && xs[x]) ? xs[x] : null, o)

让我们采用简化的路径,其中我们只想访问_id_。

['id'].reduce((xs, x) => (xs && xs[x]) ? xs[x] : null, {id: 10})

我们使用提供的对象初始化_reduce_函数,然后检查对象是否已定义,如果是,则验证键是否存在。根据(xs && xs[x])的结果,我们返回值或null等等。

如所示,我们可以轻松地缓解必须隐式检查任何可空或未定义值的问题。如果你宁愿传递字符串路径而不是数组,则_get_函数只需要进行一些较小的调整,我将留给感兴趣的读者来实现。

Ramda

我们可以使用Ramda函数来实现相同的功能,而不是编写自己的函数。

Ramda自带的一个函数是path。 _path_需要两个参数,路径和对象。让我们在Ramda中重新编写示例。

const getUserComments = R.path(['user', 'posts', 0, 'comments'])

现在,我们可以使用_props_调用_getUserComments_,无论是否找到所需的值,都会返回null。

getUserComments(props) // [ 'Good one!', 'Interesting...' ]getUserComments({}) // null

但是,如果我们想在找不到指定路径时返回与null不同的内容,Ramda还提供了pathOr。 _pathOr_需要默认值作为初始参数。

const getUserComments = R.pathOr([], ['user', 'posts', 0, 'comments'])getUserComments(props) // [ 'Good one!', 'Interesting...' ]getUserComments({}) // []

感谢Gleb Bahmutov提供有关pathpathOr的见解。

Ramda + Folktale

让我们还将Folktale的Maybe加入到混合中。例如,我们可以构建一个通用的getPath函数,该函数期望路径以及我们要从中检索值的对象。

const getPath = R.compose(Maybe.fromNullable, R.path)const userComments =
  getPath(['user', 'posts', 0, 'comments'], props)

调用_getPath_将返回Maybe.JustMaybe.Nothing

console.log(userComments) // Just([ 'Good one!', 'Interesting...' ])

**通过将结果包装在Maybe中,我们获得了什么?**采用这种方法,我们现在可以安全地继续使用_userComments_,而无需手动检查userComments是否返回null或所需值。

console.log(userComments.map(x => x.join(',')))
// Just('Good one!,Interesting...')

当找不到值时,同样的方法也适用。

const userComments =
    getPath(['user', 'posts', 8, 'title'], props)

console.log(userComments.map(x => x.join(',')).toString())
// Nothing

查看示例。我们还可以将所有的props包装在Maybe中。这使我们能够使用composeK,它知道如何将props链接在一起。

// example using composeK to access a deeply nested value.const getProp = R.curry((name, obj) =>
  Maybe.fromNullable(R.prop(name, obj)))const findUserComments = R.composeK(
  getProp('comments'),
  getProp(0),
  getProp('posts'),
  getProp('user')
)console.log(findUserComments(props).toString())
// Just([ 'Good one!', 'Interesting...' ])console.log(findUserComments({}).toString())
// Nothing

查看示例。

这些都非常高级,使用Ramda的path应该足够了。但是让我们快速看一下其他示例。例如,我们也可以使用Ramda的composechain来实现与上面示例相同的效果。

// using compose and chainconst getProp = R.curry((name, obj) =>
  Maybe.fromNullable(R.prop(name, obj)))const findUserComments =
  R.compose(
    R.chain(getProp('comments')),
    R.chain(getProp(0)),
    R.chain(getProp('posts')),
    getProp('user')
  )console.log(findUserComments(props).toString())
// Just([ 'Good one!', 'Interesting...' ])console.log(findUserComments({}).toString())
// Nothing

查看示例。

与以前的示例相同,但这次使用pipeK

// example using pipeK to access a deeply nested value.const getProp = R.curry((name, obj) =>
  Maybe.fromNullable(R.prop(name, obj)))const findUserComments = R.pipeK(
  getProp('user'),
  getProp('posts'),
  getProp(0),
  getProp('comments')
)console.log(findUserComments(props).toString())
// Just([ 'Good one!', 'Interesting...' ])console.log(findUserComments({}).toString())
// Nothing

查看示例。

还可以通过使用mappipeK示例进行检查。感谢Tom Harding提供的pipeK示例。

镜头

最后,我们还可以使用lenses。Ramda带有lensProplensPath

// lenses
const findUserComments =
  R.lensPath(['user', 'posts', 0, 'comments'])console.log(R.view(findUserComments, props))
// [ 'Good one!', 'Interesting...' ]

同样,我们可以像我们的path示例一样将结果包装在Maybe中。当需要更新任何嵌套值时,lenses非常有用。有关lenses的更多信息,请查看我的JavaScript中的Lenses简介文章。

总结

处理嵌套数据时,我们应该对许多检索值的方式有一个良好的概述。除了知道如何推出我们自己的实现之外,我们还应该基本了解Ramda提供的有关此问题的函数,并且甚至可能更好地了解为什么将结果包装在Either或Maybe中是有意义的。此外,我们还触及了_lenses_主题,除了使我们能够检索任何值之外,还使我们能够更新深度嵌套的数据而不会使我们的对象发生变异。

最后,你不应该再编写以下代码了。

// updating the previous example...props.user &&
props.user.posts &&
props.user.posts[0] &&
props.user.posts[0].comments &&
props.user.posts[0].comments[0]

特别感谢Gleb BahmutovTom HardingToastal在Twitter上提供的示例和见解。

更新:2017年3月24日

Gleb Bahmutov发表了Call me Maybe,将这里讨论的概念推进了一步。强烈推荐阅读。

译自:https://medium.com/javascript-inside/safely-accessing-deeply-nested-values-in-javascript-99bf72a0855a

版权声明:本文内容由TeHub注册用户自发贡献,版权归原作者所有,TeHub社区不拥有其著作权,亦不承担相应法律责任。 如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

点赞(0)
收藏(0)
一个人玩
先找到想要的,然后出发

评论(0)

添加评论