React高階組件使用教程詳解
高階組件(HOC)
概述
是React復(fù)用組件邏輯的一種高級(jí)技巧,是一種基于React組合特性而形成的設(shè)計(jì)模式
高階組件是參數(shù)為組件,返回值為新組件的函數(shù)
簡(jiǎn)單理解:
- 高階組件本身是 函數(shù),傳參數(shù)是組件,返回值也是組件;
- 高階組件不用關(guān)心數(shù)據(jù)是如何渲染的,只用關(guān)心邏輯即可
- 被包裝的組件本身不用關(guān)心數(shù)據(jù)是怎么來(lái)的,只用負(fù)責(zé)渲染即可
- 最后渲染的是高階組件返回的組件
高階組件的調(diào)用過(guò)程類似于這樣:
const EnhancedComponent = higherOrderComponent(WrappedComponent);
應(yīng)用場(chǎng)景:redux 中的 connect
具體怎么編寫呢?往下看…
使用HOC解決橫切關(guān)注點(diǎn)問(wèn)題
橫切關(guān)注點(diǎn)問(wèn)題:指的是一些具有橫越多個(gè)模塊的行為,使用傳統(tǒng)的軟件開(kāi)發(fā)方法不能夠達(dá)到有效的模塊化的一類特殊關(guān)注點(diǎn)。
組件是React 中代碼復(fù)用的基本單元,但某些模式并不適合傳統(tǒng)組件
假設(shè)有一個(gè) CommentList 組件,訂閱外部數(shù)據(jù)源,用于渲染評(píng)論列表:
class CommentList extends React.Component { constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); this.state = { // 假設(shè) "DataSource" 是個(gè)全局范圍內(nèi)的數(shù)據(jù)源變量,來(lái)自外部,自身帶有很多方法 comments: DataSource.getComments() //假設(shè)getComments()這個(gè)方法可以獲取所有的評(píng)論 }; } componentDidMount() { // 訂閱更改;監(jiān)聽(tīng) DataSource ,發(fā)生變化時(shí)更新數(shù)據(jù) DataSource.addChangeListener(this.handleChange); } componentWillUnmount() { // 清除訂閱 DataSource.removeChangeListener(this.handleChange); } handleChange() { // 當(dāng)數(shù)據(jù)源更新時(shí),更新組件狀態(tài) this.setState({ comments: DataSource.getComments() //假設(shè)getComments()這個(gè)方法可以獲取所有的評(píng)論 }); } render() { return ( <div> {this.state.comments.map((comment) => ( <Comment comment={comment} key={comment.id} /> ))} </div> ); } } // 假設(shè) DataSource:來(lái)自外部;它自身有很多方法,如:getComments(),addChangeListener,removeChangeListener 等 // 假設(shè) <Comment /> 是子組件,父組件 CommentList 需要將 comment 、key 傳遞給它
假設(shè)有個(gè) 訂閱單個(gè)博客帖子的組件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} />; } }
以上兩個(gè)組件的不同點(diǎn)
- 調(diào)用方法不用
以上兩個(gè)組件的相同點(diǎn)
- 在掛載時(shí),向 DataSource 添加一個(gè)更改偵 聽(tīng)器在偵 聽(tīng)器
- 內(nèi)部,當(dāng)數(shù)據(jù)源發(fā)生變化時(shí),調(diào)用 setState
- 在卸載時(shí),刪除偵 聽(tīng)器
上面兩個(gè)組件相同點(diǎn)的地方被不斷的重復(fù)調(diào)用,在大型項(xiàng)目中,所以我們需要將這些共同使用的地方給抽象出來(lái),然后讓許多組件之間共享它,這正是高階組件擅長(zhǎng)的地方。
編寫一個(gè)創(chuàng)建組件函數(shù),這個(gè)函數(shù)接收兩個(gè)參數(shù),一個(gè)是要被包裝的子組件,另一個(gè)則是該子組件訂閱數(shù)據(jù)的函數(shù)。
const CommentListWithSubscription = withSubscription( CommentList, (DataSource) => DataSource.getComments() ); const BlogPostWithSubscription = withSubscription( BlogPost, (DataSource, props) => DataSource.getBlogPost(props.id) ); //以上寫法相當(dāng)于高級(jí)組件的調(diào)用,withSubscription為自定義的高階組件;CommentList:被包裝的子組件;CommentListWithSubscription:返回的包裝后的組件
當(dāng)渲染 CommentListWithSubscription 和 BlogPostWithSubscription 時(shí), CommentList 和 BlogPost 將傳遞一個(gè) data prop,其中包含從 DataSource 檢索到的最新數(shù)據(jù)
// 此函數(shù)接收一個(gè)組件... function withSubscription(WrappedComponent, selectData) { // ...并返回另一個(gè)組件... return class extends React.Component { constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); this.state = { data: selectData(DataSource, props) }; } componentDidMount() { // ...負(fù)責(zé)訂閱相關(guān)的操作... DataSource.addChangeListener(this.handleChange); } componentWillUnmount() { DataSource.removeChangeListener(this.handleChange); } handleChange() { this.setState({ data: selectData(DataSource, this.props) }); } render() { // ... 并使用新數(shù)據(jù)渲染被包裝的組件! // 請(qǐng)注意,我們可能還會(huì)傳遞其他屬性 return <WrappedComponent data={this.state.data} {...this.props} />; } }; }
HOC不會(huì)修改傳入的組件,也不會(huì)使用繼承來(lái)復(fù)制其行為,相反HOC是通過(guò)將組件包裝在容器組件中來(lái)組成新的組件,HOC是純函數(shù),沒(méi)有副作用
- 被包裝組件接收來(lái)自容器組件的所有prop,同時(shí)也接收一個(gè)新的用于render的data prop
- HOC不用關(guān)心數(shù)據(jù)的使用方式,被包裝組件也不用關(guān)心數(shù)據(jù)是怎么來(lái)的
不用改變?cè)冀M件使用組合
不要試圖在 HOC 中修改組件原型(或以其他方式改變它)
function logProps(InputComponent) { InputComponent.prototype.componentDidUpdate = function(prevProps) { console.log('Current props: ', this.props); console.log('Previous props: ', prevProps); }; // 返回原始的 input 組件,暗示它已經(jīng)被修改。 return InputComponent; } // 每次調(diào)用 logProps 時(shí),增強(qiáng)組件都會(huì)有 log 輸出。 const EnhancedComponent = logProps(InputComponent) //上面這種寫法會(huì)造成另一個(gè)同樣會(huì)修改componentDidUpate的HOC增強(qiáng)它,那么前面的HOC就會(huì)失效。
HOC不應(yīng)該修改傳入組件,而應(yīng)該使用組合的方式,將組件包裝在容器組件中實(shí)現(xiàn)功能。
function logProps(WrappedComponent) { return class extends React.Component { componentDidUpdate(prevProps) { console.log('Current props: ', this.props); console.log('Previous props: ', prevProps); } render() { // 將 input 組件包裝在容器中,而不對(duì)其進(jìn)行修改。Good! return <WrappedComponent {...this.props} />; } } }
約定-將不相關(guān)的 props 傳遞給被包裹的組件
HOC為組件添加特性,自身不應(yīng)該大幅改變約定,HOC應(yīng)該透?jìng)髋c自身無(wú)關(guān)的props,大多數(shù)HOC都應(yīng)該包含一個(gè)類似于下面的render方法
render() { // 過(guò)濾掉非此 HOC 額外的 props,且不要進(jìn)行透?jìng)? const { extraProp, ...passThroughProps } = this.props; // 將 props 注入到被包裝的組件中。 // 通常為 state 的值或者實(shí)例方法。 const injectedProp = someStateOrInstanceMethod; // 將 props 傳遞給被包裝組件 return ( <WrappedComponent injectedProp={injectedProp} {...passThroughProps} /> ); }
約定-最大化可組合性
有時(shí)候它僅接受一個(gè)參數(shù),也就是被包裹的組件:
const NavbarWithRouter = withRouter(Navbar);
HOC通常也可以接收多個(gè)參數(shù)
const CommentWithRelay = Relay.createContainer(Comment, config);
常見(jiàn)的HOC簽名(React Redux的connect函數(shù)):
// React Redux 的 `connect` 函數(shù)const ConnectedComment = connect(commentSelector, commentActions)(CommentList);
拆分connect函數(shù)
// connect 是一個(gè)函數(shù),它的返回值為另外一個(gè)函數(shù)。 const enhance = connect(commentListSelector, commentListActions) // 返回值為 HOC,它會(huì)返回已經(jīng)連接 Redux store 的組件 const ConnectedComment = enhance(CommentList);
約定-包裝顯示名稱以便輕松調(diào)試
HOC創(chuàng)建的容器組件會(huì)和任何其他組件一樣,顯示在React Developer Tools中,為了方便調(diào)試,需要選擇顯示一個(gè)名稱,以表明他是HOC的產(chǎn)物
function withSubscription(WrappedComponent) { class WithSubscription extends React.Component {/* ... */} WithSubscription.displayName = `WithSubscription(${getDisplayName(WrappedComponent)})`; return WithSubscription; } function getDisplayName(WrappedComponent) { return WrappedComponent.displayName || WrappedComponent.name || 'Component'; }
使用高階組件的注意事項(xiàng)
不要在render方法中使用HOC
render() { // 每次調(diào)用 render 函數(shù)都會(huì)創(chuàng)建一個(gè)新的 EnhancedComponent // EnhancedComponent1 !== EnhancedComponent2 const EnhancedComponent = enhance(MyComponent); // 這將導(dǎo)致子樹(shù)每次渲染都會(huì)進(jìn)行卸載,和重新掛載的操作! return <EnhancedComponent />; }
務(wù)必復(fù)制靜態(tài)方法
// 定義靜態(tài)函數(shù) WrappedComponent.staticMethod = function() {/*...*/} // 現(xiàn)在使用 HOC const EnhancedComponent = enhance(WrappedComponent); // 增強(qiáng)組件沒(méi)有 staticMethod typeof EnhancedComponent.staticMethod === 'undefined' // true //為了解決這個(gè)問(wèn)題,你可以在返回之前把這些方法拷貝到容器組件上: function enhance(WrappedComponent) { class Enhance extends React.Component {/*...*/} // 必須準(zhǔn)確知道應(yīng)該拷貝哪些方法 :( Enhance.staticMethod = WrappedComponent.staticMethod; return Enhance }
Refs 不會(huì)被傳遞
雖然高階組件的約定是將所有 props 傳遞給被包裝組件,但這對(duì)于 refs 并不適用。那是因?yàn)?ref 實(shí)際上并不是一個(gè) prop - 就像 key 一樣,它是由 React 專門處理的。如果將 ref 添加到 HOC 的返回組件中,則 ref 引用指向容器組件,而不是被包裝組件。
到此這篇關(guān)于React高階組件使用教程詳解的文章就介紹到這了,更多相關(guān)React高階組件內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
React Router V5:使用HOC組件實(shí)現(xiàn)路由攔截功能
這篇文章主要介紹了React Router V5:使用HOC組件實(shí)現(xiàn)路由攔截功能,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-03-03React高級(jí)特性Context萬(wàn)字詳細(xì)解讀
React的context就是一個(gè)全局變量,可以從根組件跨級(jí)別在React的組件中傳遞。React context的API有兩個(gè)版本,React16.x之前的是老版本的context,之后的是新版本的context2022-11-11React中的權(quán)限組件設(shè)計(jì)問(wèn)題小結(jié)
這篇文章主要介紹了React中的權(quán)限組件設(shè)計(jì),整個(gè)過(guò)程也是遇到了很多問(wèn)題,本文主要來(lái)做一下此次改造工作的總結(jié),對(duì)React權(quán)限組件相關(guān)知識(shí)感興趣的朋友一起看看吧2022-07-07使用React封裝一個(gè)Tree樹(shù)形組件的實(shí)例代碼
這篇文章主要介紹了使用React封裝一個(gè)Tree樹(shù)形組件的實(shí)例,文中通過(guò)代碼示例講解的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2024-03-03