網站首頁 編程語言 正文
高階組件(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 引用指向容器組件,而不是被包裝組件。
原文鏈接:https://blog.csdn.net/xbczrz/article/details/128111357
相關推薦
- 2022-11-19 React實現多個場景下鼠標跟隨提示框詳解_React
- 2022-06-19 python繪制餅圖和直方圖的方法_python
- 2022-09-26 RNN的手動推導與代碼逐行實現
- 2022-04-11 git項目初次push提示error: failed to push some refs to ht
- 2022-05-28 C語言?超詳細講解鏈接器_C 語言
- 2022-09-25 linux基礎入門1 vi/vim編輯器
- 2022-08-23 Selenium多窗口切換解決方案_python
- 2024-02-29 UNI-APP中點擊事件多重響應問題的解決,list列表項item和列表項item中按鈕的點擊事件沖
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支