高階組件相關知識點詳解

高階組件知識點理解


本章節,我們將深入高階組件詳細瞭解,高階組件相關的知識點,包括什麼是高階組件,以及如何創建高階組件,高階組件實現的幾種方式,常用的使用場景等等;首先我們需要來了解下,和高階組件非常類似的一個概念,高階函數:如果一個函數操作其他函數,將其他函數作爲參數或返回值,將其稱爲高階函數。

  • 關於高階組件的概念:高階組件(high-order component)類似於高階函數,接收 React 組件作爲輸入,輸出一個新的 React 組件。高階組件讓代碼更具有複用性、邏輯性與抽象特徵。可以對 render 方法作劫持,也可以控制 propsstate

  • 實現高階組件的方法有如下2種:

    • 屬性代理(props proxy):屬性組件通過被包裹的 React 組件來操作 props
    • 反向代理(inheritance inversion):高階組件繼承於被包裹的 React 組件。

第一種方法:屬性代理

屬性代理是比較常見的一種高階組件的實現方法。如下:

const MyContainer = (WrappedComponent) => {
    return class extends Component {
        render() {
            return (
                <WrappedComponent {...props} />
            )
        }
    }
}
export default MyContainer;

render 方法中返回傳入 WrappedComponentReact 組件。這樣就可以通過高階組件來傳遞 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 引用,這意味着它可以使用 WrappedComponentstateprops、生命週期和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;

組件命名

當包裹一個高階組件時,我們失去了原始的 WrappedComponentdisplayName,而組件名字是方便我們開發與調試的重要屬性。

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 {
    //...
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章