Как стать автором
Обновить

Комментарии 47

Вот это жесть, полная) Открою вам великую тайну, есть такая штука, называется MobX. Так вот, она решит все ваши проблемы

В MobX надо оборачивать данные классом и делать его oservable. Это даже многословнее хуков.

Вместо тысячи слов просто сравните код на хуках и с MobX'ом.
https://codesandbox.io/s/boring-frost-n3c02z?file=/src/App.tsx

Просто посмотрите на разницу в читаемости, в удобстве написания кода, в кол-вах строк и символах. По всем фронтам лидирует MobX. Про оптимизацию лишних рендеров при использовании MobX из коробки я вообще молчу и про то, что с MobX'ом можно все передавать по ссылке и читать/вызывать свойства и метода абсолютно из любых мест.

Вместо тысячи байт..

class TestWire {

  @mem list( next = [] ) { return next }
  
  @act add() {
    this.list([ this.list().length ])
  }

}

class TestApp extends Component< TestApp > {
  @mem test() { return new TestWire }
  compose() { return <>...</> }
}
class TestMobX {

  list = []
  
  add = ()=> {
    this.list = [ this.list.length ]
  }

  constructor() {
      makeAutoObservable( this, {
        list: observable.struct
      } )
  }

}

const TestApp = observer( ()=> {
    const[ state ]= useState( ()=> new TestMobX() )
    return <>...</>
} )
function useTestHook() {

  const[ list, setList ]= useState( [] )

  const add = ()=> {
    const next = [ list.length ]
    if( !equals( list, next ) ) setList( next )
  }

  return { list, add }

}

const TestApp = ()=> {
    const state = useTestHook()
    return <>...</>
}

По моему, так гораздо проще и производительнее ;)

function CounterButton() {
  let count = 0;
  const handleClick = () => {
    count++;
    btn.update();
  };
  const btn = button(
    { click$e: handleClick }, // props
    () => `You clicked ${count} times`, // child
  );
  return btn;
}

По моему, так гораздо проще и производительнее ;)

Лол, каждый раз в ручную обновлять компоненты при изменениях, удачи)) Ну и ваш пример конечно компонентом с большой натяжкой можно назвать, т.к. это максимально не дружелюбный код в отличии от реактовских компонентов с JSX'Ом.

В Реакте вы также вручную обновляете компонент, JSX или "функциональная нотация" мало чем отличаются.

В Реакте вы также вручную обновляете компонент, JSX или "функциональная нотация" мало чем отличаются.

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

Когда вы задаете новое значение в своем стейте, будь он в MobX или же в setState. Вы вызовом этой функции тригеррите обновление. То-есть вызываете обновление вручную.

Когда вы задаете новое значение в своем стейте, будь он в MobX или же в setState. Вы вызовом этой функции тригеррите обновление. То-есть вызываете обновление вручную.

Есть принципиальная разница, межу вызовом его так(в случае с MobX):

this.items = response.items;

Или множеством строк и файлов, в которых описаны редьюсеры/эффекты когда мы используем тот же redux или rtk или любую другую шляпу. Даже в виде кода сюда это вставлять не хочу, ибо руки в этом марать даже смысла нет, и так всё очевидно любому школьнику.

Начинаю тоже использовать mobx в своих проектах вместо redux, в целом все нравится, единственное, немного напрягает необходимость все оборачивать в observer вручную. Не встречался ли какой-нибудь способ все это автоматизировать?

Не встречался ли какой-нибудь способ все это автоматизировать?


Я написал себе плагин для Vite который автоматом все компоненты заворачивает в observer, но можно и под Webpack адаптировать.

Вот код
import { Plugin } from 'vite';
import * as path from 'path';
import '@babel/core';
import * as babelParser from '@babel/parser';
import generate from '@babel/generator';
import { AssignmentExpression, CallExpression, ExpressionStatement, Identifier } from '@babel/types';


const projectRoot = path.resolve(__dirname, '../../');
const srcRoot = path.resolve(projectRoot, 'src');

const mobxImportAst = babelParser.parse(`import { observer } from 'mobx-react-lite';`, { sourceType: 'module' }).program.body[0];
const wrapFnASt = babelParser.parse(`Name = observer(Name)`, { sourceType: 'module' }).program.body[0];

