Галоўная / Артыкулы

Компаненты вышэйшага порядку: ад класовых обертакоў да функцый і хуків

Дазнайцеся, як працуюць компаненты вышэйшага порядку ў React, як той самы хук-логгер выглядае як клас і як функцыя, а таксама чаму спецыяльныя хукі заменілі большасць сцэнарыяў викорыстоўвання HOC.

Выдавец: REACTAPP.TOP5 хв чытання1092 слоў

Крыніцы: React 27: Higher Order Components (HOC) — Deep Dive (Class vs Functional Components)

Задоўга да появы спецыяльных хуків, разработчыкам React была патрэбна способа для аддачы спецыяльных функцый, такіх як логаванне, пераканання ў автентыкацыі чы абчысленне дадзеных, у багацько компонентаў. Рашэнням стала складная компанента (HOC) – звычайная функцыя, якая прымеўа компанент і стварае ўзгаджаны кэйс навакола яго. Ёсць прыклад таго ж HOC для логавання, створанага як клас і як функцыя, пасля чаго яго пораўнююць зі спецыяльным хукам.

Чаму HOC-ы з’явіліся ў эпоху класаў

Калі большая частка коду React была напісана як класы, павтарэнне логіки было незручным. Уявіце, што Profile, Dashboard і Settings всім патрэбна можлівасць логавання моменту ўстановкі і апдэйта, перакананняся, што корыстнік увайшоў, завантажэння дадзеных і обробка памылак. Без спяльнай абстракціі кожны клас павтараў той самы констрактор, настройкі стану, методы жыцёвага циклу і дапаможныя методы. HOC-ы рашылі гэту проблему, загорнуўшы клас у іншы клас, які мае спяльную логіку жыцёвага циклу.

HOC — гэта не спецыяльны API React, а проста композіцыя функцый, якая застосовуецца да компонентаў.

HOC для логавання, створанный з класаў

Напісанне заглушкі

Следуючы HOC вяртае новы клас, які логавае момент установкі загорнутага компонента і ўсё час, калі яны апдэююцца, а пасля выводзіць первынны компонент, праадначыўшы ўсе прапсы:

import React, { Component } from "react";

export function withRenderLogger(WrappedComponent) {
  return class extends Component {
    componentDidMount() {
      console.log(`${WrappedComponent.name} mounted`);
    }
    componentDidUpdate() {
      console.log(`${WrappedComponent.name} updated`);
    }
    render() {
      return <WrappedComponent {...this.props} />;
    }
  };
}

Камуфляжны клас контролюе методы жыццёвага циклу і перадае this.props без змян, таму ў асловным компаненте немае коду для логавання.

Як яго застаўляць

Вы экспортуее версію, якая абгорнута камуфляжным класом, у замене на чысты клас:

import React, { Component } from "react";
import { withRenderLogger } from "./withRenderLogger";

class Profile extends Component {
  render() {
    return <h2>Profile Component</h2>;
  }
}
export default withRenderLogger(Profile);

Прадставленне Profile тепер прадстаўляе камуфляжны клас, які у свою чаргу прадстаўляе справжні Profile. Працягнуты клас є ананімным, таму заданне displayName, напрыклад withRenderLogger(Profile), спакойнае чытанне ў React DevTools.

Косты HOC-аў, базаваных на класах

Це працавало, але з труднасцямі:

  • вкладаныя визначэнні класаў і прыязычванне this
  • логіка, распадзелена між методамі жыццёвага циклу
  • глыбэйшыя дрэвы компанентаў, таму для адлучэння бягункі патрабавалася працава з камуфляжнымі класамі
  • колізіі імен пропаў, калі калькі HOC-аў вводзяць пропы з аднаковымі іменамі
  • непрамыма зв’язка між HOC і прапамі обгорнутага компонента
  • Тая ж ідея з функцыйнымі компонентамі

    Без класаў, this чы рою методаў жыццёвага цыклу, HOC ператвараецца ў функцыю, якая вяртае функцыйны компонент:

    function HOC(WrappedComponent) {
      return function Enhanced(props) {
        return <WrappedComponent {...props} />;
      };
    }
    

    Ёсць прыклад логгера, перапісанага у такім стыле:

    export function withLogger(WrappedComponent) {
      return function Enhanced(props) {
        console.log(`${WrappedComponent.name} rendered`);
       return <WrappedComponent {...props} />;
      };
    }
    

    Спосаб выкарыстоўвання ідэнтычны версіі з класамі:

    import React from "react";
    import { withLogger } from "./withLogger";
    
    function Dashboard() {
      return <h2>Dashboard Component</h2>;
    }
    export default withLogger(Dashboard);
    

    Ён логуе ў кожны раз, калі відбываецца рендер, але як пасляэфект у тэле рендера. React можа відбываць рендер частае, чым фактычна зберагае змянення, а Strict Mode відбывае рендер два разы пад час розработы, таму магуць з’явіцца дублікаты логаў; для чаго-небудзь большага, чым простая дыягностика, выкорыстоўваюце useEffect.

    Маленькая дэманстрацыйная аплікацыя

    Кнапкі лямоўвае межы класовага Profile і функцыйнага Dashboard, тады як оба HOC-а фіксуюць лог. Адкрыттыя дэманстрацыі за дапамою рэндарування, а не выклікаў памылак, робяць іх працэс прагнозаваным пад режым Fast Refresh:

    import React, { useState } from "react";
    import Profile from "./Profile";
    import Dashboard from "./Dashboard";
    
    export default function App() {
      const [toggle, setToggle] = useState(true);
      return (
        <div style={{ padding: 20 }}>
          <h1>HOC Demo</h1>
          <button onClick={() => setToggle(!toggle)}>
            Re-render Components
          </button>
          {toggle ? <Profile /> : <Dashboard />}
        </div>
      );
    }
    

    Profile.js выкарыстоўвае класовы HOC:

    import React, { Component } from "react";
    import { withRenderLogger } from "./withRenderLogger";
    
    class Profile extends Component {
      render() {
        return <h2>Profile Component</h2>;
      }
    }
    export default withRenderLogger(Profile);
    

    Dashboard.js выкарыстоўвае функцыйны HOC:

    import React from "react";
    import { withLogger } from "./withLogger";
    
    function Dashboard() {
      return <h2>Dashboard Component</h2>;
    }
    export default withLogger(Dashboard);
    

    Модуль класовага HOC:

    import React, { Component } from "react";
    
    export function withRenderLogger(WrappedComponent) {
      return class extends Component {
        componentDidMount() {
          console.log(`${WrappedComponent.name} mounted`);
        }
        componentDidUpdate() {
          console.log(`${WrappedComponent.name} updated`);
        }
        render() {
          return <WrappedComponent {...this.props} />;
        }
      };
    }
    

    І модуль функцыйнага HOC:

    export function withLogger(WrappedComponent) {
      return function Enhanced(props) {
        console.log(`${WrappedComponent.name} rendered`);
        return <WrappedComponent {...props} />;
      };
    }
    

    Этыя файлы працуюць у будзь-якай стандартной настаўке React; з Vite файлы JSX называюцца .jsx.

    Чаму спецыяльныя хукі заменілі большасць HOC-аў

    Спецыяльныя хукі зараз пакрываюць большасць колышніх сцэнарыяў выкарыстоўвання HOC-аў: завантажэнне дадзеных з іншых выкарыстоўчыкаў, пераканання ў автарызацыі, трэйкінг, аслухальнікі DOM і падпіскі. Логгер таксама стае хуком:

    function useLogger(name) {
      useEffect(() => {
        console.log(`${name} rendered`);
      });
    }
    
    function Dashboard() {
      useLogger("Dashboard");
      return <h2>Dashboard Component</h2>;
    }
    

    Хукі не дадзяюць шараў-обгортакоў, замест таго вяртаюць значэнні і складаюцься без калізыяў. Без масэвай залежнасцей эфект выкананы пасля кожнага збережэння.

    HOC-ы все ўсё падходзяць для умовнага обгортання (прадстаўлення экрана аблогавання замест компонента), бібліятэкаў з API HOC і коду на адной класе; React.memo мае той самы формат. Застосавляйце HOC-ы на рэвэле модуля: стварэнне обгортака пад час рэндарування перезапускае паддрэсін і ведае пра яго стан.

    Ключовыя выводы

    • HOC обгортае компонент і вяртае яго паўнэйшаю версію.
    • Класовыя компоненты выкарыстоўвалі HOC-ы, таму што спадзялецца логікай жыццўнага циклу інакш было б падражнаюча.
    • У функцыйных компонентах HOC — гэта проста функцыя, якая вяртае функцыйны компонент.
    • Спецыяльныя хукі ёсць стандартным выборам для павтарнага выкарыстоўвання логікі сёгодні; заставайце HOC-ы для обгортання і умовнага атрыбутавання.
  • Установіце displayName, утримайце побачныя эфекты за межамі процеса рэндарування і ніколы не створайце HOC-аў пад час рэндарування.