首頁 > 軟體

React高階元件使用教學詳解

2022-12-03 14:00:08

高階元件(HOC)

概述

是React複用元件邏輯的一種高階技巧,是一種基於React組合特性而形成的設計模式

高階元件是引數為元件,返回值為新元件的函數

簡單理解:

  • 高階元件本身是 函數,傳引數是元件,返回值也是元件;
  • 高階元件不用關心資料是如何渲染的,只用關心邏輯即可
  • 被包裝的元件本身不用關心資料是怎麼來的,只用負責渲染即可
  • 最後渲染的是高階元件返回的元件

高階元件的呼叫過程類似於這樣:

const EnhancedComponent = higherOrderComponent(WrappedComponent);

應用場景:redux 中的 connect

具體怎麼編寫呢?往下看…

使用HOC解決橫切關注點問題

橫切關注點問題:指的是一些具有橫越多個模組的行為,使用傳統的軟體開發方法不能夠達到有效的模組化的一類特殊關注點。

元件是React 中程式碼複用的基本單元,但某些模式並不適合傳統元件

假設有一個 CommentList 元件,訂閱外部資料來源,用於渲染評論列表:

class CommentList extends React.Component {
   constructor(props) {
     super(props);
     this.handleChange = this.handleChange.bind(this);
     this.state = {
       // 假設 "DataSource" 是個全域性範圍內的資料來源變數,來自外部,自身帶有很多方法
       comments: DataSource.getComments()  //假設getComments()這個方法可以獲取所有的評論
     };
   }
   componentDidMount() {
     // 訂閱更改;監聽  DataSource ,發生變化時更新資料
     DataSource.addChangeListener(this.handleChange);
   }
   componentWillUnmount() {
     // 清除訂閱
     DataSource.removeChangeListener(this.handleChange);
   }
   handleChange() {
     // 當資料來源更新時,更新元件狀態
     this.setState({
       comments: DataSource.getComments()  //假設getComments()這個方法可以獲取所有的評論
     });
   }
   render() {
     return (
       <div>
         {this.state.comments.map((comment) => (
           <Comment comment={comment} key={comment.id} />
         ))}
       </div>
     );
   }
 }
 // 假設 DataSource:來自外部;它自身有很多方法,如:getComments(),addChangeListener,removeChangeListener 等
//  假設 <Comment /> 是子元件,父元件 CommentList 需要將 comment 、key 傳遞給它

假設有個 訂閱單個部落格貼文的元件BlogPost,與上面的模式類似:

class BlogPost extends React.Component {
 constructor(props) {
   super(props);
   this.handleChange = this.handleChange.bind(this);
   this.state = {
     blogPost: DataSource.getBlogPost(props.id)
   };
 }
 componentDidMount() {
   DataSource.addChangeListener(this.handleChange);
 }
 componentWillUnmount() {
   DataSource.removeChangeListener(this.handleChange);
 }
 handleChange() {
   this.setState({
     blogPost: DataSource.getBlogPost(this.props.id)
   });
 }
 render() {
   return <TextBlock text={this.state.blogPost} />;
 }
}

以上兩個元件的不同點

  • 呼叫方法不用

以上兩個元件的相同點

  • 在掛載時,向 DataSource 新增一個更改偵 聽器在偵 聽器
  • 內部,當資料來源發生變化時,呼叫 setState
  • 在解除安裝時,刪除偵 聽器

上面兩個元件相同點的地方被不斷的重複呼叫,在大型專案中,所以我們需要將這些共同使用的地方給抽象出來,然後讓許多元件之間共用它,這正是高階元件擅長的地方。

編寫一個建立元件函數,這個函數接收兩個引數,一個是要被包裝的子元件,另一個則是該子元件訂閱資料的函數。

 const CommentListWithSubscription = withSubscription(
    CommentList,
    (DataSource) => DataSource.getComments()
  );
  const BlogPostWithSubscription = withSubscription(
    BlogPost,
    (DataSource, props) => DataSource.getBlogPost(props.id)
  );
//以上寫法相當於高階元件的呼叫,withSubscription為自定義的高階元件;CommentList:被包裝的子元件;CommentListWithSubscription:返回的包裝後的元件

當渲染 CommentListWithSubscription 和 BlogPostWithSubscription 時, CommentList 和 BlogPost 將傳遞一個 data prop,其中包含從 DataSource 檢索到的最新資料

 // 此函數接收一個元件...
function withSubscription(WrappedComponent, selectData) {
 // ...並返回另一個元件...
 return class extends React.Component {
   constructor(props) {
     super(props);
     this.handleChange = this.handleChange.bind(this);
     this.state = {
       data: selectData(DataSource, props)
     };
   }
   componentDidMount() {
     // ...負責訂閱相關的操作...
     DataSource.addChangeListener(this.handleChange);
   }
   componentWillUnmount() {
     DataSource.removeChangeListener(this.handleChange);
   }
   handleChange() {
     this.setState({
       data: selectData(DataSource, this.props)
     });
   }
   render() {
     // ... 並使用新資料渲染被包裝的元件!
     // 請注意,我們可能還會傳遞其他屬性
     return <WrappedComponent data={this.state.data} {...this.props} />;
   }
 };
}

