Pull to refresh
15
11.1
Константин Роман@nihil-pro

User

Send message

иногда проще полностью пересоздать заново группу элементов

React way

рекомендуют все-таки пересоздавать элементы, чтобы не засорять DOM

React way + очень странное утверждение. Каким образом изменение свойства уже созданного HTML элемента засоряет DOM?

проще все-таки обрабатывать изменения в одной-единственной функции

React way

На этом, собственно, и были построены классовые компоненты реакта

Который как вы выше сами признаете – неэффективен.

тогда уже рендерим (обновляем) - это довольно знакомый шаблон.

React way

Вы буквально пытаетесь воссоздать React, но в этом нет смысла. Нужно перестроить в голове ментальную модель.

elem.setAttribute("attr1", "val1"); elem.setAttribute("attr2", "val2"); elem.setAttribute("attr3", "val3");

Предположим, что мы это забатчим, что же мы получим? Ничего. Первый setAttribute меняет состояние элемента, и если это уместно, то произойдет какой-то painting или layouting. Потом второй, и процесс повторится. Потом третий. Какая разница, забатчили мы это или нет? Никакая. На любой вызов setAttribute, не важно откуда, браузер сделает свои дела, только в предлагаемом вами варианте мы добавляем дополнительную сложность без какого-либо профита.

В том же дейт-пикере нет особой необходимости хранить таблицы годов и месяцев в DOM и скрывать их через стили.

Никто так и не делает, разумеется. Там просто пара элементов в которых просто меняется контент.

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

Вы предлагаете создать состояние, и периодически синхронизовывать его с DOM. Это тот же React way. HTMLElement это уже состояние. Вызвав setAttribute или elem.prop = value мы как раз его изменяем, а браузер сделает свои дела, когда посчитает нужным.

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

При переключениях месяцев у нас перестраивается таблица с числами

Все еще обновление уже существующих элементов. То есть у нас есть таблица с днями, условные 6 рядов по 7 колонок, и при изменении месяца, изменяется textContent у некоторых ячеек, а у некоторых, дополнительно еще и атрибут disabled. В таком кейсе нет причин убить одни, уже существующие элементы что-бы создать набор таких же, только с другим текстом.

При этом может меняться атрибут с отображаемым месяцем и годом

Та же историю, только еще проще. Количество и названия месяцев не меняется от года к году.

Я правда не могу представить ситуацию, когда было бы нужно убить одну разметку и создать новую, и мне еще не приходилось реализовывать что-то вроде метода render в своих компонентах, но даже если представить что такое потребуется, то все равно нет смысла в усложнении. Не нужно недооценивать производительность DOM API, это не React. Вот пример с комбобоксом из статьи: stackblitz, тут в нем 5000 опций с jsonplaceholder, при чем опции изначально рендерит React, и тем не менее, он работает без каких либо лагов, причем в песочнице, хотя в нем нет никаких специальных оптимизаций, и тем более виртуализации. Попробуйте что-то поискать, выбрать пару значений и т.д., а после, чтобы совсем отсеять любые сомнения поставьте атрибут selected всем опциям:

return (
  <box-option 
    selected // !
  >
    {todo.title}
  </box-option>
);

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

Превосходная статья. 

Спасибо, приятно слышать.

Не рассматривали ли Вы такой алгоритм.Создаем общую функцию рендера, в которой мы сравниваем значения атрибутов, и если изменились, только тогда меняем часть DOM.

Нет. Тут дело видимо в некорректных ассоциациях. Веб-компонент, это не реакт компонент, ему не нужно «ререндерится». Более того, сам ререндер в каком-то реакте тоже вводит в заблуждение. В реальности (на примере реакта) все устроено так: рендерится компонент, элементы вставляются в DOM, последующие ререндеры это изменение свойств/атрибутов уже вставленных в DOM элементов.

Тоже с веб-компонентом. То, что вы подразумеваете под рендером это вставка в DOM, и это происходит один раз – element.shaddowRot.innerHtml = someHtml. Это все. Изменение атрибута это просто изменение атрибута. При использовании с тем же реактом, это означает что произошли какие-то изменения, реакт ререндерит то, что у него называется компонентом, что в действительности приводит к вызову setAttribute у нашего элемента (и у других в разметке).

А в attributeChangeCallback мы добавляем батчинг операций с помощью очереди микрозадач. Что-то такое:

Это ничего не даст кроме дополнительной сложности или потребления памяти.

2) насколько легко использовать adoptedStyleSheets при разработке?

Никаких проблем. В нем хранятся стили перенесенные из lightDom, то есть их можно смотреть там. При дебаге (по крайней мере в хроме) adoptedStyleSheets доступны для просмотра.

Не рассматривали ли использование какого-либо статического свойства

Конечно, это упомянуто в статье: loadCssFromUrls и loadCssFromDocumentStyleSheets. Первый как раз вставит импорты соответствующих ссылок во внутренний <style>.

Зачем в статье про векторный поиск в postgresql, приводить код реализации crud на питоне? Канале это имеет отношение к теме?

Они уже давно это сделали)

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

WeakRef здесь больше как подстраховка

Какой-то странный способ программировать. Вы или понимаете что код делает, или нет. Какая может быть подстраховка, если ясно что ссылку на объект держит Set, и пока он держит GC этот объект не очистит?

чем незначительное влияние на производительность

Очень даже значительное.

победили утечки памяти

export type EffectCb = (() => void) & {
  status: "active" | "inactive";
  children?: Set<EffectCb>;
  parent?: WeakRef<EffectCb>;
  cleanupSet?: Set<() => void>;
  component?: WeakRef<HTMLElement>;
  destroy?: () => void;
};

