Привет, друзья!


Представляю вашему вниманию перевод этой статьи, вызывавшей определенный резонанс в сообществе React-разработчиков.


Дорогой React, мы встречаемся уже почти 10 лет. Мы прошли долгий путь вместе. Но ситуация вышла из-под контроля. Нам нужно поговорить.


Ты был единственным


Ты у меня не первый. До тебя у меня были длительные отношения с jQuery, Backbone и Angular. Я знал, чего следует ожидать от фреймворка: лучший пользовательский интерфейс, продуктивность и опыт разработки, но также фрустрация от необходимости изменения способа написания кода в угоду парадигме фреймворка.


Когда я тебя встретил, я находился в длительных отношениях с Angular. Я был измотан watch и digest, не говоря уже о scope. Я искал то, что не заставляло бы меня чувствовать себя несчастным.


Это была любовь с первого взгляда. Твое однонаправленное связывание данных было таким освежающим по сравнению с тем, что я знал. Целая категория проблем с синхронизацией данных и производительностью для тебя просто не существовала. Ты был чистым JavaScript, а не его жалким подобием, представленным строкой в элементе HTML. У тебя была вещь под названием "декларативный компонент", которая была настолько прекрасной, что все на тебя засматривались.





Понять тебя было не просто. Мне пришлось поработать над своими привычками кодирования, чтобы поладить с тобой, но это того стоило! Поначалу я был так счастлив с тобой, что все время рассказывал о тебе окружающим.


Герои новых форм


Странности начались, когда я попросил тебя обработать отправку формы. На чистом JS с формами и инпутами работать сложно, на React — еще сложнее.


Сначала разработчик должен выбрать между управляемыми и неуправляемыми инпутами. Оба подхода имеют свои недостатки и баги в пограничных ситуациях. Но почему я должен делать такой выбор? Два различных подхода — это слишком много.


"Рекомендуемым" подходом являются управляемые компоненты, которые очень многословны. Для того, чтобы отправить форму, требуется написать такой код:


import React, { useState } from 'react';

export default () => {
    const [a, setA] = useState(1);
    const [b, setB] = useState(2);

    function handleChangeA(event) {
        setA(+event.target.value);
    }

    function handleChangeB(event) {
        setB(+event.target.value);
    }

    return (
        <div>
            <input type="number" value={a} onChange={handleChangeA} />
            <input type="number" value={b} onChange={handleChangeB} />

            <p>
                {a} + {b} = {a + b}
            </p>
        </div>
    );
};

И если бы существовало только два подхода, я был бы счастлив. Но и-за огромного количества кода, который требуется для разработки реальной формы с дефолтными значениями, валидацией, зависимыми инпутами и сообщениями об ошибках, я вынужден использовать сторонние библиотеки для работы с формами. Каждая из них имеет свои недостатки:


  • Redux-form была естественным выбором при использовании Redux, но однажды ее ведущий разработчик покинул проект, чтобы заняться разработкой
  • React-final-form, которая содержала большое количество багов и… ведущий разработчик снова ушел. После этого я стал использовать
  • Formik, популярное, но тяжелое решение, медленное для больших форм с ограниченным функционалом. Поэтому я решил использовать
  • React-hook-form, которая является быстрой, но содержит скрытые баги и имеет документацию, похожую на лабиринт.

После многих лет работы с формами в React, я все еще не знаю, как делать это правильно. Когда я смотрю на то, как работает с формами Svelte, я начинаю чувствовать, что в React применяется неправильная абстракция. Взгляните на этот код:


<script>
    let a = 1;
    let b = 2;
</script>

<input type="number" bind:value={a}>
<input type="number" bind:value={b}>

<p>{a} + {b} = {a + b}</p>

Ты слишком чувствителен к контексту


Вскоре после нашего знакомства, ты представил мне своего щенка Redux. Ты не мог никуда без него пойти. Поначалу я не возражал, поскольку это было очень мило. Но вскоре я осознал, что вокруг него вращается твой мир. Кроме того, это сильно усложняло разработку фреймворка — другие разработчики не могли легко настраивать существующие редукторы.


