All streams
Search
Write a publication
Pull to refresh
47
0
Дмитрий Казаков @DmitryKazakov8

Front-end архитектор

Send message
Благодарю за статью и хорошо структурированный код. Сам незримо участвовал для получения опыта — за пару дней собрал рабочий прототип (тоже canvas), определил моменты в которых был слаб и точечно прокачал. Отдавая себе отчет, что первое-второе место для javascript-разработчика (а участвовало их много) взять будет неоправданно сложно, а поощрительные призы экономически невыгодны, дальше дорабатывать не стал. А пара дней на то, чтобы расшевелить мозг новыми задачами — это удовольствие.
Поддерживаю, статья вообще ни о чем, конкретики никакой. Хранить утилиты в отдельной папке, использовать чистые функции, думать перед тем как писать код… Для кого это все
Видимо, напишет еще одну часть, в которой будут новые глупые ошибки
Спасибо, вы правы, подход createProxy(store).registration.email.toString() / toString(createProxy(store).registration.email) может облегчить рефакторинг, если совпадут факторы: в проекте используется типизация, она сделана корректно, IDE ее понимает и корректно меняет параметры при рефакторинге.

В остальных случаях строка подходит отлично — findAndReplace 'store.registration.email' справится с той же скоростью и надежностью, пожалуй я перебдел в данном случае.
Хотя если проект на MobX то лучше storePath все же строкой, чтобы компонент формы не подписывался на обновление значения конкретных полей.
Стараюсь не вписываться в такие проекты — жизнь одна, и если оглядываешься и видишь что полгода не развивался вообще, а писал бизнес-логику и правил баги, в постоянной спешке и стрессе, то должны быть мощнейшие другие мотивирующие факторы. В госкомпаниях и их подрядчиках таких факторов как правило немного. Благо живем в мире, переживающем глобализацию, и найти интересный и хорошо оплачиваемый проект вполне возможно
Для сервиса шаблонизированных темплейтов на node.js я использовал Marko — он на порядок быстрее Pug, кстати, так как преобразует все в чистые функции. Выдерживал прессинг танка с коронными 4мс скоростью ответа (вход-выход по nginx). Возможно, пригодится

Реакт да, тормоз в этом смысле, но у него ssr это побочная фича, которая мало кому нужна
Полностью согласен про «лучшее из возможного» — все эти презентации, встречи и т.п. нужны для того, чтобы согласовать разумные сроки. Мне встречались такие кейсы:
— сроки назначены сверху (или клиентами), часто уже просрочены, и абсолютно оторваны от реальности. Здесь обучение руководства с помощью презентаций и маппинг их радужных представлений с реальностью необходимы, иначе проект просто умрет. Например, от сильного прессинга, под которым невозможно станет работать.
— сроки сильно приуменьшены, но реальны — как принято говорить, «минимально необходимы». В этом случае нужно донести, что бизнес-задача будет выполнена, но образуется техдолг, который будет замедлять и замедлять развитие проекта, до состояния когда простейшая задача будет выполняться полдня или целый день и приводить к непредсказуемым багам. На моем опыте в такое состояние проект приходит в среднем за полтора года, при подобной схеме управления.
— сроки адекватные, техдолг минимальный
— сроки раздутые — тут уже работу нужно проводить с командой, чтобы не распалялась на «а давайте все перепишем на новую хайповую технологию» и другие бесполезные в основном затеи.

Если работаю в компаниях с первыми двумя «управленческими схемами», и при этом «предоставьте Ваше видение мы им подотремся», варианта два — проверить себя, как долго сможешь там отсидеть, либо найти более адекватных ребят.
Достаточно классический разговор — в молодости по максимализму не раз вел такие, отстаивая идеализированный мир, где важны не деньги, а самореализация и позитивный след в истории, способствующий развитию. Со временем баланс выровнялся, и примерно ["деньги", "реализация и развитие", "польза для общества"].test(benefit => weight(benefit) === "33.3%") === true. Для меня этот баланс поддерживает гармонию компонентов в системе «личность».

