摘要:右側展現對應產品。我們使用命名為的對象表示過濾條件信息,如下此數據需要在組件中進行維護。因為組件的子組件和都將依賴這項數據狀態。化應用再回到之前的場景,我們設計化函數,進一步可以簡化為對于的偏應用即上面提到的相信大家已經理解了這么做的好處。
使用 React 開發應用,給予了前端工程師無限“組合拼裝”快感。但在此基礎上,組件如何劃分,數據如何流轉等應用設計都決定了代碼層面的美感和強健性。
同時,在 React 世界里提到 curry 化,也許很多開發者會第一時間反應出 React-redux 庫的 connect 方法。然而,如果僅僅機械化地停留于此,而沒有更多靈活地應用,是非常可惜的。
這篇文章以一個真實場景為基礎,從細節出發,分析 curry 化如何化簡為繁,更優雅地實現需求。
場景介紹需求場景為一個賣食品的電商網站,左側部分為商品篩選欄目,用戶可以根據:價格區間、商品年限、商品品牌進行過濾。右側展現對應產品。如下圖:
作為 React 開發者,我們知道 React 是組件化的,第一步將考慮根據 UE 圖,進行組件拆分。這個過程比較簡單直觀,我們對拆分結果用下圖表示:
對應代碼為:
初級實現
React 是基于數據狀態的,緊接著第二步就要考慮應用狀態。商品展現結果數據我們暫時不需要關心。這里主要考慮應用最重要的狀態,即過濾條件信息。
我們使用命名為 filterSelections 的 JavaScript 對象表示過濾條件信息,如下:
filterSelections = { price: ..., ages: ..., brands: ..., }
此數據需要在 Products 組件中進行維護。因為 Products 組件的子組件 Filters 和 ProductResults 都將依賴這項數據狀態。
Filters 組件通過 prop 接收 filterSelections 狀態,并拆解傳遞給它的三項篩選子組件:
class Filters extends React.Component { render() { return (); }; }
同樣地,ProductResults 組件也通過 prop 接收 filterSelections 狀態,進行相應產品的展示。
對于 Filters 組件,它一定不僅僅是接收 filterSelections 數據而已,同樣也需要對此項數據進行更新。為此,我們在 Products 組件中設計相應的 handler 函數,對過濾信息進行更新,命名為 updateFilters,并將此處理函數作為 prop 下發給 Filters 組件:
class Products extends React.Component { constructor(props) { super(props); this.state = { filterSelections: { price: someInitialValue, ages: someInitialValue, brands: someInitialValue, } } } updateFilters = (newSelections) => { this.setState({ filterSelections: newSelections }) }; render() { return(); } }
注意這里我們對 this 綁定方式。有興趣的讀者可以參考我的另一篇文章:從 React 綁定 this,看 JS 語言發展和框架設計。
作為 Filters 組件,同樣也要對處理函數進行進一步拆分和分發:
class Filters extends React.Component { updatePriceFilter = (newValue) => { this.props.selectionsChanged({ ...this.props.filterSelections, price: newValue }) }; updateAgeFilter = (newValue) => { this.props.selectionsChanged({ ...this.props.filterSelections, ages: newValue }) }; updateBrandFilter = (newValue) => { this.props.selectionsChanged({ ...this.props.filterSelections, brands: newValue }) }; render() { return (); }; }
我們根據 selectionsChanged 函數,通過傳遞不同類型參數,設計出 updatePriceFilter、updateAgeFilter、updateBrandFilter 三個方法,分別傳遞給 PriceFilter、AgeFilter、BrandFilter 三個組件。
這樣的做法非常直接,然而運行良好。但是在 Filters 組件中,多了很多函數,且這些函數看上去做著相同的邏輯。如果將來又多出了一個或多個過濾條件,那么同樣也要多出同等數量的“雙胞胎”函數。這顯然不夠優雅。
currying 是什么在分析更加優雅的解決方案之前,我們先簡要了解一下 curry 化是什么。curry 化事實上是一種變形,它將一個函數 f 變形為 f",f" 的參數接收原本函數 f 的參數,同時返回一個新的函數 f"",f"" 接收剩余的參數并返回函數 f 的計算結果。
這么描述無疑是抽象的,我們還是通過代碼來理解。這是一個簡單的求和函數:
add = (x, y) => x + y;
curried 之后:
curriedAdd = (x) => { return (y) => { return x + y; } }
所以,當執行 curriedAdd(1)(2) 之后,得到結果 3,curriedAdd(x) 函數有一個名字叫 partial application,curriedAdd 函數只需要原本 add(X, y) 函數的一部分參數。
Currying a regular function let’s us perform partial application on it.curry 化應用
再回到之前的場景,我們設計 curry 化函數:updateSelections,
updateSelections = (selectionType) => { return (newValue) => { this.props.selectionsChanged({ ...this.props.filterSelections, [selectionType]: newValue, }); } };
進一步可以簡化為:
updateSelections = (selectionType) => (newValue) => { this.props.selectionsChanged({ ...this.props.filterSelections, [selectionType]: newValue, }) };
對于 updateSelections 的偏應用(即上面提到的 partial application):
updateSelections("ages"); updateSelections("brands"); updateSelections("price");
相信大家已經理解了這么做的好處。這樣一來,我們的 Filters 組件完整為:
class Filters extends React.Component { updateSelections = (selectionType) => { return (newValue) => { this.props.selectionsChanged({ ...this.props.selections, [selectionType]: newValue, // new ES6 Syntax!! :) }); } }; render() { return (); }; }
當然,currying 并不是解決上述問題的唯一方案。我們再來了解一種方法,進行對比消化,updateSelections 函數 uncurried 版本:
updateSelections = (selectionType, newValue) => { this.props.updateFilters({ ...this.props.filterSelections, [selectionType]: newValue, }); }
這樣的設計使得每一個 Filter 組件:PriceFilter、AgeFilter、BrandFilter 都要調用 updateSelections 函數本身,并且要求組件本身感知 filterSelections 的屬性名,以進行相應屬性的更新。這就是一種耦合,完整實現:
class Filters extends React.Component { updateSelections = (selectionType, newValue) => { this.props.selectionsChanged({ ...this.props.filterSelections, [selectionType]: newValue, }); }; render() { return ( <>this.updateSelections("price", value)} /> this.updateSelections("ages", value)} /> this.updateSelections("brands", value)} /> > ); }; }
其實我認為,在這種場景下,關于兩種方案的選擇,可以根據開發者的偏好來決定。
總結這篇文章內容較為基礎,但從細節入手,展現了 React 開發編寫和函數式理念相結合的魅力。文章譯自這里,部分內容有所改動。
廣告時間:
如果你對前端發展,尤其對 React 技術棧感興趣:我的新書中,也許有你想看到的內容。關注作者 Lucas HC,新書出版將會有送書活動。
Happy Coding!
PS: 作者?Github倉庫?和?知乎問答鏈接?歡迎各種形式交流!
我的其他幾篇關于React技術棧的文章:
從setState promise化的探討 體會React團隊設計思想
從setState promise化的探討 體會React團隊設計思想
通過實例,學習編寫 React 組件的“最佳實踐”
React 組件設計和分解思考
從 React 綁定 this,看 JS 語言發展和框架設計
做出Uber移動網頁版還不夠 極致性能打造才見真章**
React+Redux打造“NEWS EARLY”單頁應用 一個項目理解最前沿技術棧真諦**
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/94377.html
摘要:右側展現對應產品。我們使用命名為的對象表示過濾條件信息,如下此數據需要在組件中進行維護。因為組件的子組件和都將依賴這項數據狀態。化應用再回到之前的場景,我們設計化函數,進一步可以簡化為對于的偏應用即上面提到的相信大家已經理解了這么做的好處。 showImg(https://segmentfault.com/img/remote/1460000014458612?w=1240&h=663...
摘要:這一周連續發表了兩篇關于的文章組件復用那些事兒實現按需加載輪子應用設計之道化妙用其中涉及到組件復用輪子設計相關話題,并配合相關場景實例進行了分析。 showImg(https://segmentfault.com/img/remote/1460000014482098); 這一周連續發表了兩篇關于 React 的文章: 組件復用那些事兒 - React 實現按需加載輪子 React ...
摘要:這一周連續發表了兩篇關于的文章組件復用那些事兒實現按需加載輪子應用設計之道化妙用其中涉及到組件復用輪子設計相關話題,并配合相關場景實例進行了分析。 showImg(https://segmentfault.com/img/remote/1460000014482098); 這一周連續發表了兩篇關于 React 的文章: 組件復用那些事兒 - React 實現按需加載輪子 React ...
閱讀 489·2021-09-03 00:22
閱讀 1365·2021-08-03 14:03
閱讀 2082·2021-07-25 21:37
閱讀 646·2019-08-30 13:18
閱讀 1875·2019-08-29 16:19
閱讀 2682·2019-08-29 13:22
閱讀 1293·2019-08-29 12:16
閱讀 2587·2019-08-26 12:16