Ты тоже это заметил и решил избавиться от Redux в пользу собственного useContext. Но в useContext отсутствует критически важная особенность Redux — возможность реагировать на изменения только части контекста. Приведенные ниже строки кода не эквиваленты с точки зрения производительности:


// Redux
const name = useSelector(state => state.user.name);
// контекст React
const { name } = useContext(UserContext);

В первом случае компонент будет подвергнут повторному рендерингу только при изменении имени. Во втором — при изменении любых данных пользователя. Это очень важно. Доходит до того, что приходится разделять контекст на части для предотвращения повторного рендеринга:


// Это безумие, но по-другому никак
export const CoreAdminContext = (props) => {
    const {
        authProvider,
        basename,
        dataProvider,
        i18nProvider,
        store,
        children,
        history,
        queryClient,
    } = props;

    return (
        <AuthContext.Provider value={authProvider}>
            <DataProviderContext.Provider value={dataProvider}>
                <StoreContextProvider value={store}>
                    <QueryClientProvider client={queryClient}>
                        <AdminRouter history={history} basename={basename}>
                            <I18nContextProvider value={i18nProvider}>
                                <NotificationContextProvider>
                                    <ResourceDefinitionContextProvider>
                                        {children}
                                    </ResourceDefinitionContextProvider>
                                </NotificationContextProvider>
                            </I18nContextProvider>
                        </AdminRouter>
                    </QueryClientProvider>
                </StoreContextProvider>
            </DataProviderContext.Provider>
        </AuthContext.Provider>
    );
};




В подавляющем большинстве случаев причиной проблем с производительностью является огромный контекст, поэтому у меня нет другого выбора, кроме как разделять его на части.


Я не хочу использовать useMemo() и useCallback(). Лишние ререндеринги — это твоя проблема, а не моя. Но ты заставляешь меня делать это. Посмотри, как мне приходится разрабатывать простую форму, чтобы она работала достаточно быстро:


// Источник: https://react-hook-form.com/advanced-usage/#FormProviderPerformance
const NestedInput = memo(
    ({ register, formState: { isDirty } }) => (
        <div>
            <input {...register('test')} />
            {isDirty && <p>This field is dirty</p>}
        </div>
    ),
    (prevProps, nextProps) =>
        prevProps.formState.isDirty === nextProps.formState.isDirty,
);

export const NestedInputContainer = ({ children }) => {
    const methods = useFormContext();

    return <NestedInput {...methods} />;
};

Прошло почти 10 лет, а проблема остается. Насколько сложно разработать useContextSelector()?


Разумеется, ты знаешь об этом. Но ты занимаешься чем угодно, кроме этого, хотя, вероятно, это твое самое узкое место с точки зрения производительности.


Мне не нужно ничего из этого


Ты объяснил мне, что я не должен обращаться к узлам DOM напрямую. Я никогда не считал DOM грязным (dirty), но поскольку тебя это тревожило, я перестал это делать. Теперь я использую refs (ссылки), как ты меня просил.


Но эти ссылки распространяются как вирус. В большинстве случаев при использовании ссылок компонентом, он передает их потомкам. Если потомком является компонент React, он должен перенаправить (передать) ссылку (forward ref) другому компоненту и т.д. до тех пор, пока один из компонентов, наконец, не отрендерит соответствующий элемент HTML.


Перенаправление ссылок могло бы выглядеть так:


const MyComponent = (props) => <div ref={props.ref}>Hello, {props.name}!</div>;

Но это было бы слишком просто. Вместо этого следует использовать (в оригинале abomination — мерзость) React.forwardRef:


const MyComponent = React.forwardRef((props, ref) => (
    <div ref={ref}>Hello, {props.name}!</div>
));

В чем проблема? — спросишь ты. В том, что forwardRef() (в случае с TypeScript) не позволяет создавать общие (generic) компоненты:


// Как мне передать ссылку в этом случае?
const MyComponent = <T>(props: <ComponentProps<T>) => (
    <div ref={/* pass ref here */}>Hello, {props.name}!</div>
);