Слишком дорогой ценой.

1) WeakRef дороги сами по себе
2) WeakRef немного тормозят GC
3) WeakRef немного тормозят event loop

Кроме того:

дочерний добавляется в children родителя, а ссылка на родителя хранится через WeakRef

Тут children просто Set, а не WeakSet, наличие в нем ссылки на EffectCb препятствует очистки WeakRef содержащее этот же EffectCb, а значит сама упаковка в WeakRef не имеет смысла.

Куда проще и дешевле была бы реализация через back-pointer.

А в ответ: хотите напишу вам драфт статьи, для публикации на таких популярных площадках как хабр или дев то?

Thread.sleep просто таймер, он не задействует ресурсы ОС, а рест это I/O, и независимо от типа потока (реальный, виртуальный), такая операция жрет ресурсы. При отправки запроса происходит примерно следующее:
– устанавливается TCP-соединение, которое занимает один порт и один файловый дескриптор
– отправляется запрос
– сокет переходит в состояние ожидания, а ось активно отслеживает его, ожидая ответа
то есть, железо занято реальной работой. Это не то же что таймер.

Кроме того, количество портов и дескрипторов ограничено. В статье речь про обработку 100 000 запросов, а это 100 000 открытых сокет соединений, что невозможно, потому что портов всего около 65 тысяч, а для соединений вообще выделяется пул из примерно 28 000. Но и это еще не все, так как после закрытия TCP соединения порт блокируется по TIME_WAIT (линукс 60 сек, про другие ос не знаю).

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

Если заменить sleep чем-то реальным, например обращению к бд, или рест запросом к другому приложению, то картинка резко перестанет быть настолько радужной.

Нет защищенных полей. Без них бывает туго, если приватное пле в базовом классе надо использовать в наследнике

Не согласен на счет «недоклассов».

В js, действительно, нет protected, но все же такое поведение можно получить, например так:

class A {
    #private = 1;
    
    get protected() {
        return this.#private;
    }
}

class B extends A {

}

console.log(new B().protected) // 1;

или еще такой вариант:

class A {
    #private = 1;
    
    static getProtectedValue(instance: A) {
        return instance.#private;
    }
    
    static setProtectedValue(instance: A, newValue: any) {
        instance.#private = newValue;
    }
}

class B extends A { };
const instance = new B();
console.log(A.getProtectedValue(instance)); // 1;

A.setProtectedValue(instance, 2);
console.log(A.getProtectedValue(instance)); // 2;

// Или так
class C extends A {
    doWork() {
        return A.getProtectedValue(this);
    }
}

console.log(new C().doWork()); // 1;

Более того, так как конструкторы также наследуют свойства, то можно и так:

// ... объявление класса А как и выше

class B extends A { };
const instance = new B();

// тут уже обращение через B
console.log(B.getProtectedValue(instance)); // 1;

B.setProtectedValue(instance, 2);
console.log(B.getProtectedValue(instance)); // 2;


class C extends A {
  doWork() {
    // а тут через C
    return C.getProtectedValue(this);
  }
}

console.log(new C().doWork()); // 1;

Ну что вы неудобные вопросы то задаете? Автор пост написал что бы за час получить плюсики от коллег и забыть, а что там с содержанием не так уж и важно))

Да ну что ты! Дэн Абрамов же сказал что классы это плохо! Смерись. И не забивай, что для многих использование классов = классовые компоненты в реакте

А про классы у меня есть отличная статья, пора их уже наконец выкинуть из JS/TS

Написали бы вначале статьи это, мы бы не тратили время на прочтение, сразу бы все поняли.

А вы, стесняюсь спросить, в курсе, что все браузерное API + все встроенное в сам язык API построено на классах?

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

Спорное утверждение, вот ваш переписанный пример на неизвестной вам «либе»:

const state = makeObservable({
  title: '',
  count: 0,
  data: [],
  pending: false,
  fetchItems() {
    this.pending = true;
    sleep().then(() => {
      this.data = ['hello', 'world'];
      this.pending = false;
    });
  },
  increment() {
    ++this.count;
  },
  decrement() {
    --this.count;
  },
  onChange(event) {
    this.title = event.target.value;
  },
});

export const App = observer(() => (
  <div>
    <h1>Hello World: {state.title}</h1>
    <div>Counter: {state.count}</div>
    <div>
      <input value={state.title} onChange={state.onChange} />
    </div>
    <div>
      <button onClick={state.increment}>incr</button>
      <button onClick={state.decrement}>decr</button>
      <button onClick={state.fetchItems}>fetch items</button>
    </div>
    {!!state.pending && <div>fetching data...</div>}
    <div>items: {state.data.join(',')}</div>
  </div>
));

Как видно, используется всего две функции makeObservable и observer. Во что тут нужно вникать? С чем тут нужно разбираться? Потыкать вживую можно в песочнице.

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

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

class State extends Observable {
  title = '';
  count = 0;
  data = [];
  pending = false;

  async fetchItems() {
    this.pending = true;
    await sleep();
    this.data = ['hello', 'world'];
    this.pending = false;
  }

  increment() {
    ++this.count;
  }

  decrement() {
    --this.count;
  }

  onChange(event) {
    this.title = event.target.value;
  }
}

export const App = observer(({ state }: { state: State }) => (
  // разметка
));

// ...
<App state={new State} />     

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

1
23 ...

Information

Rating
643-rd
Registered
Activity