После того как React решил заменить классовые компоненты на функциональные с блэджеком и хуками, он, неизбежно, привел сообщество к холивару, поскольку у каждого вида компонентов есть свои достоинства и недостатки. Сегодня я решил написать решение, которое может прекратить этот холивар, и, разумеется, начать новый.
Давайте посмотрим в чем преимущества и недостатки каждого из вида компонентов:
Классовые:
+ Есть наследование
+ Удобно расположить разные части кода по разным методам
- State может быть только один
- Везде надо писать this
- В целом больше кода
Функциональные:
+ Короче
+ Можно делать много state-ов
+ useEffect с зависимостями выглядит логичнее всяких componentDidUpdate
- Весь код свален в одну функцию
- Вместо наследования не очень наглядные решения вроде пользовательских хуков
А есть все совместить? Я подумал и написал react‑fcomponents. Сразу скажу, что проект на ранней стадии и я даже не уверен, что буду им пользоваться сам, хотя возможно и буду. Но в этой статье я в любом случае объясню как им пользоваться.
Что такое react‑fcomponents? Если кратко, это класс, внутри которого работают хуки и есть несколько дополнительных удобств. Сам класс на данный момент очень короткий.
Давайте посмотрим как им пользоваться на следующем примере:
//ExampleClass.js
import { useEffect, useState } from "react";
import FComponent, { make } from "react-fcomponents";
class ExampleClass extends FComponent {
useTest = (...args) => {
useEffect(() => {
console.log('mounted');
}, []);
return useState(...args);
}
useHooks() {
this.useTest();
this.useState('test', 0);
}
render() {
return <div>{this.props.text}<button onClick={
() => {
this.setStates.test(this.states.test + 1)
}
}>{this.states.test}</button></div>;
}
}
export default make(ExampleClass);
//App.js
import ExampleClass from './ExampleClass';
function App() {
return (
<div>
<ExampleClass text="Hello World" />
<ExampleClass text="Hello World2" />
</div>
);
}
export default App;
Начнем с конца. Как можно заметить, экспортируется не ExampleClass, а make(ExampleClass). Это делается для того, чтобы React не решил что это классовый компонент. Внутри make создает новый экземпляр класса, и возвращает хитрую функцию, которая в конечном счете запускает метод render. Но React считает компонент функциональным!
Теперь посмотрим на метод render:
render() {
return <div>{this.props.text}<button onClick={
() => {
this.setStates.test(this.states.test + 1)
}
}>{this.states.test}</button></div>;
}
С чего тут можно начать. Во‑первых, this.props работает. Кстати, это не обязательно — можно написать render (props) и далее работать без this. Но вот если вы будете работать с this в других методах класса, это пригодится.
Далее, в данном примере я не добавил в render хуки (они в методе useHooks, про который я расскажу позже), но их там можно спокойно использовать и они там будут работать. Вообще, render это содержимое функционального компонента и делать в нем можно все то же, что в функциональном компоненте.
Далее, мы видим весьма необычный метод работы со стейтами. Есть некие this.states.test и this.setStates.test. Очевидно, что это чтение и запись стейта, но откуда оно взялось? Для этого рассмотрим второй метод класса — useHooks:
useHooks() {
this.useTest();
this.useState('test', 0);
}
useHooks — это опциональный метод класса, код в котором вызывается перед вызовом кода render. Предполагается, что в него вы положите вызовы хуков, чтобы не захламлять ими render. Здесь я вызываю два хука — this.useTest и this.useState. Первый рассматривать не вижу смысла — там просто какие‑то обычные useState и useEffect. А вот второй интереснее.
this.useState — это встроенный хук, который принимает на вход уникальное имя стейта и его значение по‑умолчанию. Как можно догадаться, внутри this.useState происходит вызов обычного React.useState, а далее его значение и сеттер кладутся в this.states[имя стейта] и this.setStates[значение стейта] соответственно. В итоге в любом методе класса появляется возможность чтения и записи стейта по его имени.
Что важно, этим тоже пользоваться не обязательно. Вы вполне можете вызывать обычный React.useState и класть его результаты в this. Просто использование this.useState может быть удобнее.
В общем, это пока все что я сделал. Результат, на мой взгляд, взял лучшее из обеих миров. Часто ли это придется использовать, пока не знаю. Но мне кажется, это мне пригодится.