Более того, ты решил, что ссылки предназначены не только для узлов DOM, но также являются эквивалентом this в функциональных компонентах. Или, другими словами, ссылка — это "состояние, изменение которого не влечет повторный рендеринг". На мой взгляд, мне приходится использовать ссылки по той причине, что твой интерфейс useEffect() является слишком странным. Другими словами, refs — это решение созданной тобой же проблемы.


Эффект бабочки (в оригинале используется игра слов — the butterfly (use) effect)


У меня несколько вопросов к useEffect(). Я понимаю, что useEffect() — это элегантное решение, унификация обработки событий монтирования, размонтирования и обновления в одном интерфейсе. Но как такое можно считать прогрессом?


// колбек жизненного цикла
class MyComponent {
    componentWillUnmount: () => {
        // ...
    };
}

// useEffect
const MyComponent = () => {
    useEffect(() => {
        return () => {
            // ...
        };
    }, []);
};

}, []); — от одной этой строчки мне становится дурно.


Я вижу эти загадочные наборы каббалистических символов по всему моему коду. Более того, ты заставляешь меня следить за зависимостями моего кода, например:


// Меняем страницу при отсутствии данных
useEffect(() => {
    if (
        query.page <= 0 ||
        (!isFetching && query.page > 1 && data?.length === 0)
    ) {
        // Запрашиваем страницу, которой не существует, устанавливаем `page` в значение `1`
        queryModifiers.setPage(1);
        return;
    }
    if (total == null) {
        return;
    }
    const totalPages = Math.ceil(total / query.perPage) || 1;
    if (!isFetching && query.page > totalPages) {
        // Запрашиваем страницу за пределами диапазона, устанавливаем значение `page` в значение последней существующей страницы
        // Выполняется при удалении последнего элемента на последней странице
        queryModifiers.setPage(totalPages);
    }
}, [isFetching, query.page, query.perPage, data, queryModifiers, total]);

Видите последнюю строку? Я должен быть уверен, что включил все реактивные переменные в массив зависимостей. А мне казалось, что подсчет ссылок (reference counting) является встроенной возможностью всех языков программирования со сборщиком мусора. Но нет, я вынужден сам управлять зависимостями, поскольку ты не умеешь этого делать.





Часто одной из зависимостей является созданная мной функция. Поскольку ты не видишь разницы между переменной и функцией, мне приходится предотвращать повторный рендеринг с помощью useCallback(), который также требует наличия массива зависимостей:


const handleClick = useCallback(
    async event => {
        event.persist();
        const type =
            typeof rowClick === 'function'
                ? await rowClick(id, resource, record)
                : rowClick;
        if (type === false || type == null) {
            return;
        }
        if (['edit', 'show'].includes(type)) {
            navigate(createPath({ resource, id, type }));
            return;
        }
        if (type === 'expand') {
            handleToggleExpand(event);
            return;
        }
        if (type === 'toggleSelection') {
            handleToggleSelection(event);
            return;
        }
        navigate(type);
    },
    [
        // О, Боже, нет
        rowClick,
        id,
        resource,
        record,
        navigate,
        createPath,
        handleToggleExpand,
        handleToggleSelection,
    ],
);

Простой компонент с несколькими обработчиками событий и колбеками жизненного цикла становится кучей тарабарского кода благодаря необходимости управлять этим адом зависимостей. А все из-за твоего решения о том, что компонент может рендериться произвольное количество раз.


Например, если я хочу создать счетчик, значение которого увеличивается каждую секунду и при каждом нажатии пользователем кнопки, я должен сделать следующее:


function Counter() {
    const [count, setCount] = useState(0);

    const handleClick = useCallback(() => {
        setCount(count => count + 1);
    }, [setCount]);

    useEffect(() => {
        const id = setInterval(() => {
            setCount(count => count + 1);
        }, 1000);
        return () => clearInterval(id);
    }, [setCount]);

    useEffect(() => {
        console.log('Значение счетчика:', count);
    }, [count]);

    return <button onClick={handleClick}>Click Me</button>;
}

Если бы ты знал, как управлять зависимостями, я мог бы написать что-то вроде:


