Comments 141
Почему так?
Сначала в React придумали написать HTML в файлах *.JS. Получилось, как всегда, трудночитаемо, хотя старались сделать максимум читабельности, как в Aphrodite. Следующим шагом был *.JSX. А CSS нормально уживался в JSX, потому что это — те же теги. Но когда появляется желание внести в инлайновые стили переменные (а оно появляется, и решают его через переменные Less/Sass/Stylus), появляется закономерно Aphrodite. Дальше смотрим, и видим, что что-то не то, как и ранее с HTML в JS. Дальше кто-то финансирует *.JSX, какая-то конторка, её стараются поругать, назвать костылями, неосиливателями своего же нативного кода (компоненты на *.JS). Но больше тех, кто молча уминает с удовлетворением.
И это — над React, не его часть. Вот, к примеру, есть плагин JSX для Vue.
Вероятно, можно было бы избежать такого количества хейта, назвав технологию не JSX, а HTMLX.
в него перестанут пихать бизнес-логику (идеально — исключительно функциональные stateless-компоненты, что достигается использованием redux или flux).
И куда ее тогда пихать, если компоненты — без логики, а редакс-слой в идеале тоже логики не содержит, только редьюс? Или редакс — только для приложений без логики?
Соответственно, бизнес-логика распределяется между действиями и контейнерами. Последние, как правило, содержат логику на уровне «взять
event.target.value
и использовать его как аргумент name
у действия changeName
». Если бизнес-логика простая (отправить несложный HTTP-запрос к REST API), то можно оставить ее в действиях (точнее, action creators). Если логика сложная, то можно вынести ее в отдельный класс типа PostService
, получив классическое Multitier-приложение (но ни Redux, ни React, тут уже ни при чем).В контексте связки React + Redux, есть «компоненты», которые не имеют ни состояния, ни бизнес-логики, а только принимают на вход свойства и выдают на выходе DOM. Есть «контейнеры», которые...
На самом деле компонентами называют и те, и те, а они уже делятся на Presentational и Container. Хотя, де-факто, это просто хипстерские названия для обычных View и толстого Controller'а.
Что я не понял — зачем вы в оригинальном комментарии упомянули о Редаксе, если такое разделение возможно и без него.
толстого Controller'аГде же он «толстый», если это просто инжектор зависимостей, занимающий, как правило, десяток строк? Реальной бизнес-логики в нем нет (а то, где она должна быть, вы решаете уже сами — я предлагаю классическое разделение на UI-, Service- и Domain-layer).
На практике, вы все-равно либо придете к Redux, либо напишете свой аналог
Либо использование MobX
class Hello {
public:
Hello() : value(6) {}
int GetValue() const { return value; }
private:
int value;
};
Формально, это как-бы «чистая функция», ведь она возвращает одинаковый результат для побайтово равных объектов (в данном случае, для всех, потому что
Hello::value
не меняется).const HelloValue = 6;
. *this
. Модификатор const
после названия метода сообщает компилятору, что this
ссылается на константу, т. е. создается контракт, согласно которому функция Hello::GetValue
не изменяет внутреннего состояния объекта.Это можно «притянуть» к тому, что для двух равных объектов метод всегда вернет одинаковый результат. Точно также, как какая-нибудь
int foo(int a) { return a + 1; }
вернет одинаковое значение для одинаковых a
.Но зачем тогда ООП?
А затем, что он не заставляет использовать чистоту там, где она не нужна, а нужен изменяемый стейт.
Класс со стейтом противоречит идее «чистоты».
Частично, ведь сам по себе он не чистый, но вы иногда можете использовать его чисто.
Чистый метод не привязан к инстансу, зачем его тогда держать в классе?
Почему вы мыслите столь ограничено? Почему чистый метод не привязан к инстансу?
Почему вы мыслите столь ограничено? Почему чистый метод не привязан к инстансу?Ну почему ограниченно? Я просто стараюсь минимизировать количество сущностей и абстракций. JS предоставляет модули, в которых я могу складывать все, что ни попадя, в том числе и эти чистые функции и константы. Зачем наворачивать вокруг них лишние сущности в виде классов?
Вы, скорей всего, ошибочно приняли мою точку зрения, я не против ООП в языках, не поддерживающих module/package-level сущности, кроме классов (привет java). Я просто не вижу смысла наворачивать классы.
Поинт про комбинирование подходов полностью поддерживаю.
Да, я понимаю, что вместо класса можно сделать структуру, содержащую данные и функции, которые этот объект обрабатывают как чисто, так и грязно. Получится старый добрый процедурный стиль конца 60-х. А со строгой типизацией это будет даже работать так же как в ООП только будет другая форма записи.
Как на меня, механика ООП просто поприятнее:
— не нужно импортировать эти функции, когда ты получаешь структуру в процедуру или объект в метод
— ide автоматически подсветит тебе все возможные варианты, когда ты напишешь
myItem.<alt-space>
— ты получаешь дополнительные удобные механизмы вроде полиформизма в ООП понимании этого слова
— Удобную функцию-конструктор
— Возможность использовать более короткие имена:
unit.move({ y: 1 })
// значительно приятнее, чем
moveUnit(unit, { y: 1 }})
// я уж молчу об
newWorld = { ...world, {
units: { ...world.units, [
[unitId]: {
...world.units[unitId],
y: world.units[unitId].y + 1
}
]}
}}
// Ну или если завернуть это в чистую функцию:
newWorld = { ...world, {
units: { ...world.units, [
[unitId]: moveUnit(world.units[unitId], { y: 1 })
]}
}}
// Или покороче:
newWorld = moveUnitInWorld(world, unit.id, { y: 1 })
Вы ведь не будете называть свою процедуру «move», ведь двигать можно что угодно?
Вы предлагаете отказываться от всего этого просто чтобы не писать классы и использовать процедурное программирование? Я понимаю, что можно, но ведь это просто менее удобно.
А если уж мы использует классы, т.к. на них удобнее писать изменяемое состояние (а есть очень мало программ, где изменяемое состояние не нужно, в подавляющем большинстве программ изменяемое состояние — это и есть основа всех бизнес-требований), то зачем плодить сущности и использовать процедуры?
Я, конечо понимаю, что если у вас не сложная логика, то можно отдать заботу о состоянии процедурной библиотеке и делать вид, что ты пользуешься ФП, но это подойдет только для чего-то уж очень простого, потому что сложность растет слишком быстро и вообще это противоестественно.
Я просто пишу код используя ООП и, при этом не отказывают от чистого кода, когда это облегчит поддержку приложения.
при этом не отказывают от чистого кодаМне кажется, у вас какой-то культ слова «чистый», потому что выражение «чистый код» — это даже близко не про «чистые функции» и «неизменяемые объекты».
И почему культ именно у меня, если я как раз говорю, что делать культ из чистоты — глупо и вредно. Какое-то странное у вас понимание термина «культ».
Я говорю о детермированности и отсутствии побочных эффектов. А вы?«Чистый код» — это красиво спроектированный код, противопоставление т. н. «спагетти-коду». Он может как использовать функциональный подход, так и не использовать его (изменяемое состояние, побочные эффекты, недетерминированность и т. д.)
И почему культ именно у меня, если я как раз говорю, что делать культ из чистоты — глупо и вредно.Ну вот, опять. Смиритесь с тем, что не все, что имеет в своем названии «чистый», связано с ФП :-) В данном случае было бы правильнее: «И почему культ именно у меня, если я как раз говорю, что делать культ из функциональщины — глупо и вредно».
что имеет в своем названии «чистый», связано с ФП
Конечно, чистота в смысле детермированности и отсутствии побочных эффектов (видите, как вы все усложнили, теперь приходится уточнять термин) — не обязательно показатель функциональности, но функциональность — обязательно чистота (все целые числа — числа, но не все числа — целые).
Чистота в смысле детермированности и отсутствии побочных эффектов возможна и в процедурной и в ОО парадигме. В определенных границах, об этом я выше и рассказывал.
делать культ из функциональщины — глупо и вредно
Конечно, это тоже вредно. Культы вообще это вредно. Но, как я уже сказал, не все то функционально, что чисто в смысле детермированности и отсутствии побочных эффектов.
И вот чистоту в смысле детермированности и отсутствии побочных эффектов, которая так популярна в ФП можно взять на вооружение и в ОО программировании для определенных задач. И этот код не обязательно будет функциональным.
An expression is said to be referentially transparent if it can be replaced with its corresponding value without changing the program's behavior
Но вот я не нашел ни одного упоминание, что термин «Чистота» можно применять исключительно к ФП. Аргументируйте или дайте пруфы, пожалуйста. Потому что пока это выглядит как «это ведь наше, ФПшное, не забирайте, пожалуйста»
Потому что пока это выглядит как «это ведь наше, ФПшное, не забирайте, пожалуйста»Напротив, я говорю, что «чистая функция» — это из ФП, все остальное, что имеет в своем названии прилагательное «чистый», к нему не относится. Потому что это вы упоминали выше «чистый метод», «чистый код» и т. д. в контексте ФП. Откройте поиск по странице и введите туда «чистый», сильно удивитесь.
Вы все еще не привели аргументов, почему общий термин программирования «Чистота» можно применять только в контексте ФП. Я понимаю, что есть также общий и синонимичный термин, но раз вы меня поняли, то функция языка общения выполнена корректно и вы просто придираетесь.
Он может как использовать функциональный подход, так и не использовать его (изменяемое состояние, побочные эффекты, недетерминированность и т. д.)
Еще раз вы почему-то отожествляете понятия ФП и остальные названные. Да, ФП — это неизменяемое состояние, отсутсвие побочных эффектов, детерминированность. Но
Неизменяемое состояние само по себе — не ФП
Отсутсвие побочных эффектов само по себе — не ФП
Детерминированность сама по себе — не ФП
Почему вы стараетесь меня убедить, что если какой-то код не изменяет состояние, не имеет побочных эффектов и детерминированный — обязательно ФП? Смотрите, такой логический вывод можно сделать из ваших слов:
Он может как использовать функциональный подход, так и не использовать его (изменяемое состояние, побочные эффекты, недетерминированность и т. д.)
Я запишу ваши слова псевдокодом:
if (ФП) {
...
} else {
изменяемое _состояние();
побочные_эффекты();
недетерминированность();
}
Простите, но я с этим совершенно не согласен.
А что мешает в ООП создавать чистые методы или даже классы, которые делают свою работу и умирают?Этот ваш комментарий вызвал недоумение не только у меня, потому что нет такого термина «чистый метод» или «чистый класс». То, что вы хотели сказать, называется «неизменяемый объект».
Я просто пишу код используя ООП и, при этом не отказывают от чистого кода, когда это облегчит поддержку приложения.Термин «чистый код» существует, но означает совершенно другое. То, что вы хотели сказать, называется «код, написанный в функциональном стиле». Аббревиатура ФП также была бы к месту.
То, что вы хотели сказать, называется «неизменяемый объект».
Нет, я не хотел сказать «неизменяемый объект». Вот пример:
var nVector = new Vector3(5, 3, 1).normalized
Вектор сделал свою работу и умер, но он не неизменяемый, зачем вы заставляете меня применять неправильные термины?
«код, написанный в функциональном стиле».
Нет, это код написанный в объектно-ориентированном стиле, а не функциональном, но он не имеет побочных эффектов, он детерминированный и он не изменяет состояние. Он чистый.
И вы всё-еще не привели аргументов для своего субъективного мнения, что слово «чистый» в контексте отсутствия побочных эффектов и детерминированности можно применять только для ФП.
Это «временный объект», в JS было бы логичнее привести более распространенный случайvar nVector = new Vector3(5, 3, 1).normalized
var ts = +new Date();
Нет, это код написанный в объектно-ориентированном стиле, а не функциональном, но он не имеет побочных эффектов, он детерминированный и он не изменяет состояние. Он чистый.Согласен, такого термина еще не придумали, потому что «stateless-объект» — оксюморон. Еще раз, «чистый код» — это термин, который другие люди стали использовать раньше вас. Есть также одноименная книга довольно известного автора.
в JS было бы логичнее привести более распространенный случай
Какой вы молодец, что придумали пример. Вот только он обращается к внешнему состоянию (ОС), следовательно не Pure, следовательно не равносилен моему примеру.
Термины уточняю, потому что у других комментаторов возникли проблемы с пониманием.
Кто? Вы меня поняли, raveclassic — тоже, сомневаюсь, что wheercool написал бы следующее, если бы понял меня в вашей странной манере, особенно учитывая, что «Чистый код» как раз об ООП.
Интересно каким образом?
И, скорее всего, в следующем сообщении вы просто неправильно расставили акценты:
Вы заявили то, что ООП поощряет чистоту, а не то что позволяет писать методы, к-ые не используют состояние.
Так кто и где меня неправильно понял?
Это «временный объект»,
А еще впомните о переменных, операторах, буквах, пикселях. Ок, это временный объект. А вот тоже временный объект:
getMoved(unit, { x: 1 })
. И что это характеризует?Я не понимаю, чего вы хотите добиться. Просто повторяя свое мнение, которое я считаю ошибочным — вы меня не переубедите.
Чистый («Pure», не «Clean») код — это код, который не имеет побочных эффектов и детерминированный. Чистой не обязана быть функция, им может быть и выражение:
Вы заставляете писать меня «Pure» вместо «Чистый»? Зачем? Чтобы меньше людей меня поняло? Зачем вы стараетесь нарушить работу коммуникативной функции языка?
только он обращается к внешнему состояниюСогласен, не удачный пример в контексте ФП, пытался придумать пример временному объекту.
И, скорее всего, в следующем сообщении вы просто неправильно расставили акценты:И, скорее всего, вы перепутали меня с другим комментатором.
А еще впомните о переменных, операторах, буквах, пикселях. Ок, это временный объект.Поверьте, я не придумываю слова на ходу, а обращаюсь к устоявшемуся понятию Temporary Objects (по ссылке C++, но относится и к другим языкам). И, таки да, временные (безымянные) переменные бывают (что вы имеете ввиду под временными пикселями, уже не совсем понятно — возможно, пиксели, которые затираются в процессе подготовки кадра).
Я не понимаю, чего вы хотите добиться.Теперь уже я сам не понимаю, тем более, что к CSS это не имеет никакого отношения. Но надеюсь, что другие читатели теперь смогут понять ваши комментарии благодаря моим усилиям.
Согласен, не удачный пример в контексте ФП, пытался придумать пример временному объекту.
В контексте чистоты. При чем тут ФП? Почему вы эти два куска кода называете ФП? Первый — классический ООП, пусть и чистый, второй — не имеет ни чистоты, ни декларативности.
var nVector = new Vector3(5, 3, 1).normalized
var ts = +new Date();
И, скорее всего, вы перепутали меня с другим комментатором.
Нет. Я имел ввиду, что вы, своим внутренним голосом неправильно расставили акценты в комментарии другого автора.
вы имеете ввиду под временными пикселями
А где я говорил о временных пикселях? Я говорил о просто пикселях, о просто переменных, о просто операторах и о других просто терминах, которые можно применить к моему примеру, которые будут корректны и которые, при этом, так само будут не соответствовать тому, что я хотел донести.
Но надеюсь, что другие читатели теперь смогут понять ваши комментарии благодаря моим усилиям.
Нет, благодаря вашим усилиям теперь сложно найти полезную информацию в этой ветке, потому что вы все залили никому не нужной водой.
var nVector = new Vector3(5, 3, 1).normalized
На всякий случай еще раз акцентирую, это чистое выражение (Pure Expression), согласно определению из книги. То, что вы путаете Pure и Clean, и уж тем более, почему-то, считаете понятия «Чистота» («Pure») и «Функциональное программирование» тождественными — это лично ваши ошибки, которые лично вам стоит исправить. Выражение может быть чистым, но при этом не быть функциональным, что я и показал на примере.
import * as Vector from 'vector';
.Вы явно путаете процедурное и функциональное программирование.
Можно усовершенствовать ваш пример до
const moveUnit => where =>
unit; (moveUnit :: TPlace -> unit -> unit)
, сдвигая последний оперируемый объект в конец, как это делается ну практически во всех функциональных языках для удобства композиции частично примененных функций. Бонус в «явности» передачи состояния (объекта) и передачи таких функций в фвп без необходимости ручного байндинга на инстанс и без класс пропертей). Но JS тут как всегда со своим синтаксисом.Вы ведь не будете называть свою процедуру «move», ведь двигать можно что угодно?Именно так бы я ее назвал в purescript, как раз таки потому, что двигать можно что угодно, так это более общий алгоритм. И накидал бы type-class'ов. Но JS тут как всегда.
Вы явно путаете процедурное и функциональное программирование
Не, я прекрасно понимаю разницу между ними. Я прекрасно понимаю разницу между С и Лисп. Или между тем, как писали на ЖС в 96-м и стараются писать в 2016-м.
Признаюсь вам честно, слышал, что говорят, что Лисп — ненастоящее ФП, а настоящее — только Хаскель. Из-за математики. Но вот это я уже не понимаю. И вчера применял термин «процедурное» именно там, где считал нужным. Если мы говорим просто о том, чтобы пользоваться процедурами в неймспейсах, которые изменяют данные в отдельно лежащих структурах, то это процедурное программирование. Для функционального необходимо кое-что большее чем «не писать классы».
Можно усовершенствовать ваш пример до const moveUnit => where => unit; (moveUnit :: TPlace -> unit -> unit)
Напишите полностью, пожалуйста.
что двигать можно что угодно, так это более общий алгоритм.
Двигать можно что угодно, но не все одинаково.
По поводу 96 и 2017. Парадокс в том, что различие между процедурой и функцией минимальное — первая императивно указывает что сделать с передаваемой структурой данных (само собой мутабельно), вторая же показывает, что должно находиться в конце, после выполнения списка преобразований. Ну то есть вместо «подвинь туда, поверни сюда», у нас результат операции над объектом — это такой объект, который был подвинут туда и повернут сюда.
Да что я вам тут рассказываю, вы и так это знаете.
По поводу Лиспа и Хаскеля — оба выходят за рамки моей повседневной практики, так что чего-то дельного и подкрепленного серьезным опытом сказать по этому поводу не смогу (хотя мне тут все пытаются продать Кложу). Но я помню Алана Кея, помню эти истории про Лисп, Смолтолк и компанию. Единственное, что могу сказать, так это что Эрланг гораздо более ООП-шный (в оригинальном смысле), чем его везде форсят как ФП.
Красота же Хаскеля и смежных языков в том, что там есть type-class'ы. Это очень похоже на привычные ООП-шные интерфейсы. Разница в том, что в ООП мне нужен явный объект (инстанс класса, имплементирующего интферфейс), чтобы на нем (явно) вызывать нужные методы. Type-class'ы же позволяют определить множество, определяемое типом данных (ну например, Moveable), над которым определена операция move. Причем «глобально». Ну то есть я не вызываю метод move на инстансе класса, имплементирующего интерфейс Moveable. Я просто пишу
move some_instance
, а возможность операции move и, главное, ее реализация описаны в расширении множества Moveable для конкретного типа данных, реализующего (через, например, instance в Хаскеле) нужный type-class (интерфейс).Да уж, действительно достаточно туманно и непонятно.
Но, смотрите, это позволяет писать алгоритмы более высокого уровня абстракции. Если мне нужно что-то подвинуть, например в перекомпоновке сцены, мне не нужно знать что именно, и главное как, я двигаю. Алгоритм перекомпоновки знает только (ну грубо говоря) про свою координатную ось, у него (допустим) есть список неких объектов (думаю, вы уже догадались, что они должны имплементить интерфейс Moveable). И теперь ему достаточно на списке (допустим list) этих объектов выполнить операцию map move list. Кратко и красиво. Данные отдельно (объекты), операции отдельно (алгебра над ними).
По поводу moveUnit. На TS это было бы что-то вот такое уродливое:
type Point = {
x: number,
y: number
};
type Unit = Point;
const move = (where: Point) => (target: Unit) => ({
where //ну тут понятно, что операция move должны быть сложнее и над структурой Unit
});
В том же purescript это выглядело бы вот так:
type Place = {
x :: Number,
y :: Number
}
type Unit = Place
move :: Place -> Unit -> Unit
move place unit = unit {
x = unit.x,
y = unit.y
}
Изюминка в том, что частично примененную функцию move (или rotate или что угодно) можно использовать в функциональной композиции, опустив реальный аргумент (последний unit), «склеив» тем самым все операции в одну. И если у вас над типом Unit определены все нужные операции (move, rotate etc, причем отдельно от самого алгоритма), вы можете просто запихнуть структуру типа Unit в результат композиции этих функций. Если же что-то не сходится, вам компилятор тут же выплюнет ошибку.
В общем-то вышеописанное отвечает и на ваш последний вопрос — двигать можно что угодно и без разницы как. А «двигающему алгоритму» абсолютно по барабану как двигается переданный в него объект, главное чтобы для множества, описывающего его (объекта) тип, была определена функция move. Да, такая же история достигается и через интерфейсы, но гораздо более многословно, вот и вся разница.
Резюмируя, обе парадигмы решают одни и те же задачи, просто одна эффективнее в плане перфоманса (ООП), но с большим шумом, а другая элегантнее и кратче (ФП) но с большим майндшифтом и пенальти по перфомансу.
PS. На JS писать красивое ФП нереально, но мечтать хочется.
Резюмируя, обе парадигмы решают одни и те же задачи, просто одна эффективнее в плане перфоманса (ООП), но с большим шумом, а другая элегантнее и кратче (ФП) но с большим майндшифтом и пенальти по перфомансу.
Ну, как на меня, на мелких примерах ФП краше почти всегда)
Интересно вот еще что — покажи как на purescript как в итоге поменять Юнит. Ну то есть функция изменения — это понятно, просто создает юнит. А как она используется в реальном приложении, когда мне, сообственно, необходимо сдвинуть этот юнит и перерисовать потом всю сцену.
Проблема в том, что не всегда нужно такое топорное обновление и вообще это все в вакууме и нужно где-то держать состояние, ну той же сцены, допустим. И чтобы не писать сейчас заклинание из монад, чтобы аккуратно обработать все эффекты, я просто оставлю ссылку на классную статейку.
Не нужно ходить далеко, проведите эксперимент. Попросите ваших знакомых написать сложение двух чисел.
Могу поделиться своим результатом. В большинстве случаев решение будет выглядеть примерно так:
class Sum {
public int Value {get; private set;}
public Sum(int initial) {
Value = initial;
}
public void Add(int value) {
Value += value;
}
}
return buildings.Where(b => b.isAvailable)
А если у меня есть 20 строений и одно из них построилось — я напишу что-то вроде:
b.isReady = true
Или вот у меня есть маркдаун парсер на JS. Что-то вроде такого:
class UniqueAppRenderer : DefaultMarkdownRenderer {
public Image (token: ImageToken) {
return <SuperAppImage token={token} />
}
}
var tree = new MarkdownTree(input);
return tree.render(new UniqueAppRenderer());
Чем этот пример не чистый? Ну или из классики:
var square = new Rectangle(100, 50).getSquare();
На ООП вы пишете чистый код тогда, когда вам необходим чистый код. Комбинируете оба метода. И вам не приходится пересоздавать весь мир, потому что где-то открылась дверь. Именно потому на ООП написано так много столь сложного софта и так мало его написано на ФП.
Объекты, что я привел не иммутабельны. У Rectangle может измениться любая сторона и getSquare будет выдавать иной результат, ровно так и с вектором.
Поэтому заявления что все равно получится редак неверны.
Мне тоже очень не нравится JSX, хотя сама идея вложенных компонентов в react хорошая. Кто-нибудь пробовал вместо JSX использовать такие шаблоны?
https://github.com/wix/react-templates
Хотя все равно кросс-трансляция будет, которая тоже неприятна :( Сейчас у меня никакой кросс-трансляции, ES5 + Knockout.js.
const Button = ({className, text}) => (
<button className={`Button ${className}`}>
{text}
</button>
);
const SuperButton = ({className, text}) => (
<Button
className={`SuperButton ${className}`}
text={text}
/>
);
.SuperButton {
color: red;
}
.Button {
color: green;
}
//Button.css
.Button {
color: green;
}
//Button.jsx
import css from './Button.css';
export const Button = ({className}) => <button className={`${css.Button} ${className}`}></button>;
//SuperButton.css
.SuperButton {
color: red;
}
//SuperButton.jsx
import { Button } from './Button.jsx';
import css from './SuperButton.css';
export const SuperButton = ({className}) => <Button className={`${css.SuperButton} ${className}`}/>;
А еще лучше с react-css-themr, который элегантно решает проблему мерджа класснеймов.
СSS внутри javascript — глупейший тренд последнего времени.
React фанбоям должно понравиться, JSX ведь им нравится.
Скоро появятся css переменные, которые позволят управлять стилями из js. И на этом точка.
Ну да, можно конечно отказаться от быстрого sass написанного на си и начать делать его аналог на js,
при условии что он все рано будет компилироваться в css, да пожалуйста, делайте, тратьте время, если получится на отлично, то им будут пользоваться. Но это будет заменой sass, а не css у которого будущие только в js.
В статье очень долго и подробно рассказывается как, но совсем нет ответа на вопрос почему. Попробую рассказать свой взгляд на эту тему.
0. CSS-in-JS это не инлайн стили
Определение стилей <div style="..."></div>
это совсем не про CSS-in-JS, инлайн-стили здесь тоже не привествуются. В статье рассказывается совсем о другом. Мы просто помещаем определение стилей в тот же JS файл, что и остальной код компонента, а CSS оттуда вытаскивается на этапе сборки или server-side-рендеринга. Ваши пользователи получат такой же CSS как и всегда, все :hover
, медиа-запросы будут работать как и раньше.
Когда мы разобрались, что CSS-in-JS уж как минимум не хуже обычного CSS, давайте разберемся, что мы можем от этого выиграть.
1. Переиспользование констант между JS и CSS
Довольно часто в responsive-сайтах нужно не только менять стили, в зависимости от размера экрана, но еще и исполнять какой-то Javascript. Например, зачем подписываться на клик на кнопку, которая все равно в мобильной версии не видна? Переиспользовать SASS-константы в Javascript — не самая простая задача. С CSS-in-JS вы сможете использовать JS-константы и для стилей, и для кода.
2. Упрощение кода
Вы когда-нибудь пытались взять квадратный корень из переменной в SASS? В Javascript для уже есть Math.sqrt
, а в SASS придется попотеть. Кроме квадратного корня вам может понадобиться еще какая-нибудь логика в стилях, и в SASS с ней однозначно хуже, потому что он не предназначен для написания сложных алгоритмов. Кроме того, JS-функции проще покрыть тестами, чем SASS-миксины.
3. Оптимизация неиспользуемых стилей
На страницу очень часто грузятся стили для динамических блоков, которые не видны на странице сразу после загрузки. Разделять CSS на критический и второстепенный не так просто, а в CSS-in-JS, если у вас есть серверный рендеринг, это проще простого. В упомянутой в статье библиотеке Aphrodite есть поддержка server-side. Когда вы отрендерите на сервере изначальное состояние html, библиотека вернет вам стили только тех компонентов, что были на самом деле использованы при рендеринге, а остальные стили доставятся на клиент уже потом, вместе с JS.
4. Динамический CSS
С помощью CSS-in-JS можно отказаться от инлайновых стилей вообще! Определение стиля в JS может быть не только статической строкой, но и функцией:
const styles = {
left: (props) => props.x + props.offset
}
В Aphrodite я этой фичи не нашел, но в JSS, которым я иногда пользуюсь, такая фича есть.
При изменении свойств компонента это значение будет пересчитываться, и библиотека синхронизирует его в CSS через document.styleSheets
, упомянутый выше в комментариях. В классическом подходе с CSS все равно иногда не получается избежать инлайновых стилей (сделать анимацию, задать координаты для попапа), а в CSS-in-JS вы сможете сгруппировать все стили в одном месте, как всегда этого и хотелось.
Так что, в следующий раз, когда вы будете решать в своем проекте одну из проблем, описанных выше, на секунду задумайтесь, что при использовании CSS-in-JS эта проблема могла бы быть решена проще.
2. И что? Если одному человеку из миллиона, раз в жизни потребуется квадратный корень в CSS, то это оправдывает все остальное? Зачем такие вещи в css? То что делают с квадратными корнями нормальные делают на canvas или webgl. В крайнем случаи в js оставляете логику и результаты устанавливаете, кому? css-var!
3. Вы беспокоитесь о том что мощный комп или телефон пользователя будет грузить стили на 0,005 секунды медленнее и поэтому увеличиваете нагрузку на сервера?
4. Ну вот тебе радость! В оправдание привели безумную идею инлайнстилей. Покупайте у меня аэрозоль, после применения которого можно не совать голову под автомобиль, что спасет Вам жизнь…
1) Во-первых, css-переменные нативно еще не поддерживаются, поэтому это лишь мечты о будущем, а фичи надо делать уже сегодня. Во-вторых, а переменную вы чем выставлять будете? Можно написать руками, а можно взять уже готовое решение, например JSS (5кб весит библиотечка)
2) Этот пункт больше про то, что миксины на SASS получаются очень уж громоздкие. Вот пример из Bootstrap. На JS то же самое смотрится читабельнее. Ну и как тестировать SASS-миксины по-прежнему непонятно.
3) В комментарии я привел ссылку на рассказ про то, почему критический CSS это важно и это совсем не 5мс разницы. Я понимаю вашу фрустрацию, но лучше прочитать материалы, прежде чем высказывать мнение "это всего 5 милисекунд и нагрузка на сервера мне дороже".
4) Возьмем для примера тултип. Его нужно спозиционировать с учетом того, что у него влево выступает стрелочка. Довольно типичная задача в верстке. В CSS-in-JS это будет в одном месте, очень наглядно
const arrowSize = 10;
const tooltipStyles = {
'&:before': {
borderWidth: arrowSize,
// тут остальные стили для стрелочки
},
left: (props) => props.left + arrowSize,
top: (props) => props.top
};
А если делать это на CSS, то часть стилей будет написана в js через $().css({top: xxx, left: xxx})
, а часть про размеры стрелочки написана в другом месте в CSS? Мы снова возвращаемся к вопросу, как нам прокинуть значение размера стрелочки из CSS в JS.
2) Зачем Вам тестировать миксины css? Вы явно пишите что-то не в том месте.
3) Те места, где важна скорость настолько, что лишние ,5ms важны не используют пререндер на сервере, а используют сервера на java, c# или в крайнем случаи ruby или pyton.
4) Зачем Вы тултипу в js ширину задаете? В будущем тултипы вообще не будут нуждаться в влезаньи в них, ведь появятся attr + *-data.
Поэтому тема, как таковая, неверна, ведь она о будущем, а я сказал что у этой ерунды нет будущего, если они не сделают альтернативу sass, которая, как я сказал, если будет быстрее и если она будет так же компилироваться в css, то даже я ей буду пользоваться.
и проблем с именами переменных я в sass не заметил и так же будет и с css. Поэтому аргументы такие — Вы тысячи людей не правы, ведь мне так не нравится.
То есть ваша позиция в том, что скоро в браузеры по полной поддержат CSS-переменные, Shadow DOM, веб-компоненты, и после этого все эти временные решения будут не нужны.
Скорее всего так и будет, только Shadow DOM стандарт так до конца и не устаканился (недавно его переписали целиком заново и он называется Shadow DOM v1 в противовес прошлому v0).
В общем, можно долго ждать и надеяться на разработчиков браузеров, а задачи как-то решать надо. Поэтому я и изучаю варианты использования CSS-in-JS, в ближайшие пару лет это точно пригодится
Обоснование этому все то что я уже сказал + то что многие делают ошибку в одном месте, а пытаются решить её в другом. И вот из-за этого появляются подобные идеи.
Будущее бывает разное.
Есть совсем далекое будущее, с межзведными перелетами, есть поближе, где все бразуеры начнут поддерживать Shadow DOM.
А есть вопрос, на чем начать пилить админку в следующем месяце.
Вы же не отказываетесь от покупки машины, только потому что ждете появления летающих автомобилей через несколько лет?
- 3. По пункту 3 не совсем ясно. Скажем сгенерировали мы CSS для некоторой страницы, без лишнего кода. Юзер кликает на другую страницу и получает новый набор CSS, большую часть из которого он уже встречал на прошлой странице. Но загружает повторно, всякий раз… Даже если юзер вернулся на ту же самую страницу. Тут уже какой-то прямо commonChunk как в webpack-е нужен? Или я сбился с толку? Как предполагается решать такие задачи?
- 4. Я правильно понял этот пункт? Это автоматически генерируемые стили в runtime? Там же вроде речь шла о предварительной компиляции всего этого добра в отдельную CSS?
- 5. Так и не понятно в итоге, что с каскадами? Просто похоронить как избыточную сложность? Возможно это решение, но выглядит радикально. В качестве альтернативы городить трудно-читаемых монстров через интерполяцию и зависимости (подключать наследуемые имена классов из соответствующих файлов и пробрасывать их внутрь зависимых, где подключать через
${}
).
Мне сама идея более тесной взаимосвязи CSS и JS кажется интересной, т.к., на мой взгляд, в больших проектах, есть почти нерешаемая проблема в виде кучи не используемого legacy кода. А тут вроде можно всё так к друг другу поджать, чтобы оно на уровне резолва зависимостей отрабатывало. Посему всё жду серебрянной пули, идеального решения. Но куда бы я пока не глянул ― выглядит вторично и "не вкусно". Особенно когда некогда простые стили превращаются в нагромождение интерполяций, методов и разных хаков.
3) Обычно в веб-приложениях переходы по ссылкам происходят без перезагрузки страницы. Повторно ни скрипты, ни стили не загружаются.
4) Предварительно выносится критический CSS — тот что юзер увидит еще до подгрузки Javascript. Стили для попапов и других динамических штук остаются в JS, потому что все равно у пользователей с отключенными скриптами они не будут видны
5) Каскады на месте. Из нашего JS кода сгенерируется совершенно обычный CSS, hover, каскады, все можно использовать как и раньше. Вот пример из JSS: http://cssinjs.org/jss-nested?v=v4.0.1
По поводу того как это выглядит в больших проектах, можно посмотреть на документацию JSS: https://github.com/cssinjs/cssinjs Этот сайт использует их же технологию для стилей. По-моему, читаются стиили так же, как и если бы были в CSS. Пример одного файла.
Ну и кроме того CSS-переменные должны иметь глобальные имена, поэтому мы возвращаемся обратно в БЭМ, только теперь в именах переменных --my-button--background-color-default
1. собирает из css картины, по очень сложным алгоритмам, для которых нужно писать тесты в миксинах.
2. для тех кто не хочет учить css и ему проще забабахать все из js.
3. для тех кто не знает сегодня что будет в css завтра, им видимо лень читать спеки.
4. тем кто делает супербыстрые сайты на медленном пререндере и ему приходится экономить на на размере css.
5. Тем кто в подобный рассказ может поверить прочитав пару таких же рассказов приведенных в доказательство.
Для классических страниц (например, таких, как эта статья), конечно, классический CSS идеален. Автор один раз верстает HTML-страницу, а вы потом накладываете на нее какие угодно стили.
Для веб-приложений классический CSS — это скорее зло, потому что стили одного компонента начинают негативно влиять на стили другого, становится трудно их переопределить и т. д. Усложняется повторное использование кода. Только inline-стили выручают (аргумент с «весом» страницы не рассматриваем — веб-приложение и так может определить, как именно оно хочет себя закешировать, и какие данные загружать повторно не нужно).
Описываемый вами подход с «динамическим» CSS, это скорее, компромисс между двумя крайностями.
Вы правы, все так и есть.
Однако в комментариях выше моего, довольно категорично заявляется что CSS-in-JS отстой и не может быть полезен вообще никогда, что и побудило меня написать свое мнение.
И Вы не представляете, я много раз писал очень сложные приложения с нуля на реакте и ангуляр и у меня ни разу не было того, о чем Вы говорите.
И хватит уже ерунду нести с глобальным пространством. Вы вот когда вэбпаком все импорты в одном месте собираете для вывода наружу не испытываете сложности? Нет! А почему? Потому что все имена разные!
Ну это если Вы понимаете о чем я, если Вы действительно не пишите только легкие приложения.
<Button />
, а завтра наследуете от него <SuperButton />
, то у вас уже большие проблемы с CSS. От того, в каком порядке соберутся стили, будет зависеть внешний вид вашей кнопки. В случае с inline-стилями такой проблемы не будет.хватит уже ерунду нести с глобальным пространствомПростите, я разве что-то где-то об этом говорил?
Или возможно Вы никогда не писали на bootstrap и считаете twetter маленьким или вымышленным?
Все что здесь привели ЗА из разряда «а что будет, если годзила встретит кинконга».
Twitter Bootstrap, как раз таки, от этого страдает. Всякие ".btn .btn-default" тому подтверждение (совершенно немасштабируемое решение, если вы решите «унаследовать» свою кнопку от стандартной).
Я говорю о том, что можно написать нужные Вам классы самому с самого нуля, как это сделано в бутстрап и обойтись вообще без чего-либо. Но не в коем случаи не тащить бутстрап туда, где придется все переписывать и переопределять, ведь он не затачивался под конкретный случай.
что Вы думаете о том, что кто-то говорит «брать бутстрап».Вы, видимо, не представляете, сколько таких заказчиков, которые хотят космолет, но слышали про бутстрап и думают, что будет быстро и дешево. Еще и упрутся рогами.
Как я понимаю, со мной не согласны фронтендеры, которые работают над большими проектами. Весь бутстрап — удобно для проектирования всяких админок и внутренностей. Сетка — для простых страниц, когда проще взять готовое и сделать за несколько часов, чем тратить время на собственную сетку.
Только не нужно называть CSS-in-JS inline-стилями.
Инлайн стили это <div style="..."></div>
, и никаких CSS-селекторов, медиа-запросов, ховеров.
А CSS-in-JS — это про генерацию обычного CSS, как будто JS теперь стал препроцессором для CSS, таким же как SASS или LESS.
Очень важно различать эти понятия, чтобы не было ложного впечатления "нафиг этот CSS-in-JS, там :hover" не работает". Работает, также как и везде.
Сейчас проверил. Есть стили:
const styles = {
button: {
background: 'blue',
'&:hover': {
background: props => props.hoverColor
}
},
}
Когда меняется props.hoverColor, обновляется правило в CSS, никаких инлайнов, все честно.
А как, новый тэг style пишется или в styleSheet обновляется?
Используется CSSOM.
В исходниках JSS это делается вот так: https://github.com/cssinjs/jss/blob/master/src/backends/DomRenderer.js#L30
Зато такие решения как requirejs порядок не гарантируют.
Это проблема идеи замены дерева зависимостей компонентов на зависимости модулей — вторые не совсем точно повторяют первые.
Проявиться она может в любых системах динамической загрузки модулей — requirejs, systemjs, webpack с настроенными чанками.
Вот только вебпак никому не должен "переразруливать зависимости", потому что все работает в полном соответствии со спецификацией этих самых зависимостей.
Решить эту проблему можно если отказаться от неявной установки стилей в момент их загрузки, заменив ее на явный вызов:
import { Button } from './Button.jsx';
import css from './SuperButton.css';
export const SuperButton = ({className}) => <Button className={`${css.SuperButton} ${className}`}/>;
css.install();
Но, скорее всего, для большинства разработчиков такое решение будет неудобным. Да и костылем оно отдает, потому что явно разделяет модули на модули-компоненты и модули вспомогательные...
Что вы понимаете под "пересборкой"?
Так ведь webpack не управляет тегами style, вот в чем проблема.
Причем, есть предчувствие, что он даже есть, через какие-нибудь ивенты.
«Типографика может создать или разрушить дизайн: выбор типа» (Typography can make or break your design: a process for choosing type)
«Type» тут — это «шрифт», а не «тип»
Забавно читать такое, когда буквально недавно закончил рефакторинг ряда компонентов своего проекта с переносом практически всей логики и вариативности отображения в CSS. Утверждаю, что CSS в JS — это проблема скорее React-разработчиков и их мета-платформы. У них таких проблем много, но погрязнув в React-е они начинают эти проблемы экстраполировать на всю веб-разработку. И говорить о "будущем" тут очень некорректно, будущее (а для кого-то уже давно настоящее) — за веб-компонентами и нативными CSS.
Посмотрите мой комментарий выше, пункт 0 именно о том, что вы говорите.
Не надо заблуждаться что CSS-in-JS это про то, что стили теперь делаются на JS. Нет, анимации все так же делаются на CSS, каскад и :hover тоже доступны. Зато, от переноса исходного кода стилей (именно исходного, а не конечного результата) в JS можно получить некоторое удобство.
Утверждаю, что CSS в JS — это проблема скорее React-разработчиков и их мета-платформы.
CSS-in-JS не проблема, а решение. Как вы осуществляете ленивую подгрузку стилей на своей любимой платформе? Как вы изолируете стили, чтобы они применялись только к конкретному компоненту?
CSS-in-JS предлагает по максимуму переиспользовать уже готовые инструменты для JS, вместо изобретения дополнительных инструментов для CSS.
Я видел ваш комментарий выше и даже плюсанул его. А в остальном — вы, прежде чем говорить о дополнительных инструментах, ленивой подгрузке, изоляции стилей и т.д, поинтересуйтесь хотя-бы что такое веб-компоненты, почитайте их актуальные спецификации, посмотрите доклады на эту тему с последнего Google I/O на ютубе… Это не очень объемные материалы, но обещаю — это очень полезно и позволяет во многом по новому взглянуть на современную веб-платформу и на ее ключевые отличия от популярных мета-платформ и прочих костылей.
Лично я эти вещи использую уже довольно давно, успел наработать свой эффективный подход, и мне, порой, просто дико смотреть на все эти "решения" несуществующих проблем. Ну и PostCSS поизучайте вдогонку.
Я в курсе что такое веб-компоненты, но они сейчас поддерживаются только в Хроме и поддержки в других браузерах в этом году ждать точно не стоит.
И Shadow DOM так просто не полифиллится, там такие костыли и проблемы с производительностью, что я уж лучше на JSS посижу, пока в браузерах нормальная поддержка не появится.
У нас в команде Хром — референсный браузер, но мы также все детально тестируем в остальных (кроме экзотики конечно). Часто сталкиваюсь с заявлениями о том, что полифилы тормозят. Блин, я бы согласился с этим пару лет назад, но сейчас мне непонятно откуда вы это берете. А мы делаем довольно сложный фронт. Ну и нельзя сказать, что только в Хроме компоненты поддерживаются (а это, если что, самый популярный браузер и он сейчас задает темп для остальных), ибо не только. Есть конечно нюансы, технология проходит долгий и трудный путь, но, если сравнивать с остальным зоопарком — для меня выбор очевиден.
Подозреваю, что вы пользуетесь не чистыми веб-компонентами, а Polymer. Это важно, потому что последние версии Polymer достигли более-менее приличной производительности, только когда перешли с честных полифиллов, на упрощенные, которые по своей сути работают так же как с CSS-modules или CSS-in-JS. Вся разница только в том, как это называть, а суть одна и та же: мы генерируем СSS с динамическими именами классов.
Ну и конечно, будет большой плюс вам, если выложите статью, где расскажете как сделать так, чтобы веб-приложение загружалось так быстро, что никакая крутилка и надпись "подождите, мы грузимся" вначале ему не была нужна.
У меня сейчас есть три проекта: на чистых компонентах, на Polymer 1.9.1 и на Polymer 2.0.1 ))) То, что вы говорите относится только к версиям 1.x
Пример приложения https://shop.polymer-project.org/
В Firefox, Internet Explorer не поддерживается. Так что все равно придется думать о фоллбеке. И в качестве фоллбека там работает такой же точно механизм, что и с CSS-in-JS с процессингом и динамической генерацией стилей в браузере пользователя.
Кроме того, синтаксис определения Polymer компонентов мне кажется неудобным, уж лучше CSS в JS писать. Но это уже тема другой дискуссии.
И в качестве фоллбека там работает такой же точно механизм, что и с CSS-in-JS с процессингом и динамической генерацией стилей в браузере пользователя.
И почему же это плохо?
Я не говорю что это плохо.
Просто я не понимаю, почему на CSS-in-JS такая негативная реакция, в то время как Polymer делает то же, и всех это устраивает.
Потому что важен язык разметки, а не его реализация.
Лично я эти вещи использую уже довольно давно, успел наработать свой эффективный подход, и мне, порой, просто дико смотреть на все эти "решения" несуществующих проблем.
Расскажите поподробнее, как вы делаете лениво загружаемые стили и рендерите критический CSS. Без троллинга, реально интересно, как это можно сделать, не перенося CSS в JS.
CSS в JavaScript: будущее компонентных стилей