古有趙子龍面對“沖鋒之勢,有進無退听隐,陷陣之志补鼻,有死無生”的局面,能萬軍叢中取敵將首級。
在我們的Javascript中风范,往往用對象(Object)來存儲一個數(shù)據(jù)結(jié)構(gòu)咨跌。如果這個結(jié)構(gòu)非常復雜,那么想要安全優(yōu)雅地取出一個值硼婿,也并非簡單锌半。
這篇文章將會詳細闡述在一個嵌套較深的場景中,如何安全的完成讀寫操作寇漫。先后會嘗試多種方法刊殉,希望對讀者有所啟發(fā)。
本文示例借鑒A.Sharif的最新文章:Safely Accessing Deeply Nested Values In JavaScript州胳,喜歡看英文原版的同學可以直接戳鏈接记焊。
場景介紹
在React開發(fā)中,我們根據(jù)數(shù)據(jù)來渲染視圖栓撞。經(jīng)常會出現(xiàn)類似下面這種情況:
const props = {
user: {
posts: [
{ title: 'Foo', comments: [ 'Good one!', 'Interesting...' ] },
{ title: 'Bar', comments: [ 'Ok' ] },
{ title: 'Baz', comments: []}
],
comments: [...]
}
}
這是一個典型的獲取用戶評論信息并加以展示的場景遍膜。其實,這還嵌套的不夠深腐缤,試想一個回復存在多層:回復的回復捌归,回復的回復的回復。岭粤。惜索。
姑且先看我們的示例吧,此時我們想獲取第一個post的評論信息剃浇。用傳統(tǒng)的javascript方法應該這么做:
props.user &&
props.user.posts &&
props.user.posts[0] &&
props.user.posts[0].comments
也許經(jīng)驗豐富的javascript開發(fā)者會明白使用這么多&&的意義从撼。這是為了在對象中相關(guān)取值的過程租副,需要驗證每一個key和index的存在性。否則會有報錯,這將會是致命性的蝎土。并且props這個數(shù)據(jù)結(jié)構(gòu)必然是動態(tài)生成的祝拯,存在有時valid有時invalid的情況士袄。在測試過程中泻拦,很難復現(xiàn)。
同樣的尷尬場景比比皆是蒲列,想象一下窒朋,如果我們需要獲取一名用戶最后一個評論博客的題目,就需要:
props.user &&
props.user.comments &&
props.user.comments[0] &&
props.user.comments[0].blog.title
這些例子夸張嗎蝗岖?其實不然侥猩。我們明白了,想要獲取一個數(shù)據(jù)值抵赢,需要一層一層遍歷屬性的存在性欺劳。這無疑是繁瑣的唧取。
解決方案
現(xiàn)在明白了我們面臨的困擾,接下來我會用幾種方法:
- 純JavaScript方法;
- 最具有函數(shù)式代表的JavaScript庫-Ramda划提,輔以柯练愕埽化(currying)等思想和方案解決問題。
JavaScript方案
先直接上代碼:
const get = (p, o) => p.reduce((xs, x) => (xs && xs[x]) ? xs[x] : null, o)
console.log(get(['user', 'posts', 0, 'comments'], props)) // [ 'Good one!', 'Interesting...' ]
console.log(get(['user', 'post', 0, 'comments'], props)) // null
注意這里我使用了一個ES5中腔剂,比較偏向函數(shù)式思想的reduce方法媒区。關(guān)于這個方法驼仪,我想很多人其實還并不理解掸犬,建議先去進行學習,或者參考我之前的一篇文章绪爸。
同時湾碎,我嘗試獲取:user->posts[0]->comments奠货,
并配以一個反例:user->post[0]->comments介褥;
當然,在反例中递惋,post數(shù)組并不存在柔滔。
我們來分析一下代碼。
const get = (p, o) =>
p.reduce((xs, x) =>
(xs && xs[x]) ? xs[x] : null, o)
我們實現(xiàn)的get方法中萍虽,接收兩個參數(shù)睛廊,第一個p表示獲取值的路徑(path);另外一個參數(shù)表示目標對象杉编。
同樣超全,為了設(shè)計上的更加靈活和抽象。我們可以柯恋寺化我們的方法:
const get = p => o =>
p.reduce((xs, x) =>
(xs && xs[x]) ? xs[x] : null, o)
這樣的話嘶朱,就可以這個姿勢調(diào)用:
const getUserComments = get(['user', 'posts', 0, 'comments'])
console.log(getUserComments(props))
// [ 'Good one!', 'Interesting...' ]
console.log(getUserComments({user:{posts: []}}))
// null
如果關(guān)于get方法中reduce的使用還不清楚,那就再看一個簡單的例子:
['id'].reduce((xs, x) => (xs && xs[x]) ? xs[x] : null, {id: 10})
// 返回10
Ramda方案
如果不自己手動設(shè)計上述方法的話光酣,我們可以使用Ramda函數(shù)式類庫完成:
const getUserComments = R.path(['user', 'posts', 0, 'comments'])
接下來調(diào)用需要這個姿勢:
getUserComments(props) // [ 'Good one!', 'Interesting...' ]
getUserComments({}) // null
如果我們想在指定路徑下未找到一個值時疏遏,不返回null,而是返回自定義的內(nèi)容呢救军?我們可以使用pathOr方法财异,第一個參數(shù)用來設(shè)置默認輸出。
const getUserComments = R.pathOr([], ['user', 'posts', 0, 'comments'])
getUserComments(props) // [ 'Good one!', 'Interesting...' ]
getUserComments({}) // []
總結(jié)
這篇文章翻譯自A.Sharif的最新文章:Safely Accessing Deeply Nested Values In JavaScript缤言,其中后半部分未做翻譯宝当。
后半部分其實分析了 Ramda+Folktale的實現(xiàn),以及Ramda+Lenses的實現(xiàn)胆萧。
Folktale和Lenses是非常函數(shù)式Functional Programming的思想庆揩,理解起來相對晦澀且比較小眾俐东。有興趣的讀者可以點擊原文去自行了解。
Happy Coding!
如果你對函數(shù)式編程并不感冒订晌,大可只學習第一部分的實現(xiàn)虏辫。對于函數(shù)式編程有興趣的同學,希望這篇文章能夠拋磚引玉锈拨,歡迎與我交流砌庄。
PS: 作者Github倉庫,歡迎通過代碼各種形式交流奕枢。