高階組件相似於高階函數,如 map()
、reduce()
、sort()
,即這樣一種函數:接收函數做爲輸入,或是輸出一個函數。一樣做爲高階組件:它接受 React 組件做爲輸入,輸出一個新的 React 組件。前端
比較典型的高階組件是 react-redux 中的 connnect()
函數,通常的使用狀況: export default connect(mapStateToProps, mapDispatchToProps)(WrappedComponent)
。首先,connect(mapStateToProps, mapDispatchToProps)
執行後返回高階組件函數,而後,輸入(WrappedComponent)
組件返回處理事後的組件並導出。react
先看一個很是簡單的高階組件的例子:redux
const HocComponent = (WarppedComponent) => {
return class WrappingComponent extends Component {
render() {
return <WarppedComponent/>;
}
};
};
export default HocComponent;
複製代碼
經過示例可知 高階組件 實際上是一個函數:輸入一個組件,輸出一個新的組件,這個新的組件是對輸入組件的一個加強。其實 高階組件 並非真正的組件,比較嚴謹的說法是: 高階組件工廠函數。但在業界,更遵照廣泛的定義:即 具備加強組件功能的函數 稱爲 高階組件。數組
那麼定義高階組件的意義在哪?promise
首先,重用代碼。如比較常見的狀況:不少組件都須要一個公共的邏輯,那麼這個邏輯,沒有必要在每一個組件當中都實現一遍。最好的方式是將這部分邏輯抽取出來,利用 高階組件 的方式拋出,這樣就會減小不少組件當中的重複代碼。網絡
其次,修改組件行爲。好比有的時候,想對一個組件作一些操做。可是又不想觸碰其中的內部邏輯,這時能夠經過一個函數生成另外一個組件幷包裹原組件,組件之間相對獨立,又不會侵入原組件。app
這是實現高階組件比較經常使用的一種模式, 文章開頭的代碼示例就是一個代理方式的高階組件,只不過沒有實現任何功能。新生成的組件 繼承自 React.Component
,同時 新生成的組件 是 傳入組件 的 代理, 並將 傳入的組件 顯示出來。新生成的組件 本身會作一些事情,其他工做所有由 傳入的組件 實現。固然,所謂的 新生成組件 就是 高階組件 了。代理方式的高階組件 主要有下面幾種應用場景:函數
能夠 編輯 傳入的組件 的 props。性能
const addHOCComponent = (WrappedComponent) => {
return class WrappingComponent extends Component {
render() {
const newProps = {add: 'newProps'};
return <WrappedComponent {...this.props} {...newProps}/>; } } } export default AddHOCComponent; 複製代碼
當使用 高階組件函數 時, 函數返回的 新的組件 會多了一個 add
的 prop。 下面是移除一個 prop 的例子:優化
const removeHOCComponent = (WrappedComponent) => {
return class WrappingComponent extends Component {
render() {
const {remove, ...otherProps} = this.props;
return <WrappedComponent {...otherProps}/>; } } } export default RemoveHOCComponent; 複製代碼
RemoveHOCComponent
組件和WrappedComponent
具備徹底一致的行爲,除了它們的 props 不同。
如上代碼可知,高階組件的複用性是很強的,經過高階組件能夠垂手可得的操控 props ,使用高階組件代碼以下:
const AddPropHoc = addHOCComponent(SomeComponent);
const RemovePropHoc = removeHOCComponent(OtherComponent);
複製代碼
高階組件 能夠將 原組件 的 內部狀態 分離,使其僅僅具備 展現組件 的功能。
const ExtractStateHOC = (WrappedComponent) => {
return class WrappingComponent extends Component {
constructor(props) {
super(props);
this.state = {
name: 'ExtractState'
};
this.onChange = this
.onChange
.bind(this);
}
render() {
const newProps = {
name: this.state.name,
onChange: this.onChange
};
return (<WrappedComponent {...newProps}/>) } onChange(event) { this.setState({name: event.nativeEvent.text}) } } }; export default ExtractStateHOC; 複製代碼
ExtractStateHOC
函數將 WrappedComponent
的 props 增長了 name 和 onChange 。而後經過 props 傳遞給原組件,這樣原組件的 內部狀態 就被抽離到了 高階組件 中去處理。下面是個具體例子:
class TextInputComponent extends Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
const {onChange, name} = this.props;
return (
<View style={styles.container}> <Text>{name}</Text> <TextInput onChange={onChange}></TextInput> </View>
);
}
}
const ExtractStateComponent = ExtractStateHOC(TextInputComponent);
複製代碼
上文中寫到的經過 高階組件 產生的 新組件 都是經過render()
函數直接返回 原組件, 其實也能夠在 原組件 的基礎之上添加一些其餘的組件。
const PackagingHOC = (WrappedComponent, style) => {
return class WrappingComponent extends Component {
render() {
return (
<View style={style}> <WrappedComponent/> </View>
)
}
}
};
export default PackagingHOC;
複製代碼
繼承方式的高階組件採用 繼承 的方式將 原組件 和 新生成的組件 關聯起來。即 新組件 繼承自 原組件 的方式。
const ExtendsHOC = (WrappedComponent) => {
return class WrappingComponent extends WrappedComponent {
render() {
return super.render();
}
}
}
複製代碼
由於是 繼承 的關係,因此在 render()
函數中直接經過 super.render()
就能夠渲染出父類的元素。代理方式的高階組件是生成新的組件,原組件和新組件是兩個不一樣的組件,每次渲染,兩個組件都要經歷完整的生命週期。而到了 繼承方式的高階組件 ,兩個組件的生命週期合併在了一塊兒,有相同的生命週期。
該方式一樣能夠操控 Props,使用 React.cloneElement
從新繪製從父類獲得的元素。
const ExtendsHOC = (WrappedComponent) => {
return class WrappingComponent extends WrappedComponent {
render() {
const elements = super.render();
const newProps = {
name: elements.type.displayName === 'Text' ? 'extends': 'other',
...this.props
};
return React.cloneElement(elements, newProps, elements.props.children);
}
}
}
複製代碼
若是 原組件 的第一層元素是 Text
,那麼添加 name
。 雖然能夠達到操控 Props 的目的,但這種方式太複雜,沒什麼實際意義,因此不多采用這種方式。除非要根據父類的顯示狀況操控不一樣的 Props。
由於是組件之間是 繼承關係 ,因此能夠操控生命週期,代理方式的高階組件沒法作到這一點,由於它生成的新的組件和原組件沒有實質的聯繫。
假設有這樣一種需求:某些特定的頁面在要刷新的時,判斷是否有權限,如無則留在原地,不作刷新。
const ExtendsHOC = (WrappedComponent) => {
return class WrappingComponent extends WrappedComponent {
shouldComponentUpdate(nextProps, nextState, nextContext) {
return condition;
}
render() {
return super.render();
}
}
}
複製代碼
shouldComponentUpdate()
函數決定是否從新渲染 原組件 的元素。如返回 false 則不會執行 render()
。
在前端組件式業務開發中,複用是無疑是提升開發效率的一大利器。React 組件經過配置 Props 能夠實現不一樣的業務邏輯,但隨着需求的不斷增多,單純的經過增長 Props 必然會形成 Props 氾濫,給持續維護形成很大障礙。這時候能夠組合式的組件開發方式,將組件可複用的部分抽象爲粒度更小的組件,而後組合在一塊兒獲得完整功能的組件。下面是一個簡單的例子,一個組合式的列表:
const HandleDecorator = (WrappedComponent) => {
return class WrappingComponent extends Component {
handlePress(msg) {
console.log(msg);
console.log(this.state);
}
render() {
return <WrappedComponent {...this.props} handlePress={this .handlePress .bind(this)}/> } } }; 複製代碼
const DataDecorator = (WrappedComponent) => {
return class WrappingComponent extends Component {
constructor(props) {
super(props);
this.state = {
dataSource: []
};
}
componentDidMount() {
const promise = new Promise((resolve) => {
setTimeout(() => {
const simulateData = ['Swift', 'React', 'SwiftUI', 'JavaScript'];
resolve(simulateData);
}, 3000);
});
promise.then(data => {
this.setState({
dataSource: data
});
})
}
render() {
return <WrappedComponent {...this.props} dataSource={this.state.dataSource}/> } } } 複製代碼
compose()
函數提供了將不一樣的函數組合在一塊兒的功能。可將不定數量的函數做爲 compose()
的參數,而後將這些做爲參數的函數按照從右到左的順序執行:第一個被執行的函數的返回值做爲下一個函數的參數,以此類推。
const ComposeComponent = compose(HandleDecorator, DataDecorator)(ListComponent);
複製代碼
到目前爲止,ListComponent
組件只須要讀取被組合增強過的 props 就能夠了。它的數據來源和事件的處理都交給了其餘高階組件處理。這樣,經過簡單的配置工做,就能完成對組件的自由支配,經過更細粒度的高階組件使組件更靈活、更容易擴展。
class ListComponent extends Component {
render() {
const {dataSource} = this.props;
return (<FlatList
style={styles.container}
data={dataSource}
renderItem={this._createRow}
keyExtractor={this._keyExtractor}
/>);
};
_createRow = (wrappedItem) => <Button title={wrappedItem.item} onPress={() => this.props.handlePress(wrappedItem.item)}/>
_keyExtractor = (item, index) => item;
}
複製代碼
高階組件對原組件的擴展主要依賴於對 props 的操做,一旦原組件沒有聲明自身能支持的 props ,高階組件對原組件再多的擴展也是無用的。以代理方式的高階組件爲例,高階組件和原組件是父子關係,它們之間天然要經過 props 交互,被加強事後的 props 是否有效,取決於原組件的支持。這樣就產生了必定的侷限性,將子組件當作函數處理能夠解決這個問題。
以這種方式實現代碼重用不是經過函數了,而是經過一個真正的 React 組件,這個組件要求必須有子組件,並且子組件必須是函數。this.props.children
引用的是子組件,獲得的結果在 render()
方法中返回,這樣就將子組件渲染出來了。
以一個倒計時程序爲例:倒計時邏輯是可重用的,倒計時結束後所要顯示的畫面應該是靈活的,因此倒計時器抽取到一個父組件中,並將一些信息經過 props 傳遞給子組件。
class CountDownView extends Component {
constructor(props) {
super(props);
this.state = {
count: 10
};
}
componentDidMount() {
this.timer = setInterval(() => {
const newCount = this.state.count - 1;
this.setState({count: newCount});
console.log(newCount);
}, 1000);
}
render() {
return this
.props
.children(this.state.count);
}
componentWillUnmount() {
clearInterval(this.timer);
}
}
複製代碼
上面的代碼設置了一個從 10 開始的倒計時器,每次定時器觸發都會更新 state ,也就每次都會執行 render()
,在其中經過 this.props.childre(this.state.count)
將當前的倒計時數傳遞給子組件。固然,起始 count 也能夠經過 props 傳遞給倒計時器組件,在 count<0 時將定時器移除。
CountDown 封裝好後,就能夠將其應用在所須要倒計時的應用場景,所須要作的僅僅是編寫一個合適的子組件,固然,要是一個函數:
<DetailView >
{
(count) => <View style={styles.container}>
<Text
style={{
color: 'black',
fontSize: 50
}}>{count > 0
? count
: '倒計時結束'}</Text>
</View>
}
</DetailView>
複製代碼
由上可知,函數做爲子組件 的模式相對來講靈活性顯然更高。但以 函數做爲子組件 雖然更靈活,但很難作性能上的優化。每次父組件的更新過程都要得到一個函數來渲染子組件,這樣沒法經過 shouldComponentUpdate()
避免沒必要要的渲染。凡事有利即有弊。具體要使用哪一種方式要根據使用場景而定。