摘要:我們可以在組件的設計上,玩轉出很多花樣。但是,如何對一個功能復雜且臃腫的組件進行分解,也許并不是一件簡單的事情。同時,借助于新的算法引擎,兩個單元組件在渲染的效率上,樂觀地預計會有較大幅度的提升。
之前分享過幾篇關于React技術棧的文章:
做出Uber移動網頁版還不夠 極致性能打造才見真章
解析Twitter前端架構 學習復雜場景數據設計
React Conf 2017 干貨總結1: React + ES next = ?
React+Redux打造“NEWS EARLY”單頁應用 一個項目理解最前沿技術棧真諦
一個react+redux工程實例
......
今天再來同大家討論 React 組件設計的一個有趣話題:分解 React 組件的幾種進階方法。
React 組件魔力無窮,同時靈活性超強。我們可以在組件的設計上,玩轉出很多花樣。但是保證組件的Single responsibility principle: 單一原則非常重要,它可以使得我們的組件更簡單、更方便維護,更重要的是使得組件更加具有復用性。
但是,如何對一個功能復雜且臃腫的 React 組件進行分解,也許并不是一件簡單的事情。本文由淺入深,介紹三個分解 React 組件的方法。
切割 render() 方法這是一個最容易想到的方法:當一個組件渲染了很多元素時,就需要嘗試分離這些元素的渲染邏輯。最迅速的方式就是切割 render() 方法為多個 sub-render 方法。
看下面的例子會更加直觀:
class Panel extends React.Component { renderHeading() { // ... } renderBody() { // ... } render() { return ({this.renderHeading()} {this.renderBody()}); }
細心的讀者很快就能發現,其實這并沒有分解組件本身,該 Panel 組件仍然保持有原先的 state, props, 以及 class methods。
如何真正地做到減少復雜度呢?我們需要創建一些子組件。此時,采用最新版 React 支持并推薦的函數式組件/無狀態組件一定會是一個很好的嘗試:
const PanelHeader = (props) => ( // ... ); const PanelBody = (props) => ( // ... ); class Panel extends React.Component { render() { return (// Nice and explicit about which props are used); } }
同之前的方式相比,這個微妙的改進是革命性的。我們新建了兩個單元組件:PanelHeader 和 PanelBody。這樣帶來了測試的便利,我們可以直接分離測試不同的組件。同時,借助于 React 新的算法引擎 React Fiber,兩個單元組件在渲染的效率上,樂觀地預計會有較大幅度的提升。
模版化組件回到問題的起點,為什么一個組件會變的臃腫而復雜呢?其一是渲染元素較多且嵌套,另外就是組件內部變化較多,或者存在多種 configurations 的情況。
此時,我們便可以將組件改造為模版:父組件類似一個模版,只專注于各種 configurations。
還是要舉例來說,這樣理解起來更加清晰。
比如我們有一個 Comment 組件,這個組件存在多種行為或事件。同時組件所展現的信息根據用戶的身份不同而有所變化:用戶是否是此 comment 的作者,此 comment 是否被正確保存,各種權限不同等等都會引起這個組件的不同展示行為。這時候,與其把所有的邏輯混淆在一起,也許更好的做法是利用 React 可以傳遞 React element 的特性,我們將 React element 進行組件間傳遞,這樣就更加像一個模版:
class CommentTemplate extends React.Component { static propTypes = { // Declare slots as type node metadata: PropTypes.node, actions: PropTypes.node, }; render() { return (...// Slot for metadata {this.props.metadata} // Slot for actions {this.props.actions}
此時,我們真正的 Comment 組件組織為:
class Comment extends React.Component { render() { const metadata = this.props.publishTime ?: Saving...; const actions = []; if (this.props.isSignedIn) { actions.push( ); actions.push( ); } if (this.props.isAuthor) { actions.push( ); } return ; }
metadata 和 actions 其實就是在特定情況下需要渲染的 React element。
比如,如果 this.props.publishTime 存在,metadata 就是
如果用戶已經登陸,則需要渲染(即actions值為)
在實際開發當中,組件經常會被其他需求所污染。
比如,我們想統計頁面中所有鏈接的點擊信息。在鏈接點擊時,發送統計請求,同時包含此頁面 document 的 id 值。常見的做法是在 Document 組件的生命周期函數 componentDidMount 和 componentWillUnmount 增加代碼邏輯:
class Document extends React.Component { componentDidMount() { ReactDOM.findDOMNode(this).addEventListener("click", this.onClick); } componentWillUnmount() { ReactDOM.findDOMNode(this).removeEventListener("click", this.onClick); } onClick = (e) => { if (e.target.tagName === "A") { // Naive check for elements sendAnalytics("link clicked", { documentId: this.props.documentId // Specific information to be sent }); } }; render() { // ...
這么做的幾個問題在于:
相關組件 Document 除了自身的主要邏輯:顯示主頁面之外,多了其他統計邏輯;
如果 Document 組件的生命周期函數中,還存在其他邏輯,那么這個組件就會變的更加含糊不合理;
統計邏輯代碼無法復用;
組件重構、維護都會變的更加困難。
為了解決這個問題,我們提出了高階組件這個概念: higher-order components (HOCs)。不去晦澀地解釋這個名詞,我們來直接看看使用高階組件如何來重構上面的代碼:
function withLinkAnalytics(mapPropsToData, WrappedComponent) { class LinkAnalyticsWrapper extends React.Component { componentDidMount() { ReactDOM.findDOMNode(this).addEventListener("click", this.onClick); } componentWillUnmount() { ReactDOM.findDOMNode(this).removeEventListener("click", this.onClick); } onClick = (e) => { if (e.target.tagName === "A") { // Naive check for elements const data = mapPropsToData ? mapPropsToData(this.props) : {}; sendAnalytics("link clicked", data); } }; render() { // Simply render the WrappedComponent with all props return; } }
需要注意的是,withLinkAnalytics 函數并不會去改變 WrappedComponent 組件本身,更不會去改變 WrappedComponent 組件的行為。而是返回了一個被包裹的新組件。實際用法為:
class Document extends React.Component { render() { // ... } } export default withLinkAnalytics((props) => ({ documentId: props.documentId }), Document);
這樣一來,Document 組件仍然只需關心自己該關心的部分,而 withLinkAnalytics 賦予了復用統計邏輯的能力。
高階組件的存在,完美展示了 React 天生的復合(compositional)能力,在 React 社區當中,react-redux,styled-components,react-intl 等都普遍采用了這個方式。值得一提的是,recompose 類庫又利用高階組件,并發揚光大,做到了“腦洞大開”的事情。
總結React 及其周邊社區的崛起,讓函數式編程風靡一時,受到追捧。其中關于 decomposing 和 composing 的思想,我認為非常值得學習。同時,對開發設計的一個建議是,不要猶豫將你的組件拆分的更小、更單一,因為這樣能換來強健和復用。
本文意譯了David Tang的:Techniques for decomposing React components一文。
Happy Coding!
PS: 作者Github倉庫,歡迎通過代碼各種形式交流。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/83696.html
摘要:搜索文本和多選框因為會發生變化,且不能通過計算得出,所以是狀態。最后,過濾過的產品列表,可以通過原始產品列表搜索文本和多選框值計算出來,因此它不是狀態。從傳入的回調函數會調用,從而更新組件。 在使用JavaScript開發大型、快速的網頁應用時,React是我們的首選。在Facebook和Instagram,React很好地減少了我們的工作量。React最強大部分之一,是讓你在開發應用...
摘要:這一周連續發表了兩篇關于的文章組件復用那些事兒實現按需加載輪子應用設計之道化妙用其中涉及到組件復用輪子設計相關話題,并配合相關場景實例進行了分析。 showImg(https://segmentfault.com/img/remote/1460000014482098); 這一周連續發表了兩篇關于 React 的文章: 組件復用那些事兒 - React 實現按需加載輪子 React ...
摘要:這一周連續發表了兩篇關于的文章組件復用那些事兒實現按需加載輪子應用設計之道化妙用其中涉及到組件復用輪子設計相關話題,并配合相關場景實例進行了分析。 showImg(https://segmentfault.com/img/remote/1460000014482098); 這一周連續發表了兩篇關于 React 的文章: 組件復用那些事兒 - React 實現按需加載輪子 React ...
閱讀 2169·2023-04-25 15:00
閱讀 2343·2021-11-18 13:14
閱讀 1154·2021-11-15 11:37
閱讀 3083·2021-09-24 13:55
閱讀 1221·2019-08-30 15:52
閱讀 2644·2019-08-29 12:35
閱讀 3359·2019-08-29 11:04
閱讀 1209·2019-08-26 12:13