Я не могу сказать, что что-то здесь для меня вторично. Вполне реально найти компанию, отвечающую такому соотношению, как, в общем, и любому другому. У вас деньги занимают 90%? Найдете и такую компанию. Хотя куда проще было бы для подобной цели работать чиновником.

К слову, достижение корректного соотношения в целях работает не только параллельно, но и последовательно — я могу устроиться на проектную работу ради денег на 2 месяца, а после его завершения — заняться обучением и творчеством, волонтерством на 2 месяца. В случае, если предложений от гармоничных компаний (на мой взгляд) в данный момент нет.
Пока (?) не перекупили, делюсь опытом для тех, кого тоже пока (?) не перекупили. Мне довелось пока (?) не превратиться в разработчика-мебель, за бонусы готового не раздражать начальство своим мнением и опытом, и исправно создающим сотни багов и мучений пользователям своим быстрокодом.
А про все то что вы описываете я уже сказал — «Не с первого раза, так с десятого», зависит от того, зачем вы вообще занимаетесь программированием, и если только за деньги — возможно, это не ваше.
react-router ну очень многого не умеет, тоже думаю о том, чтобы написать статью про проектирование качественного роутера. И про ssr на этом стеке тоже тема актуальная, хотя его, по моим прикидкам, сделать намного проще.
Хорошая практика — везде использовать именованные экспорты / импорты. Вместе import Field from './field' лучше использовать import { Field } from './Field'. Иначе готовьтесь к Fild / Feld и т.п. переменным по недосмотру.

Непрофессиональный цикл статей, на мой взгляд. Даже в простейших примерах явные ошибки. То setState как будто синхронный, то текстовые пропы от руки каждый раз пишутся вместо констант, то вот label здесь без htmlFor. Если кто-то все же читает, лучше используйте такой паттерн:


<Input.Text
  label="Name"
  storePath="forms.registration.email"
/>

// и внутри компонента

const id = generateId(props.storePath);

return (
  <div>
    <label htmlFor={id}>{props.label}</label>
    <input type="text" id={id} value={getValueFromStore(props.storePath)}/>
  </div>
)

Акцент на автоматической генерации id, удобстве получения данных из стора, и фокусировании инпута при клике на label.


В примере storePath строкой задано, что при рефакторинге может вылезти в баг, поэтому тоже лучше генерировать storePath={pathToString(forms.registration.email)}

Дипломатические методы — обсуждение, убеждение, презентации для руководства разного уровня — могут принести достаточно ресурсов, чтобы сделать большую часть проекта качественно. Я в нескольких компаниях на цифрах показывал, сколько убытков в деньгах и времени приносит некачественный быстрокод и засилье «дешевых» разработчиков в проекте — и если для руководства важен бизнес и результат, а не распил, то непременно пойдут навстречу. Не с первого раза, так с десятого.

Удобнее было бы не контролировать с помощью PropTypes постфактум, а дать сразу набор возможных значений


Button.appearance = {
  default: 'default',
  primary: 'primary',
  secondary: 'secondary',
}

<Button appearance={Button.appearance.primary}>Click me</Button>
«Извращение», «все неправильно» и особенно «все уже поняли что всем нужна типизация», как модно было говорить в докладах на недавних конференциях, для меня не аргументы использовать эту технологию.

Если бы я знал о явных преимуществах и они стоили всех мучений и долгого обучения — как вы правильно заметили, за полгода команда довольно сильных специалистов не научилась «правильно работать со строгой типизацией», хотя в основном проблемы зависели не от команды, то можно было бы дальше возиться с этим и обильно осваивать бюджет компании. Но нет. Предпочитаю делать продуманные качественные продукты и приносить пользу, а не возиться с почти бесполезными инструментами, не двигающими к этой цели.

Я не говорю за всех разработчиков и за все проекты — как я писал выше, в отдельных библиотеках использование типизации скорее оправдано, а в сложных многосоставных системах вроде SPA явно негативный эффект — замедление разработки без видимых профитов. IMHO
Я описал только малую часть проблем — каждый день несколько часов работы команды уходило на типизацию и борьбу с негативными эффектами. Разумеется, все можно победить — вопрос зачем? Пользу от типизации в SPA я до сих пор не увидел.