HOC不會修改傳入的元件,也不會使用繼承來複制其行為,相反HOC是通過將元件包裝在容器元件中來組成新的元件,HOC是純函數,沒有副作用

  • 被包裝元件接收來自容器元件的所有prop,同時也接收一個新的用於render的data prop
  • HOC不用關心資料的使用方式,被包裝元件也不用關心資料是怎麼來的

不用改變原始元件使用組合

不要試圖在 HOC 中修改元件原型(或以其他方式改變它)

function logProps(InputComponent) {
 InputComponent.prototype.componentDidUpdate = function(prevProps) {
   console.log('Current props: ', this.props);
   console.log('Previous props: ', prevProps);
 };
 // 返回原始的 input 元件,暗示它已經被修改。
 return InputComponent;
}
// 每次呼叫 logProps 時,增強元件都會有 log 輸出。
const EnhancedComponent = logProps(InputComponent)
//上面這種寫法會造成另一個同樣會修改componentDidUpate的HOC增強它,那麼前面的HOC就會失效。

HOC不應該修改傳入元件,而應該使用組合的方式,將元件包裝在容器元件中實現功能。

function logProps(WrappedComponent) {
    return class extends React.Component {
      componentDidUpdate(prevProps) {
        console.log('Current props: ', this.props);
        console.log('Previous props: ', prevProps);
      }
      render() {
        // 將 input 元件包裝在容器中,而不對其進行修改。Good!
        return <WrappedComponent {...this.props} />;
      }
    }
  }

約定-將不相關的 props 傳遞給被包裹的元件

HOC為元件新增特性,自身不應該大幅改變約定,HOC應該透傳與自身無關的props,大多數HOC都應該包含一個類似於下面的render方法

render() {
  // 過濾掉非此 HOC 額外的 props,且不要進行透傳
  const { extraProp, ...passThroughProps } = this.props;
  // 將 props 注入到被包裝的元件中。
  // 通常為 state 的值或者實體方法。
  const injectedProp = someStateOrInstanceMethod;
  // 將 props 傳遞給被包裝元件
  return (
    <WrappedComponent
      injectedProp={injectedProp}
      {...passThroughProps}
    />
  );
}	

約定-最大化可組合性

有時候它僅接受一個引數,也就是被包裹的元件:

const NavbarWithRouter = withRouter(Navbar);

HOC通常也可以接收多個引數

const CommentWithRelay = Relay.createContainer(Comment, config);

常見的HOC簽名(React Redux的connect函數):

// React Redux 的 `connect` 函數const ConnectedComment = connect(commentSelector, commentActions)(CommentList);

拆分connect函數

  // connect 是一個函數,它的返回值為另外一個函數。
  const enhance = connect(commentListSelector, commentListActions)
  // 返回值為 HOC,它會返回已經連線 Redux store 的元件
 const ConnectedComment = enhance(CommentList);

約定-包裝顯示名稱以便輕鬆偵錯

HOC建立的容器元件會和任何其他元件一樣,顯示在React Developer Tools中,為了方便偵錯,需要選擇顯示一個名稱,以表明他是HOC的產物

function withSubscription(WrappedComponent) {
 class WithSubscription extends React.Component {/* ... */}
 WithSubscription.displayName = `WithSubscription(${getDisplayName(WrappedComponent)})`;
 return WithSubscription;
}
function getDisplayName(WrappedComponent) {
 return WrappedComponent.displayName || WrappedComponent.name || 'Component';
}

使用高階元件的注意事項

不要在render方法中使用HOC

render() {
  // 每次呼叫 render 函數都會建立一個新的 EnhancedComponent
  // EnhancedComponent1 !== EnhancedComponent2
  const EnhancedComponent = enhance(MyComponent);
  // 這將導致子樹每次渲染都會進行解除安裝,和重新掛載的操作!
  return <EnhancedComponent />;
}

務必複製靜態方法

   // 定義靜態函數
 WrappedComponent.staticMethod = function() {/*...*/}
 // 現在使用 HOC
 const EnhancedComponent = enhance(WrappedComponent);
 // 增強元件沒有 staticMethod
 typeof EnhancedComponent.staticMethod === 'undefined' // true
//為了解決這個問題,你可以在返回之前把這些方法拷貝到容器元件上:
function enhance(WrappedComponent) {
   class Enhance extends React.Component {/*...*/}
   // 必須準確知道應該拷貝哪些方法 :(
   Enhance.staticMethod = WrappedComponent.staticMethod;
   return Enhance
 }

Refs 不會被傳遞

雖然高階元件的約定是將所有 props 傳遞給被包裝元件,但這對於 refs 並不適用。那是因為 ref 實際上並不是一個 prop - 就像 key 一樣,它是由 React 專門處理的。如果將 ref 新增到 HOC 的返回元件中,則 ref 參照指向容器元件,而不是被包裝元件。

到此這篇關於React高階元件使用教學詳解的文章就介紹到這了,更多相關React高階元件內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com