function Counter() {
    const [count, setCount] = createSignal(0);

    const handleClick = () => setCount(count() + 1);

    const timer = setInterval(() => setCount(count() + 1), 1000);

    onCleanup(() => clearInterval(timer));

    createEffect(() => {
        console.log('Значение счетчика:', count());
    });

    return <button onClick={handleClick}>Click Me</button>;
}

К слову, это валидный код Solid.js.





Наконец, мудрое использование useEffect() требует прочтения 53-страничного руководства. По-моему, это ужасно. Если для правильного применения инструмента необходимо изучить такой объем материала, не говорит ли это о том, что данный инструмент не очень хорошо спроектирован?


Прими решение, наконец


Ты пытался улучшить useEffect и представил мне useEvent, useInsertionEffect, useDeferredValue, useSyncExternalStore и другие уловки.


Они позволяют тебе выглядеть красиво:


function subscribe(callback) {
    window.addEventListener('online', callback);
    window.addEventListener('offline', callback);
    return () => {
        window.removeEventListener('online', callback);
        window.removeEventListener('offline', callback);
    };
}

function useOnlineStatus() {
    return useSyncExternalStore(
        subscribe, // React не будет выполнять повторную подписку при передаче одной и той же функции
        () => navigator.onLine, // Как получать значение на клиенте
        () => true, // Как получать значение на сервере
    );
}

Но, на мой взгляд, это помада на свинье. Если бы реактивные эффекты было легче использовать, другие хуки просто бы не потребовались.


Другими словами: тебе приходится расширять основной интерфейс все больше и больше. Для людей, вроде меня, поддерживающих огромные кодовые базы, это постоянная инфляция API является ночным кошмаром. Непрестанное увеличение косметики постоянно напоминает о том, что ты пытаешься скрыть.


Ты слишком строгий


Хуки — это отличная идея, но они имеют свою цену. И эта цена — правила использования хуков. Их нелегко запомнить, еще сложнее применять их на практике. Но они заставляют меня тратить на код гораздо больше времени, чем нужно.


Например, у меня есть "компонент-инспектор", который является перетаскиваемым. Пользователи также могут скрывать этот компонент. В скрытом состоянии ничего не рендерится. Поэтому мне бы хотелось осуществлять "ранний выход", чтобы не регистрировать обработчики для ничего:


const Inspector = ({ isVisible }) => {
    if (!isVisible) {
        // ранний выход
        return null;
    }
    useEffect(() => {
        // Добавляем обработчики
        return () => {
            // Удаляем обработчики
        };
    }, []);
    return <div>...</div>;
};

Но нет, согласно правилам useEffect и другие хуки не могут вызываться условно. Поэтому мне приходится добавлять условие раннего выхода во все эффекты в случае, когда проп isVisible имеет значение false:


const Inspector = ({ isVisible }) => {
    useEffect(() => {
        if (!isVisible) {
            return;
        }
        // Добавляем обработчики
        return () => {
            // Удаляем обработчики
        };
    }, [isVisible]);

    if (!isVisible) {
        // Выход не такой ранний, каким мог бы быть
        return null;
    }
    return <div>...</div>;
};

Как следствие, все эффекты будут иметь isVisible в качестве зависимости. Потенциально эти эффекты могут запускаться слишком часто, что повредит производительности. Я знаю, что я должен создать промежуточный компонент для предотвращения рендеринга чего-либо в случае, когда isVisible === false. Но почему я должен это делать? Это всего лишь один пример сложностей, возникающих в связи с необходимостью соблюдения правил использования хуков — существует много других примеров. В результате большАя часть моего кода направлена исключительно на удовлетворение правил.


Правила хуков — это следствие деталей реализации — реализации, выбранной тобой для хуков. Но так быть не должно.


Ты слишком старый


Ты появился в 2013 году и стараешься обеспечивать обратную совместимость новых фич так долго, как это только возможно. И я благодарен тебе за это — это одна из причин, по которой я смог разработать огромную кодовую базу с тобой. Но обеспечение обратной совместимости имеет свою цену: документация и многие ресурсы сообщества, в лучшем случае, устарели, в худшем — вводят в заблуждение.


Например, когда я ищу "Отслеживание позиции курсора в React" на "StackOverflow", первым результатом является решение, устаревшее еще 100 лет назад:


