Ушел перепроходить курс по реакту)
Всем привет!

Сорри, что меня почти не было видно и я затянул с переводом уроков по React. Просто я тут вдруг устроился еще на одну работу, и времени стало прям сильно меньше :) Тем не менее, пришли выходные и получилось некоторое время использовать с пользой, и я таки доперевел этот раздел. Очень всем советую к прохождению. Даже если вы уже прошли этот раздел, но пока еще не являетесь экспертом, рекомендую перепройти повторно (я там много что исправил и добавил комментариев).

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

Почему я вообще на этом акцентирую внимание? Да потому что сам все эти уроки так же прошел, и меня просто убивает подача там информации. По большей степени там многое подается вообще в отрыве от чистого JavaScript. Особенно убивают формулировки типа "Там, где у вас в методе render прописан return, вот там надо JS писать в фигурных скобках". Вот ниразу return ничего здесь не определяет. Определяет внутри ли JSX-тегов вы пытаетесь писать свой JS-код, или за пределами их. Потому что еще раз: "Реакт - это просто HTML вперемешку с JavaSciprt, и обеспечивается это за счет JSX-шаблонизации".

А еще Реакт-компоненты - это просто функции. И даже если это классы, то все равно вспоминаем, что классы в JS - это тоже по сути просто синтаксический сахар, а по сути своей все равно это функции. Так вот, чтобы полноценно изучать React, следует сначала боле менее изучить чистый JavaScript плюс синтаксис ES6 (и хотя бы немного HTML и CSS). В противном случае вы рискуете совсем неправильно понять React и остаться на уровне простого копи-пастера кода.

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

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

А на счет xState я пока не определился добро это или зло. С одной стороны это позволяет более наглядно и четко заложить логику приложения, заствляя подгонять вьюхи под предполагаемую логику. С другой стороны, как и в случае с библиотеками типа Redux, это сильно раздувает кодовую базу. Я не увидел там ничего, что помогло бы сделать то, чего не умеет Реакт из коробки. Так что я все же больше против этой библиотеки, чем за нее.

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

В одном из решений здесь есть пример того, как точно не надо делать (хотя технически это возможно, и даже решение принято как правильное).
increment() { this.setState({ count: ++this.state.count }); } decrement() { this.setState({ count: --this.state.count }); }
Технически, эти методы делают то, чего от них и ожидалось: увеличивают и уменьшают значение состояния. Но в данном случае проблема в том, что используются операторы ++ и --. Соль обоих операторов в том, что они не просто возвращают новое значение (если они использованы перед переменной), но еще и меняют саму переменную. То есть еще до того, как реально отработался метод setState(), значение переменной this.state.count уже было изменено. И в чем же здесь проблема? А в том, что у вас в этот момент потерялась возможность среагировать на это изменение внутри своего компонента и сравнить его со старым (или новым) состоянием. В реакте в классовых компонентах есть некоторые методы специально для таких случаев, например shouldComponentUpdate, который дает возможность вам решить стоит ли ререндерить компонент в ответ на полученные изменения, и componentDidUpdate, который выполняется сразу после обновления компонента (при поступлении новых свойств или изменении состояния). Логика обоих методов в том, чтобы иметь возможность сравнить старое и новое состояние и в зависимости от изменений решить стоит ли еще что-то предпринять. Вот давайте добавим эти методы в правильно написанном классе:
shouldComponentUpdate(_props, newState) { console.log('shouldComponentUpdate oldState', { ...this.state }); console.log('shouldComponentUpdate newState', { ...newState }); return true; } componentDidUpdate(_oldProps, oldState) { console.log('componentDidUpdate oldState', { ...oldState }); console.log('componentDidUpdate newState', { ...this.state }); }
Кликнем increment и посмотрим вывод в консоль:
shouldComponentUpdate oldState {count: 0} shouldComponentUpdate newState {count: 1} componentDidUpdate oldState {count: 0} componentDidUpdate newState {count: 1}

