banner
SlhwSR

SlhwSR

热爱技术的一名全栈开发者
github
bilibili

hoc、hofの実装と使用シーン

一、はじめに#

高階関数(Higher-order function)は、次のいずれかの条件を満たす関数です。

  • 1 つ以上の関数を入力として受け取る
  • 関数を出力する

Reactでは、高階コンポーネントは、1 つ以上のコンポーネントを引数として受け取り、コンポーネントを返すものであり、本質的にはコンポーネントではなく関数です。

const EnhancedComponent = highOrderComponent(WrappedComponent);

上記のコードでは、この関数はコンポーネントWrappedComponentを引数として受け取り、加工された新しいコンポーネントEnhancedComponentを返します。

このような高階コンポーネントの実装方法は、本質的にはデコレーターパターンです。

二、実装方法#

基本的な高階コンポーネントの実装テンプレートは次のようになります。

import React, { Component } from 'react';

export default (WrappedComponent) => {
  return class EnhancedComponent extends Component {
    // 何かしらの処理
    render() {
      return <WrappedComponent />;
    }
  }
}

渡された元のコンポーネントWrappedComponentに対して、必要な操作(プロップスの操作、ステートの抽出、他の要素でのラップなど)を行い、必要なコンポーネントEnhancedComponentを作成します。

高階コンポーネントに共通のロジックを配置し、コンポーネントに一貫した処理を実装することで、コードの再利用性を実現します。

したがって、高階コンポーネントの主な機能は、コンポーネントの共通ロジックをカプセル化し、コンポーネント間で共通のロジックをより良く再利用することです。

ただし、高階コンポーネントを使用する際には、通常、次のような規則に従うことが一般的です。

  • プロップスを一貫させる
  • 関数コンポーネント(ステートレスコンポーネント)では ref 属性を使用しないでください。なぜなら、それにはインスタンスがないからです。
  • 元のコンポーネントWrappedComponentをいかなる方法でも変更しないでください。
  • 関連のないプロップス属性をラップされたコンポーネントWrappedComponentに透過的に渡す
  • render () メソッド内で高階コンポーネントを使用しないでください。
  • compose を使用して高階コンポーネントを組み合わせる
  • デバッグしやすいように表示名をラップしてください。

ここで注意する必要があるのは、高階コンポーネントはすべてのプロップスを渡すことができますが、ref を渡すことはできないということです。

高階コンポーネントに ref を追加する場合、ref はラップされたコンポーネントではなく、最も外側のコンテナコンポーネントのインスタンスを指します。ref を渡す必要がある場合は、React.forwardRefを使用します。例:

function withLogging(WrappedComponent) {
    class Enhance extends WrappedComponent {
        componentWillReceiveProps() {
            console.log('Current props', this.props);
            console.log('Next props', nextProps);
        }
        render() {
            const {forwardedRef, ...rest} = this.props;
            // forwardedRefをrefに割り当てる
            return <WrappedComponent {...rest} ref={forwardedRef} />;
        }
    };

    // React.forwardRefメソッドは、propsとrefの2つの引数をコールバック関数に渡します
    // したがって、ここでのrefはReact.forwardRefによって提供されます
    function forwardRef(props, ref) {
        return <Enhance {...props} forwardRef={ref} />
    }

    return React.forwardRef(forwardRef);
}
const EnhancedComponent = withLogging(SomeComponent);

三、応用シーン#

上記の理解に基づいて、高階コンポーネントはコードの再利用性と柔軟性を向上させることができます。実際のアプリケーションでは、権限制御、ログ記録、データ検証、例外処理、統計レポートなど、コアビジネスとは関係のないが複数のモジュールで使用される機能に使用されることがよくあります。

例えば、キャッシュからデータを取得してレンダリングする必要があるコンポーネントが存在するとします。通常の場合、次のように書くことがあります。

import React, { Component } from 'react'

class MyComponent extends Component {

  componentWillMount() {
      let data = localStorage.getItem('data');
      this.setState({data});
  }
  
  render() {
    return <div>{this.state.data}</div>
  }
}

上記のコードはもちろんこの機能を実現できますが、他のコンポーネントにも同様の機能がある場合、各コンポーネントでcomponentWillMount内のコードを繰り返し書く必要があります。これは明らかに冗長です。

次に、高階コンポーネントを使用して書き換えることができます。例:

import React, { Component } from 'react'

function withPersistentData(WrappedComponent) {
  return class extends Component {
    componentWillMount() {
      let data = localStorage.getItem('data');
        this.setState({data});
    }
    
    render() {
      // {...this.props}を使用して、現在のコンポーネントに渡されたプロパティをラップされたコンポーネントWrappedComponentにも渡す
      return <WrappedComponent data={this.state.data} {...this.props} />
    }
  }
}

class MyComponent2 extends Component {  
  render() {
    return <div>{this.props.data}</div>
  }
}

const MyComponentWithPersistentData = withPersistentData(MyComponent2)

また、コンポーネントのレンダリングパフォーマンスの監視などもあります。例:

class Home extends React.Component {
    render() {
        return (<h1>Hello World.</h1>);
    }
}
function withTiming(WrappedComponent) {
    return class extends WrappedComponent {
        constructor(props) {
            super(props);
            this.start = 0;
            this.end = 0;
        }
        componentWillMount() {
            super.componentWillMount && super.componentWillMount();
            this.start = Date.now();
        }
        componentDidMount() {
            super.componentDidMount && super.componentDidMount();
            this.end = Date.now();
            console.log(`${WrappedComponent.name} コンポーネントのレンダリング時間は ${this.end - this.start} msです`);
        }
        render() {
            return super.render();
        }
    };
}

export default withTiming(Home);

参考文献#

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。