// https://astexplorer.net/
export function wrapInToObserver(mode: string) {
    return [
        {
            name: 'vite:wrap-into-observer:pre',
            enforce: 'pre',
            transform(src, id) {
                if (path.normalize(id).indexOf(srcRoot) !== 0) return false;
                const ext = path.extname(id);
                if (ext !== '.tsx') return false;

                const node = babelParser.parse(src, {
                    sourceType: 'module',
                    plugins: [
                        'jsx',
                        'typescript',
                        'decorators-legacy',
                    ],
                });

                let codeChanged = false;

                const appendStatements = [];

                let mobxReactImported = false;

                for (const statement of node.program.body) {
                    let fnBody = null;
                    let s = statement;

                    // Проверка импортирован ли mobx-react-lite
                    if (statement.type === 'ImportDeclaration') {
                        if (statement.source.value === 'mobx-react-lite') {
                            for (const spec of statement.specifiers) {
                                if (spec.type === 'ImportSpecifier') {
                                    if (spec.local.name === 'observer') {
                                        mobxReactImported = true;
                                    }
                                }
                            }
                        }
                    }

                    // Если export
                    if (statement.type === 'ExportNamedDeclaration') {
                        s = statement.declaration;
                    }

                    if (s.type === 'FunctionDeclaration') {
                        fnBody = s.body.body;
                    }
                    else if (s.type === 'VariableDeclaration') {
                        const init = s.declarations[0].init;
                        if (init.type === 'ArrowFunctionExpression') {
                            fnBody = init.body;
                            if (fnBody.body) {
                                fnBody = fnBody.body;
                            }
                        }
                    }
                    else if (s.type === 'ExpressionStatement') {
                        if (s.expression.type === 'AssignmentExpression' && (s.expression.right.type === 'ArrowFunctionExpression' || s.expression.right.type === 'FunctionExpression')) {
                            fnBody = s.expression.right.body;
                        } else {
                            continue;
                        }
                    }
                    else {
                        continue;
                    }

                    if (fnBody) {
                        let isReactComponent = false;
                        recursiveObj(fnBody, (obj) => {
                            if (['JSXElement', 'JSXFragment'].includes(obj.type)) {
                                isReactComponent = true;
                            }
                        });

                        if (isReactComponent) {
                            codeChanged = true;

                            if (s.type === 'VariableDeclaration') {
                                const observerWrapper: CallExpression = {
                                    type: 'CallExpression',
                                    callee: {
                                        type: 'Identifier',
                                        name: 'observer',
                                    },
                                    arguments: [s.declarations[0].init],
                                }

                                s.declarations[0].init = observerWrapper;
                            } else if (s.type === 'FunctionDeclaration') {
                                appendStatements.push(statement);
                            } else if (s.type === 'ExpressionStatement') {
                                const exp = s.expression as AssignmentExpression;

                                const observerWrapper: CallExpression = {
                                    type: 'CallExpression',
                                    callee: {
                                        type: 'Identifier',
                                        name: 'observer',
                                    },
                                    arguments: [exp.right],
                                }

                                exp.right = observerWrapper;
                            }
                        }
                    }
                }


                if (codeChanged) {
                    const newBody = [];

                    if (!mobxReactImported) {
                        newBody.push(mobxImportAst);
                    }

                    for (const statement of node.program.body) {
                        if (appendStatements.includes(statement)) {
                            newBody.push(statement);
                            let s = statement;
                            if (statement.type === 'ExportNamedDeclaration') {
                                s = statement.declaration;
                            }
                            if (s.type === 'FunctionDeclaration') {
                                const fnName = s.id.name;
                                const st: ExpressionStatement = JSON.parse(JSON.stringify(wrapFnASt));
                                const expression: AssignmentExpression = st.expression as any;
                                (expression.left as Identifier).name = fnName;
                                ((expression.right as CallExpression).arguments[0] as Identifier).name = fnName;
                                newBody.push(st); // FnName = observer(FnName);
                            }
                        } else {
                            newBody.push(statement);
                        }
                    }
                    node.program.body = newBody;
                }


                if (codeChanged) {
                    const ast = {
                        type: 'Program',
                        body: node.program.body,
                    };
                    const code = generate(ast).code;
                    //console.log(code);
                    return code;
                }
            },
        } as Plugin,
    ];
}

function recursiveObj(target, callback) {
    function eachRecursive(obj) {
        for (let k in obj) {
            if (typeof obj[k] == 'object' && obj[k] !== null) {
                callback(obj[k]);
                eachRecursive(obj[k]);
            } else {

            }
        }
    }

    eachRecursive(target);
}

Спасибо, посмотрю

А ещё есть Vue. Полностью избавляет от проблем, включая бессонницу и почечных камней.

Я не могу понять чем может нравиться JSX? Это же просто ужас, месиво из js и html.

PHP изобретали тоже для месива с HTML. Потом вдруг одумались и стали называть такой код "лапшой".

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

Ну если человек хочет написать месиво, то отсутсвие JSX его вряд ли остановит)

Используйте JSX как шаблонизатор и будет добро)

Не могу понять, что может вызывать неприязнь в JSX? Этож просто вызовы createElement в краткой форме