class ContextMenu extends React.Component {
    state = {
        visible: false,
    };

    render() {
        return (
            <canvas
                ref="canvas"
                className="DrawReflect"
                onMouseDown={this.startDrawing}
            />
        );
    }

    startDrawing(e) {
        console.log(
            e.clientX - e.target.offsetLeft,
            e.clientY - e.target.offsetTop,
        );
    }

    drawPen(cursorX, cursorY) {
        // Для отображение информации в подписи
        this.context.updateDrawInfo({
            cursorX: cursorX,
            cursorY: cursorY,
            drawingNow: true,
        });

        // Рисование
        const canvas = this.refs.canvas;
        const canvasContext = canvas.getContext('2d');
        canvasContext.beginPath();
        canvasContext.arc(
            cursorX,
            cursorY /* начальная позиция */,
            1 /* радиус */,
            0 /* начальный угол */,
            2 * Math.PI /* конечный угол */,
        );
        canvasContext.stroke();
    }
}




Когда я ищу npm-пакет для определенной фичи React, я, в основном, нахожу заброшенные пакеты со старым синтаксисом. Например, react-draggable. Это де-факто стандарт для реализации перетаскивания в React. У него много открытых вопросов и низкая активность разработки. Вероятно, старый синтаксис (классы) не привлекает новых участников (contributors).


Официальная документация по-прежнему рекомендует использовать componentDidMount и componentWillUnmount вместо useEffect. Команда разработчиков React работает над новой версией документации под названием Beta docs на протяжении последних двух лет. А воз и ныне там.


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


Семейное дело


Поначалу твой отец Facebook выглядел очень круто. Facebook хотел "Сблизить людей" — я в деле! Когда бы я ни приезжал к твоим родителям, я всегда встречал новых друзей.


Но затем все стало очень плохо. Твои родители участвовали в схеме манипулирования толпой. Они изобрели концепцию "Фейковых новостей". Они начали собирать обо всех информацию без их согласия. Посещать твоих родителей стало опасно — до такой степени, что несколько лет назад я удалил свой аккаунт Facebook.


Я понимаю, что дети не отвечают за поступки родителей. Но ты все еще живешь с ними. Они финансируют твою разработку. Они — твои крупнейшие пользователи. Ты зависишь от них. Если в один прекрасный день они поплатятся за свое поведение, ты пострадаешь вместе с ними.


Другие основные JS-фреймворки смогли освободиться от родителей. Они стали независимыми и присоединились к организации под названием OpenJS Foundation. Node.js, Electron, Webpack, Lodash, ESlint и даже Jest теперь финансируются коллективом компаний и частных лиц. Раз смогли они, ты тоже сможешь. Но ты этого не делаешь. Ты остаешься с родителями. Почему?





Это не я, это ты


У нас с тобой одинаковые цели в жизни — помогать разработчикам создавать лучший UI. Я делаю это с помощью React-admin. Поэтому я понимаю вызовы, с которыми ты сталкиваешься, и компромиссы, на которые ты вынужден идти. Твоя работа не из легких, и, вероятно, ты решаешь множество проблем, о которых я не имею ни малейшего понятия.


Но я все время стараюсь скрывать твои недостатки. Когда я говорю о тебе, я никогда не упоминаю указанных выше проблем — я притворяюсь, что мы — отличная пара, без облаков на горизонте. В react-admin я предоставляю интерфейс, который избавляет пользователей от необходимости прямого взаимодействия с тобой. И когда люди жалуются на react-admin, я делаю все возможное, чтобы решить их проблемы, хотя в большинстве случаев эти проблемы связаны с тобой. Будучи разработчиком фреймворка, я тоже нахожусь на передовой. Я сталкиваюсь с новыми проблемами одним из первых.


Я изучал другие фреймворки. Каждый из них имеет свои недостатки: Svelte — это не JavaScript, SolidJS содержит неприятные ловушки, например:


// это работает в `SolidJS`
const BlueText = props => <span style="color: blue">{props.text}</span>;

// а это нет
const BlueText = ({ text }) => <span style="color: blue">{text}</span>;

