Комментарии 78
Каждый плагин умеет что-то свое, но еще нет такого, который бы исправлял прямо все проблемы кода разом, да это и невозможно. Насколько мне известно, команда eslint сейчас работает над инструментами, которые бы частично решали эту проблему.
Один из core-разработчик, работая над react-native решил использовать inline-стили, чтобы не возиться с написанием css-парсера на тот момент. Неожиданно этот «анти-паттерн» оказался достаточно популярным среди сообщества и сейчас эта идея получила широкое развитие.
Мне нравится идея, что стили компонента лежат рядом с ним, а не определяются глобально. Другое дело, что inline-стили не кешируются и работают медленнее, чем классы.
Для решения этой и многих других проблем со стилями я в своих проектах сейчас использую jss, но также много хорошего слышал про aphrodite.
Так как статья рассчитана в основном на новичков, то мне показалось, что лучше на этом этапе тему css опустить и написать потом отдельную статью, если будет много интереса к этой теме.
Да, и оно как-то очень естественно вышло. Может мне повезло и я случайно обошел грабли :)
Конфиг из статьи. Я до сих пор суть проблемы не понимаю. То, что вы определяете в jss-стилях, не экстрактится by design. То есть у вас:
1) один большой css, который сформирован на базе классических импортов
2) jss все свои стили при Server Side Rendering формирует и вставляет в HTML, если не используете SSR, то просто по одному блоку style на каждый тип компонента, который ренденрится на странице
Ну вы так быстро ответили "да", видимо не поняли вопроса. Я даже удивился :) Конфиг из статьи к jss не имеет никакого отношения.
То, что by design — это понятно, но помечтать о том, чтобы jss выдрать build-time, вполне можно, если не использовать его динамические фичи. Вот, например, попытка (насколько я понял, заброшенная) сделать что-то на эту тему: https://github.com/markdalgleish/jss-loader
Строки build и nodemon в package.json должны выглядеть так:
«set NODE_ENV='production' node node_modules/webpack/bin/webpack -p»
«set NODE_PATH=./src node node_modules/nodemon/bin/nodemon server.js»
А в файле .eslintrc замените строку «linebreak-style»: [2, «windows»]
P.S. Извиняюсь за двойной комментарий, мой косяк.-
set NODE_ENV='production' && node node_modules/webpack/bin/webpack -p
А второй кроссплатформенно заменяется на
node node_modules/nodemon/bin/nodemon server.js ./src
Ну и от себя, webpack и nodemon будут нужны не в одном проекте и имеет смысл их ставить глобально.
Тогда еще проще
set NODE_ENV='production' && webpack -p
nodemon server.js ./src
В а чем преимущество глобальной установки, кроме экономии на символах?
Во-первых, на linux для глобальной установки нужно sudo
Во-вторых, не всегда все проекты используют одну и ту же версию библиотеки. Особенно это актуально при скором релизе webpack 2.
Кажется экономия на паре пакетов в npm install все равно не стоит того, что придется потратить на разборки, почему ничего не собирается, если пытаться запустить webpack 1 на проекте в webpack 2. Явная декларация версии webpack и всех остальных иструментов тоже экономит время.
Но это мое мнение из собственного опыта.
Я с Вами согласен, поэтому в статье писал ровно такие скрипты, которые использую сам в проектах, размещаю в npm и деплою продакшен, в которых используются только локальные пакеты. Потому что это единственный по-настоящему правильный способ.
Но при этом вполне допускаю, что в песочнице у людей может быть, что угодно, и мне кажется, что это нормально, ведь это же обучение — тут важно, чтобы сразу все работало, дабы не растерять энтузиазм на начальном этапе, когда это критически важно.
А остальное — это наживное и с опытом придет.
nodemon
, смог правильно подключить server.js
только после того как в конце NODE_PATH
была добавлена точка с запятой, то есть получилось так:set NODE_PATH=./src; && nodemon server.js
Я бы рад, но в моем основном проекте я начал использовать devise_token_auth на бэкенде, соответственно на стороне front-end — redux-auth. Спустя некоторое время я его переписал и опубликовал в виде redux-oauth, так как redux-auth не поддерживал ряд важных мне фич (например, API requests для server-side rendering), плюс он непомерно тяжелый для того, что умеет. Я планировал в своем цикле осветить именно этот стек, так как он работает у меня в продакшене примерно полгода без видимых проблем, и я им более, чем доволен.
Возможно в будущих проектах я перейду на JWT, так как будет другой бэкенд и тогда опубликую условный redux-jwt, если не найду ничего готового, но пока у меня такого опыта, которым я мог бы авторитетно поделиться.
Много разработчиков его понимают или только избранные? :)
Этот механизм позволяет пользователю получить доступ к контенту существенно быстрее.
Каким образом?
Самое главное, что пользователь имеет доступ к контенту почти сразу, а не спустя две и более секунды, как это бывает в случае традиционных client-sideJavaScript приложений.
Что входит в эти 2 секунды?
Это время генерации страницы?
Выигрыш получается за счет того, что не надо дожидаться скачивания клиентского JavaScript, а это 200кб и более с учетом минификации и сжатия.
Почему же не нужно?
изоморфный подход делает ваше приложение гораздо приятнее для пользователя.
В чем приятность, если отвалились скрипты?
Если ваше клиентское JavaScript приложение перестало работать из-за ошибки, то ваш сайт скорее всего станет бесполезным для пользователя. В изоморфном же случае есть хороший шанс, что пользователь все же сможет сделать то, что он хочет.
Почему?
- То, что описано в статье разработчик с опытом собирает в течение 5-10 минут. Написание самого приложения по скорости мало отличается от других технологий. Facebook, AirBnB, Twitter, порталы Yahoo, девпортал Apple и масса других сайтов — это все сейчас на реакте
- В 2 секунды входит загрузка JS и инициализация, причем если временем загрузки можно пренебречь, то вот с инициализацией вы ничего сделать не сможете. Если у вас client-side rendering, то вот эти 2+ секунды JS будет запускаться и формировать страницу, а пользователь — любоваться на заставку "Loading".
- Не нужно ждать, потому что сервер Node.js отдает HTML с контентом. Да, в течение этих 2 секунд кнопочки работать не будут, но скорее всего пользователь и не успеет ничего сделать
- Если отвалились скрипты, то client-side приложение даже ничего не покажет, изоморфное же в теории работать дальше, как nojs приложения
Я Вас понимаю, я много лет относился к node и серверному JavaScript с огромным скепсисом и до сих пор считаю, что писать backend на ноде не надо вот совсем, но после того, как сам попробовал реакт на одном из своих проектов и генерация view слоя упала с 20 секунд до 0.5 при переходе с rails на rails-api + node.js, выяснилось, что за последние несколько лет очень многое изменилось в индустрии и что производительность JavaScript может доходить до 30% от C, а не 1%, как когда-то было.
Если Вы найдете время и желание и погрузитесь в это, все Ваши вопросы отпадут сами собой достаточно быстро.
Не нужно ждать, потому что сервер Node.js отдает HTML с контентом. Да, в течение этих 2 секунд кнопочки работать не будут, но скорее всего пользователь и не успеет ничего сделать
А как обрабатывается случай, когда всё таки успеет?
И насколько сложнее сделать изоморфное приложение с бэкэндом не на Node? То есть понятно, что теоретически можно выполнить JS-код на чём угодно, но есть ли для этого готовые инструменты хотя бы в мейнстримных Java, C#, Python, etc.?
It depends. Если у Вас кнопка, которая чистый JavaScript (ну там выбор цвета аватарки в виджете, который генерит аватарки), то упс. Если это ссылка или кнопка формы, то вместо того, чтобы логика обрабатывалась клиентским JS, который вероятно сделает что-нибудь полезное (отрендерит часть страницы и покажет loading indicator, например, или сделает валидацию), будет выполнено "классическое вебовское действие", то есть запрос уйдет на сервер и форма будет отправлена POST'ом соответственно. Важно понимать, что эти fallbacks не совсем бесплатны с точки зрения работы программиста и нужно их реализовывать там, где надо, а не везде. Но это здорово, что есть такая возможность, и в теории можно сделать даже так, что сайт вполне себе работал даже если у клиента JS отключен совсем.
- Я очень часто сталкиваюсь с таким вопросом, когда рассказываю знакомым про изоморфные приложения. В примере и в следующих частях я пишу как раз такое приложение: фронт изоморфный, а бек — на rails-api.
То есть у Вашего веб-приложения будет 2+ серверной части: backend, который предоставляет REST API и node.js часть, которая рендерит HTML.
Грубо говоря, раньше у Вас было rails или там php, C#, Python, whatever приложение, которое а) обрабатывало запросы б) рендерило HTML, который отдавало клиенту.
Теперь у Вас разделились сущности и это, на самом деле, очень здорово: а) приложение на whatever технологии обрабатывает запросы и отдает JSON ответ и б) приложение на node.js, которое обрабатывает запросы, делает запросы к API при необходимости и отдает HTML клиенту. И вот б) в этой схеме нам нужен, чтобы сайт работал быстрее с точки зрения пользователя, отдавал поисковикам контент без необходимости выполнять JS и "сглаживал" ошибки клиентского JS. Тут важно отметить, что разницы между клиентским и изоморфном подходом с точки зрения кода очень мало: код клиентской и серверной части совпадает на 95%+, то есть реализация этой фичи обходится очень дешево, принимая во внимание, сколько пользы она приносит
Тут еще добавлю, что разделять front и back очевидно хорошо, потому что
- тот же API может быть использован мобильными приложениями
- удобно разделить разработку на две команды
- проще тестировать
1. То-то я вижу, они тупят :)
2. Хм, то есть по факту не на 2с быстрее, а скорее всего на 2с медленнее :) В классике js инициализируется моментально после загрузки.
3. А PHP не отдает что ли? :) Ни чем не лучше.
4. Разницы нету…
5. Я не то что отношусь со скепсисом.
NodeJS на самом деле очень быстрый, но не так просто переносить на асинхронную модель синхронный код.
Может есть наработки это упрощающие, хз.
6. Что это за приложение с генерацией 20 с? Это на сервере столько шаблон отрабатывал? Да, Руби тот еще скороход :)
7. Зачем мне погружаться, если я слышу только голословные утверждения?
А PHP не отдает что ли? :) Ни чем не лучше.
Вы или не понимаете, о чем говорите, или это тролллинг.
PHP отдает статичный html, который так и останется статичным html, можно лишь вручную его менять. React на сервере отдает статичный html, который на клиенте сразу же подхватывается клиентским реактом и дальше работает так, будто этот реакт его и сгенерировал (то есть при обновлении состояния будет обновится и html).
npm run nodemon
выдало ошибку, что Express не найден. Добавьте, пожалуйста, команду npm install express
чтобы обозначить установку фрэймворкаВот пример зборки на сервере
var webpack = require('webpack');
var path = require('path');
var fs = require('fs');
var nodeModules = {};
fs.readdirSync('node_modules')
.filter(function(x) {
return ['.bin'].indexOf(x) === -1;
})
.forEach(function(mod) {
nodeModules[mod] = 'commonjs ' + mod;
});
var babelPlugins = ['transform-runtime'];
module.exports = {
entry: './index.js',
target: 'node',
output: {
path: path.join(__dirname, 'build'),
filename: 'backend.js'
},
module : {
loaders: [
{
loader : 'babel',
exclude: /node_modules/,
query: {
plugins: babelPlugins,
presets: ["stage-0", "react","es2015-node5"],
}
},
{
test: /\.css$/, loader: "style-loader!css-loader"
},
]
},
externals: nodeModules,
plugins: [
new webpack.IgnorePlugin(/\.(css|less)$/),
new webpack.BannerPlugin('require("source-map-support").install();',
{ raw: true, entryOnly: false })
],
devtool: 'sourcemap'
}
не забудте забрать полифил и babel-register с серверного кода
Если сюда добавить необходимость делать клиентские API запросы после инициализации и вспомнить, что на мобильном интернете часто можно столкнуться с весьма ощутимыми задержками
Это вы скорее описываете преимущества серверного рендеринга, а не изоморфного. Ведь во втором случае это будет происходить только при загрузке первой страницы. Если js успешно подгрузится, конечно. А дальше всё те же запросы к API.
Правильно ли я понимаю, что babel-polyfill
просто ищет упоминания всяких Promise
в коде и добавляет полифилы в бандл? То есть даже если браузер современный ему всё равно всё это придётся скачать? Или возможно создавать разные бандлы под современные и старые браузеры?
https://babeljs.io/docs/usage/polyfill/
В принципе да, примерно это он и делает. Я не экспериментировал, поэтому пока не имею такого опыта, но технически не вижу проблем сделать 2 сборки: с полифиллом и без. Соответственно express по User-Agent понимает, кто к нему пришел и отдает HTML шаблон с соответствующим бандлом.
А почему вы решили описывать настройки в .babelrc
, а не в конфиге вебпака?
In addition to the shell's pre-existing PATH, npm run adds node_modules/.bin to the PATH provided to scripts. Any binaries provided by locally-installed dependencies can be used without the node_modules/.bin prefix.
Спасибо за труд. Штудируя статью документации по оптимизации производительности, не втыкаюсь, почему этот код не работает:
handleClick() {
// This section is bad style and causes a bug
const words = this.state.words;
words.push('marklar');
this.setState({words: words});
}
а этот работает:
handleClick() {
this.setState(prevState => ({
words: prevState.words.concat(['marklar'])
}));
}
Спасите-помогите! :)
State явно можно менять только в конструкторе.
Непонятно, тогда почему этот код работает?

