摘要:介紹這是一篇短文,旨在展示多種在中安全地訪問深層嵌套值的方式。所以每次我們想要訪問深度嵌套的數(shù)據(jù)時(shí),都必須明確地進(jìn)行手動(dòng)檢查。我們還觸及了,可以更新深度嵌套數(shù)據(jù)而不會(huì)改變對象。
介紹
這是一篇短文,旨在展示多種在javascript中安全地訪問深層嵌套值的方式。
下面的例子通過不同的方式來解決這一問題。
開始之前,讓我們看下實(shí)際遇到這種狀況時(shí)..
假設(shè)有一個(gè)props對象(如下),當(dāng)我們需要獲取user對象的posts的第一條的comments對象,通常會(huì)如何操作?
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
最直白的方式是確保每一個(gè)key或index的存在再去訪問它的下一級??紤]的多點(diǎn),當(dāng)需求變化需要去請求第一條comments時(shí),這個(gè)式子會(huì)變得越來越長。
// updating the previous example... props.user && props.user.posts && props.user.posts[0] && props.user.posts[0].comments && props.user.posts[0].comments[0]
所以每次我們想要訪問深度嵌套的數(shù)據(jù)時(shí),都必須明確地進(jìn)行手動(dòng)檢查?;蛟S很難說清這一點(diǎn),試想一下當(dāng)我們不希望檢驗(yàn)users對象下的posts,只是希望獲取到users下的最后一條comment,和前面的解決思路是相違背的。
這個(gè)例子可能有些夸張,但你懂我的意思,為了得到深層嵌套的值,我們需要檢驗(yàn)整個(gè)結(jié)構(gòu)的每一級(所有父級)。
所以,現(xiàn)在我們已經(jīng)更好地理解了實(shí)際想要解決的問題,讓我們來看看不同的解決方案。前面一些是通過javascript,再后面通過Ramda,再再后面是Ramda和Folktale。將通過一些比較有趣并且不算高級的例子來說明,希望大家在本次專題里有所收益。
JavaScript首先,我們不希望手動(dòng)檢驗(yàn)每一級是否為空或是未定義,我們希望有一種精簡且靈活的方式來應(yīng)對各種數(shù)據(jù)源。
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這個(gè)方法
const get = (p, o) => p.reduce((xs, x) => (xs && xs[x]) ? xs[x] : null, o)
我們傳入路徑(path)作為第一個(gè)參數(shù),需要獲取的對象(object)作為第二個(gè)參數(shù)。
思考一下這第二個(gè)參數(shù)o(object),你可能會(huì)問自己:我們期望這個(gè)方法有什么功能?應(yīng)該是一個(gè)輸入特定路徑并且針對任何對象都能返回是否存在預(yù)期對象的方法。
const get = p => o => p.reduce((xs, x) => (xs && xs[x]) ? xs[x] : null, o) const getUserComments = get(["user", "posts", 0, "comments"])
通過這種方式,我們可以調(diào)用getUserComments和之前的props對象或是任何其他對象。這也暗示我們必須得像這樣不停琢磨這個(gè)get函數(shù),
最終我們能打印出結(jié)果,驗(yàn)證下是否如預(yù)期得結(jié)果。
console.log(getUserComments(props)) // [ "Good one!", "Interesting..." ] console.log(getUserComments({user:{posts: []}})) // null
get函數(shù)實(shí)質(zhì)上就是在減少先前的路徑。
讓我們來簡化一下,現(xiàn)在我們只想訪問這個(gè)id。
["id"].reduce((xs, x) => (xs && xs[x]) ? xs[x] : null, {id: 10})
我們用提供的對象初始化reduce函數(shù),每一層通過(xs && xs[x]) 檢驗(yàn)對象是否被定義且有效, 然后依次遞歸或是返回null退出。
就像上面的例子一樣,我們可以輕巧地解決這一問題。當(dāng)然如果你偏向習(xí)慣用字符串路徑而不是數(shù)組來表達(dá)路徑,還需要對get函數(shù)做一些小改動(dòng),我將留給感興趣的讀者來實(shí)現(xiàn)。
我們也可以利用Ramda函數(shù)庫來實(shí)現(xiàn)相同的功能,而不是編寫自己的函數(shù)。
Ramda提供了一個(gè)path方法,兩個(gè)參數(shù)輸入, path以及object。讓我們用Ramda重寫這個(gè)例子。
const getUserComments = R.path(["user", "posts", 0, "comments"])
現(xiàn)在通過getUserComments傳入數(shù)據(jù)源就能得到我們希望的值,如果沒有找到就會(huì)得到null。
getUserComments(props) // [ "Good one!", "Interesting..." ] getUserComments({}) // null
但是如果我們想要返回的無效值不是null呢?Ramda提供了pathOr。pathOr需要傳入默認(rèn)值作為參數(shù)。
const getUserComments = R.pathOr([], ["user", "posts", 0, "comments"]) getUserComments(props) // [ "Good one!", "Interesting..." ] getUserComments({}) // []
感謝Gleb Bahmutov提供對于path和pathOr的見解。
Ramda + Folktale讓我們再加入Folktale的Maybe。例如我們可以構(gòu)建一個(gè)更通用的getPath函數(shù)(同樣傳入path和object)。
const getPath = R.compose(Maybe.fromNullable, R.path) const userComments = getPath(["user", "posts", 0, "comments"], props)
調(diào)用getPath會(huì)返回Maybe.Just或是Maybe.Nothing。
console.log(userComments) // Just([ "Good one!", "Interesting..." ])
將我們的返回結(jié)果包在Maybe中有什么用呢?通過采用這種方式,我們可以安全地使用userComments,無需手動(dòng)檢驗(yàn)userComments是否返回nul。
console.log(userComments.map(x => x.join(","))) // Just("Good one!,Interesting...")
沒有任何值時(shí)也是如此。
const userComments = getPath(["user", "posts", 8, "title"], props) console.log(userComments.map(x => x.join(",")).toString()) // Nothing
我們可以把所有屬性包裹在Maybe內(nèi)。這使我們能夠使用composeK來實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用。
// 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
這種方式是非常前衛(wèi)的,使用Ramda地path方法其實(shí)就足夠了。不過讓我簡單看下下面這個(gè)例子(通過Ramda地compose和chain實(shí)現(xiàn)同樣的效果)
// 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也能實(shí)現(xiàn)同樣的效果。
// 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..." ]總結(jié)
我們應(yīng)該對如何檢索嵌套數(shù)據(jù)的多種方法有了清楚的理解。除了知道如何自己實(shí)現(xiàn)外,還應(yīng)該對Ramda提供的關(guān)于這個(gè)問題的功能有一個(gè)基本的了解。甚至可以更好地i理解為什么將結(jié)果包含Either或Maybe中。我們還觸及了Lenses,可以更新深度嵌套數(shù)據(jù)而不會(huì)改變對象。
最后,你再也不會(huì)去編寫下面這樣的代碼了。
// updating the previous example... props.user && props.user.posts && props.user.posts[0] && props.user.posts[0].comments && props.user.posts[0].comments[0]
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/107638.html
摘要:比如的,的提供訪問安全對象的支持。在使用的情況下,這意味著表達(dá)式在達(dá)到其第一個(gè)假值后將停止向后執(zhí)行。這便可用于安全地訪問嵌套屬性。與上述短路示例類似,此方法通過檢查值是否為假來進(jìn)行操作。該方法優(yōu)于該方法的地方是避免屬性名稱的重復(fù)。 Uncaught TypeError: Cannot read property foo of undefined.這種錯(cuò)誤想必在我們?nèi)粘i_發(fā)中都到過,這有...
摘要:如果這個(gè)結(jié)構(gòu)非常復(fù)雜,那么想要安全優(yōu)雅地取出一個(gè)值,也并非簡單。這是為了在對象中相關(guān)取值的過程,需要驗(yàn)證每一個(gè)和的存在性。并且這個(gè)數(shù)據(jù)結(jié)構(gòu)必然是動(dòng)態(tài)生成的,存在有時(shí)有時(shí)的情況。在測試過程中,很難復(fù)現(xiàn)。 古有趙子龍面對沖鋒之勢,有進(jìn)無退,陷陣之志,有死無生的局面,能萬軍叢中取敵將首級。在我們的Javascript中,往往用對象(Object)來存儲一個(gè)數(shù)據(jù)結(jié)構(gòu)。如果這個(gè)結(jié)構(gòu)非常復(fù)雜,那么...
摘要:如果這個(gè)結(jié)構(gòu)非常復(fù)雜,那么想要安全優(yōu)雅地取出一個(gè)值,也并非簡單。這是為了在對象中相關(guān)取值的過程,需要驗(yàn)證每一個(gè)和的存在性。并且這個(gè)數(shù)據(jù)結(jié)構(gòu)必然是動(dòng)態(tài)生成的,存在有時(shí)有時(shí)的情況。在測試過程中,很難復(fù)現(xiàn)。 古有趙子龍面對沖鋒之勢,有進(jìn)無退,陷陣之志,有死無生的局面,能萬軍叢中取敵將首級。在我們的Javascript中,往往用對象(Object)來存儲一個(gè)數(shù)據(jù)結(jié)構(gòu)。如果這個(gè)結(jié)構(gòu)非常復(fù)雜,那么...
摘要:與持久化工程師花了年時(shí)間打造,與同期出現(xiàn)。有持久化數(shù)據(jù)結(jié)構(gòu),如等,并發(fā)安全??偨Y(jié)篇幅有限,時(shí)間也比較晚了,關(guān)于前端數(shù)據(jù)的扁平化與持久化處理先講這么多了,有興趣的同學(xué)可以關(guān)注下,后面有時(shí)間會(huì)多整理分享。 (PS: 時(shí)間就像海綿里的水,擠到?jīng)]法擠,只能擠擠睡眠時(shí)間了~ 知識點(diǎn)還是需要整理的,付出總會(huì)有收獲,tired but fulfilled~) 前言 最近業(yè)務(wù)開發(fā),從零搭建網(wǎng)頁生成器,...
閱讀 2694·2021-10-22 09:55
閱讀 2026·2021-09-27 13:35
閱讀 1280·2021-08-24 10:02
閱讀 1507·2019-08-30 15:55
閱讀 1209·2019-08-30 14:13
閱讀 3484·2019-08-30 13:57
閱讀 1983·2019-08-30 11:07
閱讀 2462·2019-08-29 17:12