Ошибки в типах в рантайме только увеличились. Рефакторинг стал сложнее — в ES6 проекте 90% рефакторинга выполняется findAndReplace с помощью регулярок, с типизацией работа удваивается. Переменные как были с опечатками или не отражающие назначения, так и остались. В опенсорс-зависимостях очень часто выходят версии с некорректными типами, ломающими сборку, и приходится форкать и вручную исправлять. Перечислять минусы могу еще долго.

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

Доброго дня.


Undo/Redo на большую глубину, сериализация состояния и истории того, как приложение в это состояние попало



тривиально реализуется через autorun. Можно делать глобальные слепки -


autorun(() => snapshots.push(JSON.stringify(store)))

очищая от функций и ненужных данных, разумеется, либо пушить только изменения конкретных данных


autorun(reaction => reaction.observing.forEach(
  ({ name, value }) => changesHistory.push({ name, value, prevValue })
)

Проигрывание этой истории и логирование последних действий при возникновении глобальной ошибки, полагаю, тоже сделать несложно — мне не пригождалось, так как стараюсь решать проблему поиска причины залогированного бага с помощью именованного stackTrace, типизированного error.name и человекопонятного error.message.


Поэтому я бы не рассматривал встроенный в Redux механизм истории как серьезный аргумент в его пользу — если он действительно нужен, реализовать его довольно просто.


По теме гонок и каскадных обновлений Redux, как вы правильно заметили, своим диспетчером решает проблему «частично», и мне, к сожалению, приходилось работать в приложениях, где «все остальные части» не решались, и они очень страдали от нагромождения бойлерплейтов и дополнительных библиотек и мидлвар, в которых крайне сложно было создать стабильный флоу. Это не камень в огород Redux, я скорее о том, что проектирование архитектуры никто не отменяет.

src/utils/withState.js
export function withState(target, fnName, fnDescriptor) {
  const original = fnDescriptor.value;

  fnDescriptor.value = function fnWithState(...args) {
    if (this.executions[fnName]) {
      return Promise.resolve();
    }

    return Promise.resolve()
      .then(() => {
        this.executions[fnName] = true;
      })
      .then(() => original.apply(this, args))
      .then(data => {
        this.executions[fnName] = false;
        return data;
      })
      .catch(error => {
        this.executions[fnName] = false;
        throw error;
      });
  };

  return fnDescriptor;
}

src/stores/CurrentTPStore.js
import _ from 'lodash';

import { makeObservable, withState } from 'utils';
import { apiRoutes, request } from 'api';

@makeObservable
export class CurrentTPStore {
  /**
   * @param rootStore {RootStore}
   */
  constructor(rootStore) {
    this.rootStore = rootStore;
    this.executions = {};
  }

  @withState
  fetchSymbol() {
    return request(apiRoutes.symbolInfo)
      .then(this.fetchSymbolSuccess)
      .catch(this.fetchSymbolError);
  }
  fetchSymbolSuccess(data) {
    return Promise.resolve();
  }
  fetchSymbolError(error) {
    console.error(error);
  }
}

src/components/TestComponent.js
import React from 'react';

import { observer } from 'utils';
import { useStore } from 'hooks';

function TestComponent() {
  const store = useStore();
  const { currentTP: { executions } } = store;

  return <div>{executions.fetchSymbol ? 'Загружается...' : 'Загружен'}</div>;
}

export const TestComponentConnected = observer(TestComponent);

Вроде полностью соответствует вашему описанию, только в виде объекта с ключами выполняемых асинхронных действий. Это выгоднее, так как позволяет обновлять компонент только при обновлении стейта конкретного действия — если же использовать массив (loaders.indexOf('fetchSymbol') !== -1), компонент будет обновляться и при попадании в этот массив всех других, ненужных для данного компонента стейтов. Хотя в Redux можно сделать селектор для этого. Глобальный лоадер можно завязать на Object.entries(executions).some(([key, value]) => value === true).


Разумеется, на любом движке или ванильном JS можно сделать то же самое, просто на MobX это очень просто и удобно.

Information

Rating
Does not participate
Location
Москва, Москва и Московская обл., Россия
Date of birth
Registered
Activity