Так здесь вроде всё, как и должно быть. state явно задается в конструкторе, а потом уже меняется только через setState.
1) Вы инициализируете state в конструкторе, все работает, противоречий нет
2) Вы в toggleState меняете state через setState. setState — это не просто сеттер JS для аттрибута state, это функция, которая делает много всего. Противоречий нет
3) В render, вы обращается к state напрямую но на чтение, это никто не запрещает. Противоречий нет.
Биндить метод в render — плохая практика. Это не критично — оно будет работать, просто создаете оверхед на ровном месте, которого легко избежать.
В конструкторе пишите
this.toggleState = this.toggleState.bind(this);
в render — onClick={this.toggleState}
Подробнее: https://ryanfunduk.com/articles/never-bind-in-render/
Проблема другая. Но я не понимаю. Цитирую из статьи:
The problem is that PureComponent will do a simple comparison between the old and new values of this.props.words. Since this code mutates the words array in the handleClick method of WordAdder, the old and new values of this.props.words will compare as equal, even though the actual words in the array have changed. The ListOfWords will thus not update even though it has new words that shoud be rendered.
Действительно, вот так работает:
handleClick() {
this.setState({words: this.state.words.concat(['marklar'])});
}
Но тогда зачем нужна вот эта вариация с prevState?
handleClick() {
this.setState(prevState => ({
words: prevState.words.concat(['marklar'])
}));
}
Ох, я понял! Эквивалентная запись с использованием spread:
handleClick() {
this.setState({...this.state, words: this.state.words.concat(['marklar'])});
}
Не совсем, тут скорее учитывается асинхронность обновления состояния.
И ещё. Очень интересно почитать "разжёванную" версию вот этой статьи.
Когда вы рендерите список элементов, вам надо передать каждому из них prop "key", который должен быть уникальным и быть как-то связанным с элементом (передавать значения от 1 до i является антипаттерном, так как не несет никакого смысла с точки зрения фреймворка). Если мы вставляем (или удаляем) элемент из списка, то при ререндеринге мы более эффктивно будем реюзать существующие элементы.
State A1:
<li key="11">11</li>
<li key="22">22</li>
<li key="33">33</li>
<li key="44">44</li>
State A2:
<li key="1">11</li>
<li key="2">22</li>
<li key="3">33</li>
<li key="4">44</li>
State B1:
<li key="11">11</li>
<li key="22">22</li>
<li key="44">44</li>
State B2:
<li key="1">11</li>
<li key="2">33</li>
<li key="3">44</li>
Если упрощенно, то при переходе от A1->B1 у нас будет 1 обновление DOM, а из A2->B2 — 3, хотя с точки зрения юзера UI идентичен
В моём проекте перед запуском сервера webpack собирает bundle.js и всё. Для чего нужен какой-то webpack-dev-server? Для чего это ненужное усложнение?
Все эти запутанные заморочки в большом количестве просто удручают. Я не могу их понять.
В статье описано, зачем нужен webpack-dev-server. Приведи конкретную цитату из статьи, которая тебе не понятна
Каждый раз пересобирать весь проект может быть весьма накладно: для проекта среднего размера сборка легко может достигать 30 и более секунд. Чтобы решить эту проблему, во время разработки очень удобно использовать webpack-dev-server.
С чего взято, что время сборки занимает 30 секунд? Занимает 2-3 секунды. Если поставить watch: true, то bundle.js сам пересобирается. А причём тут webpack-dev-server? Вот это и непонятно. Я хочу разобраться. Пока не разберусь, не успокоюсь.
webpack — это сборщик, он конвертирует проект в конечные ассеты: js и css
webpack-dev-server — это сервер, который запускает webpack для первоначальной сборки и далее для каждого измененного файла. Его задача: сборка + хостинг ассетов в процессе разработок + hot reload, то есть обновление страницы в браузере автоматически в процессе разработки
С чего взято, что время сборки занимает 30 секунд?
речь же не о хелло вордах
Нельзя ли прямо из react-приложения обратиться к БД? Вроде как нельзя, так как это javascript. А как тогда сервер в SSR-приложении рендерит готовый html с выгруженными из БД данными? Куда ни смотрю, везде для выгрузки данных делают ajax-запрос. А как это происходит в случае SSR. Какая-то путаница в голове.
То есть, чтобы сренденрить страницу, сервер проделывает http-запроосы (а то реально долго в сравнении с прямым запросом к БД)?
SSR делает такой же запрос к API как и приложение в браузере. Если ты один раз сделаешь честное профилирование, то увидишь, что разница между http и прямым запросом к БД будет не очень большой, но взамен:
1) ты не привязываешься к БД: сервис предоставляет интерфейс, а откуда он берет данные неважно (сейчас БД, потом nosql, потом что-нибудь еще)
2) ты не пишешь два раза код, который делает одно и тоже (SSR дергает бд, frontend — API)
3) frontend никогда не должен лезть в БД напрямую в боевом проекте. У тебя просто заберут весь контент и все
Спасибо за ваши ответы. Хотелось бы узнать от опытного разработчика, как вы преодолели эти вещи.
Блин, возмущен он. Я наверное последний раз отвечаю, так как в статьях все это описано, только подробнее.
Во время SSR сервер рекурсивно обходит компоненты (для этого надо какую-нибудь либу взять или самому этот механизм реализовать) и собирает какие запросы к API нужно выполнить, чтобы отдать страницу со всеми данными.
После того, как список запросов известен, сервер их выполняет, их результат сохраняется в redux store и только после этого выполняется рендеринг с использованием всех нужных данных из стора. В результате получается HTML страница со всеми данными. Это страница + состояние redux store отдается клиенту.
Клиент отображает HTML и в фоне загружает JS, инициализирует Реакт приложение и загружает в redux начальное состояние, которое передал сервер.
Соответственно, SE и другие, лишенные JS, просто получают HTML с данными и до JS шага не доходят, что в принципе ок.
https://blog.tableflip.io/server-side-rendering-with-react-and-redux/ — в этой статье всё хорошо написано. Может, кто-то, как и я, тоже столкнётся с этой проблемой. Поэтому эта статья им поможет.
Очень упрощённо так:
1. Определить роут для выдачи через AJAX
const app = express();
app.get('/api/get/some/data', (req, res) => {
const returnValue = db.get_some_data(); // упрощённо - можно извлечь данные из БД
res.send(returnValue);
});
2. При отрисовке на сервере вызвать его без AJAX, а в браузере — через AJAX
let someData;
if (isServer)
someData = await some_kind_of_http.get('/api/get/some/data');
else
someData = await true_http.get('/api/get/some/data');
Таким образом если HTML как то зависит от AJAX данных (список какой-нибудь или страница товара), то данные будут извлечены из db.
Но когда лезешь разбираться и пытаешься сделать сам, то тут возникают кучу проблем. Везде бардак. Сплошной бардак. Каждый пишет, как хочет. Нет единых стандартов. Какие-то библиотека работают так, какие-то эдак. Кучу настроек, в которых очень легко запутаться. И в каждую настройку надо вникать, чтобы понимать, что на самом деле происходит. А их туча! Могут работать по-разному. Нет элегантности, простоты и красоты — что всегда должно быть превыше всего в программировании.
в следующий раз сбор стека займет едва ли более 5 минут.
Каждый раз это занимает 2-3 дня, потому что с момента прошлой сборки стартера прошло не менее 6 месяцев, а то и год и ВСЁ УЖЕ УСТАРЕЛО!
React.js: собираем с нуля изоморфное / универсальное приложение. Часть 1: собираем стек