Как мы видим, в этих методах действительно присутствуют разные значения в старом и новом состоянии. То есть здесь мы легко можем написать логику типа
if(oldState.count !== this.state.count) { // ... some logic }

А теперь сделаем вызов на неправильном компоненте и посмотрим результат:
shouldComponentUpdate oldState {count: 1} shouldComponentUpdate newState {count: 1} componentDidUpdate oldState {count: 1} componentDidUpdate newState {count: 1}

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

В общем, старайтесь избегать таких подводных камней. Это может привести к тому, что состояние изменится, а ререндеринг в DOM не выполнится, потому что старое состояние будет равно новому.
>> Не очень понял, почему, но мне вообще оказалось не нужно распарсивать JSON.
Это потому что в GraphQL-схеме у тебя задан тип JSON.
{ "name": "content", "description": null, "args": [], "type": { "kind": "SCALAR", "name": "JSON", "ofType": null }, "isDeprecated": false, "deprecationReason": null },
GraphQL-сервер сам на лету обрабатывает JSON-поля. Точнее чистый GraphQL таким не занимается, но apollo-server да.

>> здесь все равно поставил :any, так как не нашел иного типа для объекта data

С any тут все понятно: у тебя для моля задано JSON. Сам же наверняка понимаешь, что по сути там может прийти любая валидная JSON-строка, так что при парсинге никак нельзя быть уверенным, какая конечная структура получится. Тем не менее, прелесть any в том, что ты легко этому можешь задать свой тип заместо any. Пример:


Как видишь, здесь я для data уже описал более четкую структуру, прописав, что это объект, который содержит свойство src, тип которому - строка (правда обязательно ли содержит или нет, это тебе уже виднее, а то может там должен быть тип src?: string | null).
А вот text я не описал, потому вот ошибки появились, надо прописать.

Но это в случае, если у тебя там структура заранее известна и она такая и есть. Ежели ты предполагашь, что там в итоге просто будет объект с произвольными полями, то можешь так описать:
data: Record<string, string>
Тогда будет доступно любое свойство, а его значение будет расценено как строка.

Николай, привет!
Посмотри, пожалуйста, исправленный вариант: https://github.com/linklib/miniwar/commit/963bea3dbc40ea5e49f813cd441ffffd29e8d342

Не очень понял, почему, но мне вообще оказалось не нужно распарсивать JSON.

И вопрос: здесь все равно поставил :any, так как не нашел иного типа для объекта data: https://github.com/linklib/miniwar/blob/963bea3dbc40ea5e49f813cd441ffffd29e8d342/src/pages/Posts/Post/View/index.tsx#L8
Явно что-то на базовом понтийном уровне не выстроено в башке, вот хочу разобраться...
Николай, спасибо! Дорабатываю код.
В целом, да. Но это если говорить про вопрос передачи поста во вьюху. Но вот с парсингом контента и дальнейшим его выводом у тебя все очень плохо.

1. JSON.parse() - это очень капризный метод. Если что-то не так, он разваливается с критической ошибкой. То есть у тебя приложение рискует частенько разваливаться. Сам попробуй в консоли, к пример, выполнить такое:
JSON.parse('')
Получишь ошибку. Если у тебя такое приведет к ошибке в реакт-компоненте, то у тебя вся страница развалился. Методы объекта JSON надо оборачивать в try/catch.

2. У тебя content имеет тип any, хотя ты даже сам в случае отсутствия объекта post передаешь по умолчанию в content пустую строку. При этом далее выполняешь content.blocks.map(....). Попробуй-ка выполнить content.blocks.map() на пустой строке.

Резуме: технически твой код может работать (если залетели правильные данные), но он очень ненадежен и обязательно даст сбои, если данные залетят невалидные. У тебя даже эслинт подсказывает "Здесь у тебя any, задай нормальный тип".
Это бардак. Такой бардак допускать нельзя. Прорабатывай типы более четко.
Николай, если будет время, посмотри, пожалуйста, правильно распутался или не очень: https://github.com/linklib/miniwar/commit/c5d27e078801c8198171cda80e2d40e5fecc8b92