本文主要帶大家深入理解React高階元件,希望大家對React高階元件有更清晰的認識。
1.在React中higher-order component (HOC)是一種重複使用元件邏輯的高階技術。 HOC不是React API中的一部分。 HOC是一個函數,該函數接收一個元件並且傳回一個新元件。在React中,元件是程式碼重複使用的基本單位。
2.為了解釋HOCs,舉下面兩個例子
CommentList元件會渲染出一個comments列表,列表中的資料來自於外部。
class CommentList extends React.Component { constructor() { super(); this.handleChange = this.handleChange.bind(this); this.state = { // "DataSource" is some global data source comments: DataSource.getComments() }; } componentDidMount() { // Subscribe to changes DataSource.addChangeListener(this.handleChange); } componentWillUnmount() { // Clean up listener DataSource.removeChangeListener(this.handleChange); } handleChange() { // Update component state whenever the data source changes this.setState({ comments: DataSource.getComments() }); } render() { return ( <p> {this.state.comments.map((comment) => ( <Comment comment={comment} key={comment.id} /> ))} </p> ); } }
接下來是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的不同方法,並且它們的輸出也不一樣,但是它們中的大部分實現是一樣的:
1.裝載完成後,給DataSource添加了一個change listener
2.當資料來源發生變化後,在監聽器內部呼叫setState
3.卸載之後,移除change listener
可以想像在大型應用程式中,相同模式的存取DataSource和呼叫setState會一次又一次的發生。我們希望抽象化這個過程,從而讓我們只在一個地方定義這個邏輯,然後在多個元件中共享。
接下來我們寫一個建立元件的函數,這個函數接受兩個參數,其中一個參數是元件,另一個參數是函數。下面呼叫withSubscription函數
const CommentListWithSubscription = withSubscription( CommentList, (DataSource) => DataSource.getComments() ); const BlogPostWithSubscription = withSubscription( BlogPost, (DataSource, props) => DataSource.getBlogPost(props.id) );
呼叫withSubscription傳的第一個參數是wrapped 元件,第二個參數是一個函數,用於檢索資料。
當CommentListWithSubscription和BlogPostWithSubscription被渲染,CommentList和BlogPost會接受一個叫做data的prop,data中保存了目前從DataSource擷取的資料。 withSubscription程式碼如下:
// This function takes a component... function withSubscription(WrappedComponent, selectData) { // ...and returns another component... return class extends React.Component { constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); this.state = { data: selectData(DataSource, props) }; } componentDidMount() { // ... that takes care of the subscription... DataSource.addChangeListener(this.handleChange); } componentWillUnmount() { DataSource.removeChangeListener(this.handleChange); } handleChange() { this.setState({ data: selectData(DataSource, this.props) }); } render() { // ... and renders the wrapped component with the fresh data! // Notice that we pass through any additional props return <WrappedComponent data={this.state.data} {...this.props} />; } }; }
HOC並沒有修改輸入的元件,也沒有使用繼承去重複使用它的行為。 HOC只是一個函數。 wrapped 元件接受了容器的所以props,同時也接受了一個新的prop(data),data用來渲染wrapped 元件的輸出。 HOC不關心資料怎麼使用也不關心資料為什麼使用,wrapped元件不關心資料是哪裡得到。
因為withSubscription只是一個常規的函數,你可以加入任數的參數。例如,你能讓data prop的名字是可設定的,從而進一步將HOC與wrapped元件隔離。
或接受一個設定shouldComponentUpdate,或是配置資料來源的參數
使用高階元件時有些需要注意的地方。
1.不要修改原始元件,這一點很重要
有以下例子:
function logProps(InputComponent) { InputComponent.prototype.componentWillReceiveProps = function(nextProps) { console.log('Current props: ', this.props); console.log('Next props: ', nextProps); }; // The fact that we're returning the original input is a hint that it has // been mutated. return InputComponent; } // EnhancedComponent will log whenever props are received const EnhancedComponent = logProps(InputComponent);
這裡存在一些問題,1.輸入的元件不能與增強的元件單獨重用。 2.如果給EnhancedComponent應用其他的HOC,也會改變componentWillReceiveProps。
這個HOC對函數類型的元件不適用,因為函數類型元件沒有生命週期函數HOC應該使用合成代替修改-透過將輸入的元件包裹到容器元件中。
function logProps(WrappedComponent) { return class extends React.Component { componentWillReceiveProps(nextProps) { console.log('Current props: ', this.props); console.log('Next props: ', nextProps); } render() { // Wraps the input component in a container, without mutating it. Good! return <WrappedComponent {...this.props} />; } } }
這個新的logProps與舊的logProps有相同的功能,同時新的logProps避免了潛在的衝突。對class類型的組件和函數類型額組件同樣適用。
2.不要在render方法中使用HOCs
React的diff演算法使用組件的身份去決定是應該更新已存在的子樹還是拆除舊的子樹並裝載一個新的,如果從render方法返回的元件與之前渲染的元件恆等(===),那麼React會透過diff演算法更新之前渲染的元件,如果不相等,之前渲染的子樹會完全卸載。
render() { // A new version of EnhancedComponent is created on every render // EnhancedComponent1 !== EnhancedComponent2 const EnhancedComponent = enhance(MyComponent); // That causes the entire subtree to unmount/remount each time! return <EnhancedComponent />; }
在元件定義的外部使用HOCs,以至於結果元件只會建立一次。在少數情況下,你需要動態的應用HOCs,你該在生命週期函數或建構函式中做這件事
3.靜態方法必須手動複製
有的時候在React元件上定義靜態方法是非常有用的。當你給某個元件應用HOCs,雖然原始元件被包裹在容器組件裡,但是傳回的新元件不會有任何原始元件的靜態方法。
// Define a static method WrappedComponent.staticMethod = function() {/*...*/} // Now apply an HOC const EnhancedComponent = enhance(WrappedComponent); // The enhanced component has no static method typeof EnhancedComponent.staticMethod === 'undefined' // true
為了讓傳回的元件有原始元件的靜態方法,就要在函數內部將原始元件的靜態方法複製給新的元件。
function enhance(WrappedComponent) { class Enhance extends React.Component {/*...*/} // Must know exactly which method(s) to copy :( // 你也能够借助第三方工具 Enhance.staticMethod = WrappedComponent.staticMethod; return Enhance; }
4.容器元件上的ref不會傳遞給wrapped component
雖然容器元件上的props可以很簡單的傳遞給wrapped component,但是容器元件上的ref不會傳遞到wrapped component。如果你給透過HOCs回傳的元件設定了ref,這個ref引用的是最外層容器元件,而不是wrapped 元件。
相關推薦
#以上是React高階元件實例解析的詳細內容。更多資訊請關注PHP中文網其他相關文章!