高階組件知識點理解
本章節,我們將深入高階組件詳細瞭解,高階組件相關的知識點,包括什麼是高階組件,以及如何創建高階組件,高階組件實現的幾種方式,常用的使用場景等等;首先我們需要來了解下,和高階組件非常類似的一個概念,高階函數
:如果一個函數操作其他函數,將其他函數作爲參數或返回值,將其稱爲高階函數。
-
關於高階組件的概念:
高階組件
(high-order component)類似於高階函數,接收 React 組件作爲輸入,輸出一個新的 React 組件。高階組件讓代碼更具有複用性、邏輯性與抽象特徵。可以對render
方法作劫持,也可以控制props
與state
。 -
實現高階組件的方法有如下
2
種:- 屬性代理(
props proxy
):屬性組件通過被包裹的React
組件來操作props
。 - 反向代理(
inheritance inversion
):高階組件繼承於被包裹的React
組件。
- 屬性代理(
第一種方法:屬性代理
屬性代理是比較常見的一種高階組件的實現方法。如下:
const MyContainer = (WrappedComponent) => {
return class extends Component {
render() {
return (
<WrappedComponent {...props} />
)
}
}
}
export default MyContainer;
在 render
方法中返回傳入 WrappedComponent
的 React
組件。這樣就可以通過高階組件來傳遞 props
,這種方法即爲屬性代理。
當然,原始組件想要具備高階組件對它的修飾,有下面兩種方式:
第1種方式
export default MyContainer;
class MyComponent extends Component {
}
export default MyContainer(MyComponent);
第2種方式
使用 ES7
中的 decorator
的屬性,我們可以通過 decorator
來轉換,以此簡化高階組件的調用。
@MyContainer
class MyComponent extends Component {
}
export default MyComponent;
控制 props
我們可以讀取、增加、編輯或是移除從 WrappedComponent
傳進來的 props
,但需要小心刪除與編輯重要的 props
。應該儘量對高階組件的 props
作新的命名以防止混淆。
const MyContainer = (WrappedComponent) => {
return class extends Component {
render() {
const newProps = { text: newText };
return (
<WrappedComponent {...props} {...newProps} />
)
}
}
}
export default MyContainer;
從上面這個案例看來,當調用這個MyContainer
高階組件時,就可以使用 text
這個新的 props
了。
通過 refs
使用引用
在高階組件中,可以接受 refs
使用 WrappedComponent
的引用。
const MyContainer = (WrappedComponent) => {
return class extends Component {
ref = (view) => {
view.mentod();
}
render() {
const props = Object.assign({}, this.props, { ref: this.ref });
return (
<WrappedComponent {...props} />
)
}
}
}
export default MyContainer;
抽象 state
可以通過 WrappedComponent
提供的 props
和回調函數抽象 state
。將原組件抽象爲展示型組件,分離內部狀態 。
const MyContainer = (WrappedComponent) => {
return class extends Component {
constructor(props);
super(props);
this.state = { name: '' };
}
onNameChange(text) {
this.setState({ name: text });
}
render() {
const newProps = {
name = { value: this.state.name, onChangeText: { this.onNameChange } }
}
return (
<WrappedComponent {...this.props} {...newProps} />
);
}
}
export default MyContainer;
使用情況如下:
@MyContainer
class MyComponent extends Component {
render() {
return (
<TextInput {...this.props.name} />
);
}
}
使用其他元素包裹 WrappedComponent
export default LoginPleaseMixin;
const MyContainer = (WrappedComponent) => {
return class exrends PureComponent {
render() {
return (
<View>
<WrappedComponent />
</View>
);
}
}
}
export default MyContainer;
第二種方法:反向繼承
反向繼承是另一種構建高階組件的方法。
const MyContainer = (WrappedComponent) => {
return class extends WrappedComponent {
render() {
return super.render();
}
}
}
export default MyContainer;
高階組件返回的組件繼承於 WrappedComponent
。因爲被動地繼承了 WrappedComponent
,所有的調用都會反向。
通過繼承 WrappedComponent
來實現,方法可以通過 super
來順序調用。
在反向繼承方法中,高階組件可以使用 WrappedComponent
引用,這意味着它可以使用 WrappedComponent
的 state
、props
、生命週期和render
方法,但他不能保證完整的子組件樹被解析。
渲染劫持
概念:渲染劫持指的就是高階組件可以控制 WrappedComponent
的渲染過程,並渲染各種各樣的結果。我們可以在這個過程中在任何 React
元素輸出的結果中讀取、增加、修改、刪除 props,或讀取或修改 React 元素樹、或條件顯示元素樹,又或是用樣式控制包裹元素樹。
反向繼承不能保證完整的子組件樹被解析,這意味着將限制渲染劫持功能。但是如果元素樹包裹了函數類型的 React 組件,就不能操作組件的子組件。
- 條件渲染:
const MyContainer = (WrappedComponent) => {
return class extends WrappedComponent {
render() {
if (this.props.loggIn) {
return super.render();
} else {
return null;
}
}
}
}
export default MyContainer;
- 對
render
輸出結果進行修改:
//...
const MyContainer = (WrappedComponent) => {
return class extends WrappedComponent {
render() {
const elementsTree = super.render();
let newProps = {};
if (elementsTree && elementsTree.type === 'input') {
newProps = {value: 'may the force be with you'};
}
const props = Object.assign({}, elementsTree.props, newProps);
const newElementsTree = React.cloneElement(elementsTree, props, elementsTree.props.children);
return newElementsTree;
}
}
}
export default MyContainer;
在這裏,我們可以做各種各樣的事,甚至可以反轉元素樹,或是改變元素樹中的 props
。
控制 state
高階組件可以讀取、修改或刪除 WrappedComponent 實例中的 state,如果需要的話,也可以增加 state。但是這樣做,可能讓 WrappedComponent 組件內部狀態變得一團糟。大部分的高階組件都應該限制讀取或增加 state,尤其是後者,可以通過重新命名 state,以防止混淆。
const MyContainer = (WrappedComponent) => {
return class extends WrappedComponent {
render() {
return (
<View>
<Text>
{JSON.stringify(this.props)}
</Text>
<Text>
{JSON.stringify(this.state)}
</Text>
{super.render()}
</View>
);
}
}
}
export default MyContainer;
組件命名
當包裹一個高階組件時,我們失去了原始的 WrappedComponent
的 displayName
,而組件名字是方便我們開發與調試的重要屬性。
HOC.displayName = `HOC{${getDisplayName(WrappedComponent)}}`;
class HOC extends ... {
static displayName = `{Hoc(${getDisplayName(WrappedComponent)})}`
function getDisplayName(WrappedComponent) {
return WrappedComponent.displayName ||
WrappedComponent.name ||
'Component';
}
}
組件參數
調用高階組件時需要傳入一些參數,可以用簡單方式實現。
function HOCFactoryFactory(...params) {
return function HOCFactory(WrappedComponent) {
return class HOC extends Component {
render() {
return (
<WrappedComponent {...this.props} />
);
}
}
}
}
使用如下:
HOCFactoryFactory(params)(WrappedComponent)
// or
@HOCFactoryFactory(params)
class WrappedComponent extends Component {
//...
}