引见
这是一篇漫笔,旨在展现多种在javascript中安全地接见深层嵌套值的体式格局。
下面的例子经由过程差别的体式格局来处理这一题目。
最先之前,让我们看下现实碰到这类状态时..
假设有一个props对象(以下),当我们须要猎取user对象的posts的第一条的comments对象,通常会怎样操纵?
const props = {
user: {
posts: [
{ title: 'Foo', comments: [ 'Good one!', 'Interesting...' ] },
{ title: 'Bar', comments: [ 'Ok' ] },
{ title: 'Baz', comments: [] },
]
}
}
// access deeply nested values...
props.user &&
props.user.posts &&
props.user.posts[0] &&
props.user.posts[0].comments
最直白的体式格局是确保每个key或index的存在再去接见它的下一级。斟酌的多点,当需求变化须要去要求第一条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]
所以每次我们想要接见深度嵌套的数据时,都必须明确地举行手动搜检。也许很难说清这一点,试想一下当我们不愿望磨练users对象下的posts,只是愿望猎取到users下的末了一条comment,和前面的处理思绪是相违犯的。
这个例子可以有些夸大,但你懂我的意义,为了获得深层嵌套的值,我们须要磨练全部构造的每一级(一切父级)。
所以,如今我们已更好地舆解了现实想要处理的题目,让我们来看看差别的处理方案。前面一些是经由过程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)作为第一个参数,须要猎取的对象(object)作为第二个参数。
思索一下这第二个参数o(object),你可以会问本身:我们希冀这个要领有什么功用?应当是一个输入特定途径而且针对任何对象都能返回是不是存在预期对象的要领。
const get = p => o =>
p.reduce((xs, x) =>
(xs && xs[x]) ? xs[x] : null, o)
const getUserComments = get(['user', 'posts', 0, 'comments'])
经由过程这类体式格局,我们可以挪用getUserComments
和之前的props对象或是任何其他对象。这也暗示我们必须得像如许不断揣摩这个get
函数,
终究我们能打印出结果,考证下是不是如预期得结果。
console.log(getUserComments(props))
// [ 'Good one!', 'Interesting...' ]
console.log(getUserComments({user:{posts: []}}))
// null
get
函数实质上就是在削减先前的途径。
让我们来简化一下,如今我们只想接见这个id。
['id'].reduce((xs, x) => (xs && xs[x]) ? xs[x] : null, {id: 10})
我们用供应的对象初始化reduce
函数,每一层经由过程(xs && xs[x])
磨练对象是不是被定义且有用, 然后顺次递归或是返回null退出。
就像上面的例子一样,我们可以轻盈地处理这一题目。固然假如你倾向习气用字符串途径而不是数组来表达途径,还须要对get
函数做一些小修改,我将留给感兴趣的读者来完成。
Ramda
我们也可以应用Ramda
函数库来完成雷同的功用,而不是编写本身的函数。Ramda
供应了一个path
要领,两个参数输入, path
以及object
。让我们用Ramda
重写这个例子。
const getUserComments = R.path(['user', 'posts', 0, 'comments'])
如今经由过程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供应关于path和pathOr的看法。
Ramda + Folktale
让我们再到场Folktale
的Maybe
。比方我们可以构建一个更通用的getPath
函数(一样传入path和object)。
const getPath = R.compose(Maybe.fromNullable, R.path)
const userComments =
getPath(['user', 'posts', 0, 'comments'], props)
挪用getPath
会返回Maybe.Just
或是Maybe.Nothing
。
console.log(userComments) // Just([ 'Good one!', 'Interesting...' ])
将我们的返回结果包在Maybe中有什么用呢?经由过程采纳这类体式格局,我们可以安全地运用userComments
,无需手动磨练userComments
是不是返回nul。
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
我们可以把一切属性包裹在Maybe内。这使我们可以运用composeK
来完成链式挪用。
// 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地compose
和chain
完成一样的结果)
// using compose and chain
const 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
还可以用map合营pipeK。谢谢Tom Harding供应pipeK的例子。
Lenses
末了,我们还可以运用Lenses
。Ramda
就带有lensProp
和lensPath
// lenses
const findUserComments =
R.lensPath(['user', 'posts', 0, 'comments'])
console.log(R.view(findUserComments, props))
// [ 'Good one!', 'Interesting...' ]
总结
我们应当对怎样检索嵌套数据的多种要领有了清晰的明白。除了晓得怎样本身完成外,还应当对Ramda供应的关于这个题目的功用有一个基础的相识。以至可以更好地i明白为何将结果包括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]