如何優雅安全地在深層數據結構中取值

古有趙子龍面對「衝鋒之勢,有進無退,陷陣之志,有死無生」的局面,能萬軍叢中取敵將首級。
在咱們的Javascript中,每每用對象(Object)來存儲一個數據結構。若是這個結構很是複雜,那麼想要安全優雅地取出一個值,也並不是簡單。javascript

這篇文章將會詳細闡述在一個嵌套較深的場景中,如何安全的完成讀寫操做。前後會嘗試多種方法,但願對讀者有所啓發。java

本文示例借鑑A.Sharif的最新文章:Safely Accessing Deeply Nested Values In JavaScript,喜歡看英文原版的同窗能夠直接戳連接。git

場景介紹

在React開發中,咱們根據數據來渲染視圖。常常會出現相似下面這種狀況:github

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

這是一個典型的獲取用戶評論信息並加以展現的場景。其實,這還嵌套的不夠深,試想一個回覆存在多層:回覆的回覆,回覆的回覆的回覆。。。編程

姑且先看咱們的示例吧,此時咱們想獲取第一個post的評論信息。用傳統的javascript方法應該這麼作:數組

props.user &&
props.user.posts &&
props.user.posts[0] &&
props.user.posts[0].comments複製代碼

也許經驗豐富的javascript開發者會明白使用這麼多&&的意義。這是爲了在對象中相關取值的過程,須要驗證每個key和index的存在性。不然會有報錯,這將會是致命性的。而且props這個數據結構必然是動態生成的,存在有時valid有時invalid的狀況。在測試過程當中,很難復現。安全

一樣的尷尬場景比比皆是,想象一下,若是咱們須要獲取一名用戶最後一個評論博客的題目,就須要:數據結構

props.user &&
props.user.comments &&
props.user.comments[0] &&
props.user.comments[0].blog.title複製代碼

這些例子誇張嗎?其實否則。咱們明白了,想要獲取一個數據值,須要一層一層遍歷屬性的存在性。這無疑是繁瑣的。app

解決方案

如今明白了咱們面臨的困擾,接下來我會用幾種方法:ide

  • 純JavaScript方法;
  • 最具備函數式表明的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中,比較偏向函數式思想的reduce方法。關於這個方法,我想不少人其實還並不理解,建議先去進行學習,或者參考我以前的一篇文章。

同時,我嘗試獲取:user->posts[0]->comments,
並配以一個反例:user->post[0]->comments;
固然,在反例中,post數組並不存在。

咱們來分析一下代碼。

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

咱們實現的get方法中,接收兩個參數,第一個p表示獲取值的路徑(path);另一個參數表示目標對象。

一樣,爲了設計上的更加靈活和抽象。咱們能夠柯粒化咱們的方法:

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

這樣的話,就能夠這個姿式調用:

const getUserComments = get(['user', 'posts', 0, 'comments'])
console.log(getUserComments(props))
// [ 'Good one!', 'Interesting...' ]
console.log(getUserComments({user:{posts: []}}))
// null複製代碼

若是關於get方法中reduce的使用還不清楚,那就再看一個簡單的例子:

['id'].reduce((xs, x) => (xs && xs[x]) ? xs[x] : null, {id: 10})
// 返回10複製代碼

Ramda方案

若是不本身手動設計上述方法的話,咱們可使用Ramda函數式類庫完成:

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

接下來調用須要這個姿式:

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

若是咱們想在指定路徑下未找到一個值時,不返回null,而是返回自定義的內容呢?咱們可使用pathOr方法,第一個參數用來設置默認輸出。

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

總結

這篇文章翻譯自A.Sharif的最新文章:Safely Accessing Deeply Nested Values In JavaScript,其中後半部分未作翻譯。
後半部分其實分析了 Ramda+Folktale的實現,以及Ramda+Lenses的實現。

Folktale和Lenses是很是函數式Functional Programming的思想,理解起來相對晦澀且比較小衆。有興趣的讀者能夠點擊原文去自行了解。

Happy Coding!

若是你對函數式編程並不感冒,大可只學習第一部分的實現。對於函數式編程有興趣的同窗,但願這篇文章可以拋磚引玉,歡迎與我交流。

PS: 做者Github倉庫,歡迎經過代碼各類形式交流。

相關文章
相關標籤/搜索