Higher Order Components

29th April 2020

Higher Order Components (HOC) - компоненты высшего порядка. Это паттерн React призванный сократить дублирование логики.

Пусть слово компонент не вводит в заблуждение, на самом деле - это функция которая возвращает компонент. За основу HOC взят паттерн Функция высшего порядка.

Фукнции высшего порядка в Javascript мы используем довольно часто. Это наши любимые .map(), reduce(), filter() и тд. То есть функции, которые в качестве аргумента принимают другие функции.

Компоненты высшего порядка очень похожи, только вместо функции, аргументом они принимают компонент. И мы, как бы, возвращаем его "прокаченную" версию. В React с HOC можно столкнуться работая с такими библиотеками как Redux или Material UI.

Пример использования

Допустим у нас есть кнопка Button.js, которая меняет цвет по клику

Button.js

import React, { Component } from 'react'

export default class Button extends Component {
    constructor(props) {
        super(props)
    
        this.state = {
             color: '#bada55'
        }
    }
    changeColor = () =>{
        const color = `#${Math.floor(Math.random()*16777215).toString(16)}`;

        this.setState({
            color
        })
    }
    render() {
        const styles = {
            padding:'10px',
            border:'none',
            cursor:'pointer',
            margin:'15px',
            background:this.state.color}
        return (
            <button onClick={this.changeColor} style={styles}>Change color </button>
        )
    }
}

Все круто. А теперь представим, что нам нужен еще компонет Box.js в котором будет использоваться та же логика, только вместо клика на кнопку, цвет будет меняться при наведении курсора на компонет.

Box.js

import React, {Component } from 'react'

export default class Box extends Component{
    constructor(props) {
        super(props)
    
        this.state = {
             color: '#bada55'
        }
    }
    changeColor = () =>{
        const color = `#${Math.floor(Math.random()*16777215).toString(16)}`;

        this.setState({
            color
        })
    }
    render() {
        const styles = {width:'200px',
                        height:'200px',
                        margin:'15px',
                        background:this.state.color} 
        return (
            <div onMouseOver={this.changeColor} style={styles}>
                Hover to change color
            </div>
        )
    }
}

Всё хорошо, но теперь у нас дублируется код. В нашем примере, в этом нет ничего плохого, 1 простая функция и компонентов всего 2, но что если нужно будет создать еще компоненты с такой же логикой и что если функций будет больше?

Чтобы не копировать код каждый раз и избежать повторов( принцип DRY Don't Repeat Yourself ), создадим компонент высшего порядка withColor.js и перенесем логику туда:

withColor.js

import React, {Component} from 'react'
    
const withColor = (WrappedComponent) =>{
        return class extends Component{
            state = {
                 color: '#bada55'
            }
        changeColor = () =>{
            const color = `#${Math.floor(Math.random()*16777215).toString(16)}`;
    
            this.setState({
                color
            })
        }
        render(){
         return (
            <WrappedComponent changeColor={this.changeColor} color={this.state.color} />
            )  
        }
    }
}

export default withColor;

Мы перенесли стейт и функцию changeColor из Button.js и Box.js в HOC withColor, который принимает в качестве аргумента компонент и возвращает его с пропсами. Теперь можно переписать Button.js и Box.js и экспортировать их с оберткой в компонент высшего порядка который мы создали.

Button.js

import withColor from './withColor'
import React from 'react'


function Button(props) {
    const styles = {
        padding:'10px',
        border:'none',
        cursor:'pointer',
        margin:'15px',
        background:props.color
    }
    return (
        <button onClick={props.changeColor} style={styles} >Change color </button>
    )
}

export default withColor(Button)

Box.js

import React from 'react'
import withColor from './withColor';

const Box = (props) =>{
    const styles = {
            width:'200px',
            height:'200px',
            margin:'15px',
            background:props.color
        } 
    return (
            <div onMouseOver={props.changeColor} style={styles}>
                Hover to change color
            </div>
    )
}

export default withColor(Box);

Теперь наши компоненты Button и Box стали проще. Функция changeColor и state вынесены в HOC и если нам придется добавить еще компонент с такой же логикой, нам не нужно будет дублировать код.

Пример далекий от практики, но главная идея должна быть понятна.


Собственно в этом и вся суть компонентов высшего порядка. Лично мне почти не приходилось использовать этот паттерн и, с введением хуков в React, юзкейсов для HOC стало меньше, но всё же это полезно знать.

Последние посты
Знакомство с Mobx
Пайпы в Angular
Debounce и throttle