<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
是React複用元件邏輯的一種高階技巧,是一種基於React組合特性而形成的設計模式
高階元件是引數為元件,返回值為新元件的函數
簡單理解:
高階元件的呼叫過程類似於這樣:
const EnhancedComponent = higherOrderComponent(WrappedComponent);
應用場景:redux 中的 connect
具體怎麼編寫呢?往下看…
橫切關注點問題:指的是一些具有橫越多個模組的行為,使用傳統的軟體開發方法不能夠達到有效的模組化的一類特殊關注點。
元件是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} />; } }
以上兩個元件的不同點
以上兩個元件的相同點
上面兩個元件相同點的地方被不斷的重複呼叫,在大型專案中,所以我們需要將這些共同使用的地方給抽象出來,然後讓許多元件之間共用它,這正是高階元件擅長的地方。
編寫一個建立元件函數,這個函數接收兩個引數,一個是要被包裝的子元件,另一個則是該子元件訂閱資料的函數。
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是純函數,沒有副作用
不要試圖在 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} />; } } }
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!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45