Но у них нет твоих недостатков. Недостатков, от которых мне иногда хочется плакать. Недостатков, которые заставляют меня искать другие решения.


Я не могу бросить тебя, детка


Проблема в том, что я не могу тебя бросить.


Во-первых, я люблю твоих друзей. MUI, Remix, react-query, react-testing-library, react-table… Когда я с этими парнями, я всегда делаю потрясающие вещи. Они делают меня лучшим разработчиком — они делают меня лучше как человека. Я не могу бросить тебя, не бросив их.


"Это экосистема, идиот".


Я не спорю, что у тебя лучшее сообщество и лучшие сторонние модули. Но, честно говоря, тот факт, что разработчики выбирают тебя не за твои качества, а за качества твоей экосистемы, вызывает жалость.


Во-вторых, я слишком много в тебя вложил. Я разработал огромную кодовую базу, которую нельзя перенести на другой фреймворк, не сойдя с ума. Я построил бизнес вокруг тебя, который позволяет мне стабильно разрабатывать открытое программное обеспечение.


Я завишу от тебя.


Позвони мне


Я был предельно честен с тобой. Теперь твой черед. Собираешься ли ты решать названные мной проблемы и, если собираешься, то когда? Что ты думаешь о разработчиках библиотек, вроде меня? Должен ли я забыть о тебе? Или мы должны остаться вместе и работать над нашими отношениями?


Что дальше? Скажи мне.