Потому что это абсолютно нечитабельная хрень))
Мне-то и "идешной" подсветки синтаксиса в html порой мало, а тут вообще со строками работать... Уже одного этого достаточно чтобы предпочесть Vue.

Нечитабельность вы своими руками делаете, если все вычисления прям в JSX пишите. Директивы шаблонов vue - вот достойный кандидат на звание абсолютной хрени :)

вы никогда серьёзно не работали с вёрсткой, да?))

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

Господи, зачем усложнять себе жизнь директивами и xslt? Какое дальнейшее развитие? Вы ещё вспомните wml)

Любой шаблонизатор как во Vue, Angular, Svelte - это лишняя когнитивная нагрузка. У нас уже есть язык программирования Javascript. В нем уже есть if, else, while...

Мы всегда могли писать Javascript в HTML. В JSX же можно с помощью Javascript создавать и модифицировать DOM динамически. Чтобы не было месива, надо бизнес логику выносить из верстки.

Добавил поддержку JSX, все как вы просили. Хотя функциональная нотация более гибкая, обновил доки, почитайте.

Если правильно понял, библиотека затевалась в том числе для оптимизации перформанса, но не увидел в статье бенчмарков.

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

реактивный jsx на нативных веб-компонентах и без хуков? stenciljs.

Бегло пробежал по Stenciljs. Много страниц документации, много правил, декораторы, механизмы свойств и состояния. Во Fusor это просто Javascript переменные и функции, всё! Отсюда и одна страница с документацией. Вэб-компонет используется для жизненного цикла, дальше ваше дело, если хотите их использовать.

А пока пятилетние Реакт-разработчики опять мучаются с рендерингом простой кнопки очередным экзотическим способом, студентики на $mol пришли и самоубиваются в 50 строк кода делают оффлайн-фёст коллаборативный редактор текста с синхронизацией в реальном времени через децентрализованную БД.

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

Ликбез по маркетингу для программистов: хорошая PR акция - это воодушевление целевой аудитории и отсутствие равнодушия у не целевой.

Вы не React улучшили, а создали нечто сомнительное. Ожидал какие-то предложения, которые вы хотите передать команде react

Я прочитал комментарии и нашел, что могу еще сказать о Solid JS

Смотрите, вы хотите избежать лишнего создания объектов. Это максимизировано в SolidJS и в Svelte.

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

Svelte использует тот же принцип, но с ним я работал очень давно. Когда работал - в нем JSX не было.

Но JSX - это такой слой концепции, который по идее можно прикрутить к чему угодно, я прикручивал его даже к игровому фреймворку Phaser (кастомный jSX - это по сути описание неких объектов/конфигураций в формате, приближенному к XML, после трансформации jSX превращается в набор объектов, с которыми вы дальше можете делать что угодно).

Поэтому я не удивлюсь, если узнаю, что JSX/TSX завезли и в Svelte.

Чем интересен ваш путь - тем, что вы пытаетесь выйти за рамки концепции, оставаясь внутри фреймворка.

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

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




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

если есть ссылка на issues, кидайте, чтобы люди были в курсе
в продакшен Solid JS не заряжали пока

В SolidJS используются теже хуки, только теперь они называются Signal. Также соединена логика изменения стейта и обновления что не гуд (смотри single-responsibility principle). Fusor должен по производительности не уступать Svelte. И там где Svelte делает компиляцию не известно как, в Fuser это видно в коде. Потому как там Javascript переменные и функции.

это не те же хуки, извините. Они могут стать хуками, когда, и я это видел, их зачем-то начинают тащить в Реакт и там заворачивают в хуки

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

createSignal в Solid - это хитрожопый функциональный вариант шаблона Observable, он обсуждался здесь - https://habr.com/ru/companies/timeweb/articles/725504/

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

Да, разумеется они имплементированы по разному. Я исходил из контекста этой статьи, что Хуки/Сигналы схожи в том, что это обертки для простейших операций, которые могут быть заменены на установку переменной let x = 0 и вызов функции () => x, как сделано в Fuser. А также они схожи в "хитрой" логике имплементации, которая будет влиять на производительность. Также схожи в том что смешивают два действия: установку стейта и обновление.

Кстати да, пришло осознание что реакту (компонентам/хукам) нужен контекст. Вне его они работать не будут. Фьюзору контекст не нужен. Ему нужен только ДОМ. Поэтому его функции/компоненты очень гибкие, их можно использовать в любом контексте.

Добавил поддержку JSX. Улучшил документацию.

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

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

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

хук - это очень просто

Хук - это грязная функция, которая работает со скрытыми глобальными объектами React, чтобы придать видимость чистоты в момент вызова ))

Потому как Реак самый логичный и простой из всего что было, до появления Фьюзора лол...

Обновил его доки, добавил JSX

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации