Компаненты вышэйшага порядку: ад класовых обертакоў да функцый і хуків
Дазнайцеся, як працуюць компаненты вышэйшага порядку ў React, як той самы хук-логгер выглядае як клас і як функцыя, а таксама чаму спецыяльныя хукі заменілі большасць сцэнарыяў викорыстоўвання HOC.
Крыніцы: 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-аў вводзяць пропы з аднаковымі іменамі
Тая ж ідея з функцыйнымі компонентамі
Без класаў, 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-аў пад час рэндарування.