Комментарии (27)


  1. illogic
    13.10.2022 17:35
    +2

    Я когда смотрю на реакт -- сам диву даюсь почему он выжил? Думаю, если бы не ресурсы Facebook и их психологи которые знают как и что впаривать, наверное он бы так долго не продержался. Например, его нетипичность и как следствие некоторый порог входа может использовать когнитивную ловушку страха потерь. Именно нетипичность, какая-то переусложненность, когда можно было иначе и проще. Если точнее -- ненаследуемость прошлого опыта, ломания его об колено, чистое навязывание React way вопреки прошлому "UX". Чтобы тяжесть соскока ощущалась пропорционально инвестированному времени.


    1. Fen1kz
      13.10.2022 18:49
      +3

      Да потому что UI = f(x) это крутой подход. Куда уж проще-то?


      Или вот подход к тому что все это компонент. Если помните, в ангуляре все пришли к тому что все делать через директивы. А react это упростил до "все компонент". и это круто.


      В статье очень весомые и валидные аргументы, с ней я согласен — но вот ваш комментарий выглядит как просто нытье жкверелюба.


    1. DarthVictor
      13.10.2022 22:35
      +4

      С выходом React добрую половину конкурирующих фреймворков точно также по-быстрому сломали об колено и переделали на React-way. Начиная c AngularJS, у которого в 1.5 директивы неожиданно стали компонентами, при этом у них добавилось нормальное описание методов жизненного цикла, а контроллеры судя по документации стали бесполезны (они и до этого были, кстати, бесполезны, но не суть).

      После выхода React все новые UI фреймворки начали переходить на однонаправленный поток данных, интерфейс как функция состояния. И это не только SPA-фреймворков касается. Мобильные фреймворки взяли от React нормальное описание интерфейса на языке программирования, а не через шаблонизатор (который по факту тоже язык программирования, но другой и плохой).

      При этом упомянутые свойства React, это не какие-то его know-how. Они были по отдельности и до него. Описание интерфейса на самом языке программирования было например в Elm. Нормальное описание методов создание-изменения-удаление было например в D3.js. Но в React эти свойства кое-как совместили.

      В итоге проекты, которые начали одновременно с первыми проектами на React писать на AngularJS или тем более на Jquery+Backbone, быстро стали неподдерживаемым куском говна. А проекты на React удавалось хоть и со скрипом, но поддерживать. Так React и выжил (это если отвечать на вопрос из вашего первого предложения). И по этой же причине под него стали косить другие фреймворки.


      1. DirectoriX
        14.10.2022 05:18
        +1

        описание интерфейса на языке программирования
        Но ведь JSX не является валидным JS, а лишь его надстройкой, которая во время компиляции сборки превращает XML-like конструкции в валидный JS-код — то есть это всё же не сам язык.


  1. DmitryKazakov8
    13.10.2022 19:26
    +5

    Все, что в статье приведено - это проблемы React way, то есть когда простой фреймворк обрастает концептами и фичами, которые намного эффективнее решались бы сторонними решениями. Но никто на самом деле не навязывает их, можно использовать только нужное, для меня это:

    • Оформление компонентов в классы

    • 3 метода жизненного цикла - componentWillMount (для SSR+client), componentDidMount (только client) и componentWillUnmount. Я бы предпочел, чтобы они более семантично и короче назывались, но это не большая проблема

    • Доступ к context (некоему глобальному объекту) из любого компонента без дополнительных импортов

    • Грамотно сделанный HTML-in-JS шаблонизатор со скоупом функции и JS-in-HTML

    • (иногда) Рефы, чтобы не генерировать уникальные идентификаторы для определенных частей компонента

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


    1. kai3341
      14.10.2022 21:22
      -1

      К сожалению, фронт-энд эволюционирует не во имя здравого смысла, а во имя моды. И мой первый камень летит в хуки


      1. DmitryKazakov8
        14.10.2022 22:39
        +1

        Хуки вообще странный концепт, я с ними тоже не подружился. Ничего мне не нужно от того что они продают - а именно объединенный в один вызов useEffect маунт-размаунт и "переиспользуемость". Семантичные методы класса для жизненного цикла удобней и ssr поддерживают, а переиспользуемость легко и в классах реализуется. В остальном только проблемы от кода, превращающего в кашу из длинных грязных функций без явных слоев логики.


        1. kai3341
          15.10.2022 15:14

          Обобрямс. У меня также есть глупые вопросы "сколько раз исполняется код" к функциональным компонентам (ответ -- на каждый рендер создаются новые замыкания. А потом разводим руками на вопрос, почему всё так тормозит). Переиспользуемость же реализует ООП. Да, в JS плохо с множественным наследованием, что вынуждает заниматься инкостыляцией, но жить можно.


          1. DmitryKazakov8
            15.10.2022 17:01

            А где во фронте, если не секрет, вам пригодилось наследование? Мне лично - нигде, кроме как отнаследовать класс от реакта, чтобы появились методы жизненного цикла и в играх. В остальном никогда. Если нужна какая-то функция в нескольких компонентах то просто импортится из утилит и вызывается, если какая-то логика выборки переиспользуется - то делается геттер в хранилище.


            1. kai3341
              15.10.2022 18:26

              Самое банальное -- разделить render на renderWait, renderReady, renderEmpty и renderError. Скорее всего, что-то из этого вам достаточно реализовать единожды.

              Мне для моего пет-проекта для навигации в гетерогенной иерархической структуре требовалось реализовать несколько типов branch-node и leaf-node. Причём, естественно, поведение всех бранчей одинаково -- возможность дозапроса вложенных нод. Тут без множественного наследования вообще никак


              1. DmitryKazakov8
                15.10.2022 22:51

                У меня обычно в проектах разные интерфейсы для renderWait и renderEmpty, поэтому просто пишу get renderWait() { return <SomeMarkup /> } и в самом render() { if (isLoading) return this.renderWait; } . В целом тут только для очень специфических кейсов пригодится унификация и наследование, и если унифицировано - то лучше вынести на уровень выше, чтобы верхний компонент показывал соответствующую разметку, не раздувая нижние компоненты.

                А разные типы одного компонента с общим функционалом сделал бы через class BranchWrapper{ render() { return Children.clone(this.props.children, { getChildNode: () => logic; }) } } . Так что варианты есть. Хотя это как раз React way, по которому не очень хочется идти, ООП довольно сильно усложняет восприятие, на мой взгляд. А еще лучше - через контекст, чтобы совсем отвязаться от пути Реакта - class SomeBranch { handleButtonClick = () => this.context.getNodes(this.props.branchId) }. Думаю, так проще, чем наследовать, но у каждого свой путь)


  1. hardtop
    13.10.2022 22:28
    +1

    Старые технологии уходят в прошлое, новые пробивают себе дорогу. Ничего нового. Реакт много дал в отношении развития тогда. Сейчас новые ветра. Никто ж не плачет о jquery?


  1. DarthVictor
    13.10.2022 23:02
    +1

    Если кому-то такой код

    <script>
        let a = 1;
        let b = 2;
    </script>
    
    <input type="number" bind:value={a}>
    <input type="number" bind:value={b}>
    
    <p>{a} + {b} = {a + b}</p>

    кажется простым, то попробуйте предсказать что выведет такой код после заполнения поля

    <script>
        let a;
    </script>
    
    <input type="number" bind:value={a}>
    <p>{typeof a}</p>

    И такой (я поменял type у поля ввода с number на date)

    <script>
        let a;
    </script>
    
    <input type="date" bind:value={a}>
    <p>{typeof a}</p>
    Вы угадали!

    number и string


    1. grimalschi
      14.10.2022 07:37
      +2

      Конечно, а чего вы ожидали? input типа date в value тоже строку хранит - это нативное поведение поля в браузерах, фреймворк тут не причем.


      1. DarthVictor
        14.10.2022 10:48

        Тогда почему у первого number? У input любого типа value строку хранит. У меня не было вопросов если бы у обоих было string.


        1. DreamShaded
          14.10.2022 11:39

          спецификация HTML, скорее всего)

          Апдейт: а, не, строку ждёт, имплементирующую число с плавающей точкой


        1. grimalschi
          14.10.2022 15:38

          Да, действительно. Немного сахара, видимо, для чисел только


    1. Eugeeny
      14.10.2022 10:47
      -2

      Вы видимо пытались этим кого-то как-то поддеть, но увы, никто не понял, что вы пытались сказать.


  1. ua9msn
    13.10.2022 23:36
    -2

    JSX был важнейшим нововведением. (я щщетаю). Все остальное холивар. Но да, теперь мы поуши в этом реакте.


  1. nirom
    14.10.2022 10:39

    Ну так в итоге-то что? Какие альтернативы нам использовать?


    1. markelov69
      14.10.2022 11:38
      +6

      Альтернатив особо и нет. Только React + MobX + Typescript. И не надо упарываться по react way, реакт нужно использовать только как view слой, но никак не для управления состоянием. Управлять состоянием должен только MobX.


    1. Fragster
      14.10.2022 12:48
      +2

      Vue, Svelte, <sarkasm>$mol</sarkasm>


      1. markelov69
        14.10.2022 13:05

        Vue, Svelte

        Если противопоставить им голый React или React+Redux(или прочую схожую ерись), то да, реакт тут аутсайдер.

        Но если им противопоставить React + MobX + Typescript и использовать реакт по назначению(то есть как библиотеку для рендера), то тогда все координально меняется. По сути ты получаешь все преимущества JSX'a и компонентного подхода, полноценную типизацию, настоящую реактивность(спасибо MobX) и не борешься с управлением состоянием приложения и отдельных компонентов, потому что этим занимается тот, кто должен, т.е. MobX.


        1. Fragster
          14.10.2022 13:12
          +2

          В vue3 + pinia все хорошо с ts и типизацией. Да и "все преимущества jsx" для меня являются весьма противоречивыми. В любом случае vue в jsx умеет, а вот react в шаблоны vue - нет.


      1. p07a1330
        15.10.2022 13:42
        +3

        $mol

        Не поминайте всуе, сейчас же блин к нам прибегут и начнуть рассказывать, какой он прорывной...


  1. bagryancevm
    14.10.2022 16:30

    Классно написанная статья. Некий такой крик души )). Когда читаешь, начинаешь задумываться над темами вещами, что выделяет автор. Я думаю, что проблемы есть всегда и везде. Нет идеального инструмента, который бы удовлетворил разом всех заказчиков.


  1. maxzhurkin
    14.10.2022 19:56

    TL; DR: статья нытья, но почитать интересно

    я не слишком близок к фронтенду, не слишком искушён в разработке, но я уверен, что претензии из раздела Ты слишком старый звучат в точности так же для проектов, не утруждающих себя обратной совместимостью: старые материалы неактуальны