All streams
Search
Write a publication
Pull to refresh
12
0
Сергей @redyuf

Программист веб и не только

Send message
Поленился сделать еще один фидл, взял из того, что было в статье, по-сути там разница в одной button.

Если уж совсем быть точным, то вот совсем эквивалентный пример на MobX и lazyObservable, с ленивой загрузкой и абстракцией от способа получения данных.

Пример на MobX
class TodoList {
    constructor() {
      this.todoContainer = lazyObservable(sink => sink(fromPromise(fetchSomeTodos())))
    }

    @computed get unfinishedTodoCount() {
        const todos = this.todoContainer.current()
        return todos && todos.status === 'fulfilled' 
            ? todos.filter(todo => !todo.finished).length
            : []
    }
}

const TodoListView = observer(({todoList}) => {
    const todoContainer = todoList.todoContainer
    const todos = todoContainer.current()
    return <div>
        {todos && todos.state === 'fulfilled' 
            ? <div>
                <ul>
                {todos.value.map(todo => 
                    <TodoView todo={todo} key={todo.id} />
                )}
                </ul>
                Tasks left: {todoList.unfinishedTodoCount}
            </div>
            : <ErrorableView fetchResult={todos}/>
        }
    </div>
})
fiddle
Основной бойлерплейт в компонентах и computed-свойствах. Моя позиция такова: error и status — просачивающиеся за пределы модели приватные детали работы с каналом связи. Как можно от них избавиться совсем?

Я не нашел реализации lazyFetch, сложно судить. Но тут проблем больше, чем кажется на первый взгляд. Например, только некоторые из них:

А если надо запустить загрузку и отдать дефолтное значение?
Что будет, если в случае ошибки сперва будет запрошен unfinishedTodoCount?
Как заставить принудительно еще раз вытянуть значение с сервера?
Что будет, если у нас не промисы, а например Observable или вообще колбэки?
Как провернуть такую же штуку, только для сохранения данных, причем разделять запись в кэш и запись на сервер?
Что будет если где-то в observable или computed произошло исключение? Как нарисовать вместо сбойного компонента крестик, не руша все приложение?
Не очень понял вопрос. Что вы считаете хэндлером в MobX?
В MobX observable свойство — это обычно только данные. Mobx решает задачу как обновить состояние, что б точно перерендерилось необходимое.
Но не решает задачу, как абстрагировать компоненты от способа загрузки данных. Есть куча хелперов поверх mobx, которые пытаются это делать, но они не решают проблему бойлерплейта и инкапсуляции, компоненты все-равно знают о статусах.

В атомах, хэндер — часть спецификации ядра, на которую возложена задача абстракции канала связи, fetch. За сцену убираются детали, вроде pending/success/error.

В итоге получается значительно меньше шаблонного кода. Сравните 2 эквивалентных примера:

Пример на lom_atom
class TodoList {
    @force $: TodoList
    @mem set todos(next: Todo[] | Error) {}
    @mem get todos() {
        fetchSomeTodos()
           .then(todos => { this.$.todos = todos })
           .catch(error => { this.$.todos = error })
        throw new mem.Wait()
    }
    @mem get unfinishedTodoCount() {
        return this.todos.filter(todo => !todo.finished).length;
    }
}

function TodoListView({todoList}) {
  return <div>
    <ul>
      {todoList.todos.map(todo =>
         <TodoView todo={todo} key={todo.id} />
      )}
    </ul>
  </div>
}
fiddle

Пример на MobX
class TodoList {
    @observable todos = [];
    @observable pending = false
    @computed get unfinishedTodoCount() {
        return this.todos.filter(todo => !todo.finished).length;
    }
    @action fetchTodos(genError) {
        this.pending = true
        this.error = null
        fetchSomeTodos(genError)
           .then(todos => { this.todos = todos; this.pending = false })
           .catch(error => { this.error = error; this.pending = false })
    }
}

const TodoListView = observer(({todoList}) => {
    return <div>
        {todoList.pending ? 'Loading...' : null}
        {todoList.error ? todoList.error.message : null}
        <ul>
            {todoList.todos.map(todo => 
                <TodoView todo={todo} key={todo.id} />
            )}
        </ul>
        Tasks left: {todoList.unfinishedTodoCount}
        <br/><button onClick={() => {todoList.fetchTodos(true)}}>Fetch with error</button>
    </div>
})
fiddle
Тут есть куча нюансов. Производительность, масштабируемость, читаемость.

Если рассмотреть технологию с позиции правил для хорошей архитектуры, например SOLID. То в компонентах очень многое покажется спорным архитектурным дизайном.

Как, например, делать компоненты открытыми для расширения, закрытыми для модификации? Представление верстки в виде шаблона, композиции элементов — не дает ответ на этот вопрос.

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

Если выбирать между чистым кодом, не зависимым от окружения вовсе и кодом зависимым как-либо, то первое предпочтительнее, при прочих равных.
Там не хватало некоторых принципиальных фичей на базовом уровне, декораторами это не сделать. Основное отличие в идее, что свойство в классе — это не просто данные или computed-функция, а данные+хэндлер, который срабатывает на чтение и запись.

Еще обработка исключений построена иначе. Если начать рефакторить mobx, то каша из топора получится.

Да и весь алгоритм в 300 строк получается.
Может я что-то не понимаю, но как тогда создать компонент без наследования от HTMLElement. Это разве не прямая зависимость от реализации?
export class TodoElement extends HTMLElement {
 ...
}


визуальное представление с минимальной логикой компонентов-виджетов.

Скажем, компонент с инпутом и выводом его значения минимальная логика? На чистом WebComponents будет более громоздко по сравнению с mobx и чистым компонентом.
А почему не должно смущать и в любом ли месте приложения?

1. Если речь идет о WebComponents, то он не избавляет от когнитивной нагрузки на программиста: код, кроме бизнес логики содержит мусор в виде конструкций для связывания. Как в WebComponents достичь уровня расширяемости, как у вас со стилями и Button/Popup?

2. В случае работы со стейтом, часто навязывается opinionated подход с actions/reducers, setState.

3. Свобода выбора есть, но выбора vendor lockin. Выбрав что-либо из этого, мы завязываемся на реализацию, а не на спецификацию. И поменять реализацию без переписывания не можем (сколько одинаковых бутстрапов есть на разных фреймворках? А ведь если верстка — чистая функция, можно было бы ее адаптировать ко многим фреймворкам).

Следуя этой логике (завязка на спецификации, а не реализации), можно предположить, что чистые компоненты на JSX и mobx — это vendor lockin в меньшей степени, а спецификации в большей, причем простые: композиция функций и классы без наследования.

Как следствие такой ненавязчивости: mobx лучше масштабируется, появляется выбор: использовать чистый mobx или надстроить над ним mobx-state-tree и получить преимущества redux.
А почему, можно поинтересоваться? Только потому, что это типа стандарт?

А если у WebComponents хороший архитектурный дизайн, то в чем это заключается?

Почему тогда столько времени он остается непопулярен? Почему много проблем с масштабированием в том же полимере?

WebComponents разве не очередной vendor lockin, только уже от API браузера. А ведь компоненты — более широкое понятие чем веб, применимое и для мобильных платформ.

Что может быть проще чистых функций и mobx-подобных объектов с данными? При этом у функции есть контракт — описание типов аргументов и зависимостей, в отличие от спецификации шаблона. Такая система почти не зависит от внешнего API и код — это чистая верстка с бизнес логикой, без вкрапления фреймворкового API. Что упрощает запуск ее где-либо еще, кроме браузера.
В IoC не только по типу, это для 80% случаев достаточно типов, в остальных — используются декораторы уточняющие.

Селекторы не типобезопасно, легко выстрелить в ногу. Можно много вариантов придумать, в JSX они будут все корявые. Например, можно сделать уникальные компоненты на основе Button или использовать уточнения:
function Select() {
  return <div >
     <Button.left />
     <Button.right />
 </div>
}
const MySelect = clone(Select, [
  [Button.left, MyLeftButton],
  [Button.right, MyRightButton]
])

Button.left — генерирует и кэширует уникальный Button с таким же интерфейсом, но другим id.

У вас в tree — уточнения, это названия методов в классе, который из tree генерируется. В композиции автоматически так не сделать, остаются только подобные компромиссы.
Просто вы написали:
Не импонирует тем, что это большой черный ящик и то, что это по-прежнему tech-lock на React.
Вот я и попытался узнать, не импонирует только Styled или вообще вся экосистема реакта (да и фронтенд в целом), т.к. пока не существует фреймворков, полностью построенных на интроспекции, где код приложения не переплетался бы с инфраструктурным кодом, хотя задача интересная и вполне осуществимая.
Разве arui-feather/cn, да и сам реакт — не ограничение в выборе инструментария?
Где граница нормы?
Про более короткую запись. Не рассматривали подобные варианты?
import bem from 'bem'
const SelectedTheme = bem('SelectedTheme')

function Select(props, {theme}: {theme: SelectTheme}) {
  return <div className={theme}>
     <Button />
     <Popup />
 </div>
}

const MyLinkSelect = clone(Select, [
  [Button, MyButton],
  [SelectTheme, MyLinkSelectTheme]
])


Здесь можно добиться хорошей типобезопасности, SelectTheme может быть функцией, объектом, классом. Button и Popup не надо объявлять как аргументы.
А что значит tech-lock?

Например, когда в коде пишем import cn from 'arui-feather/cn' или extends React.Component или когда бабел генерирует из JSX код с React.createElement, это не tech-lock?

Просто по мне, не tech-lock, когда в коде приложеня импортов нет совсем, только чистые функции и классы без наследования (POJO), а работоспособность и связывание обеспечивается интроспекцией.
На мой взгляд, эта идея не годится для больших приложений. Хороший DI нужен, только если он не прибит к реакту, поддерживает типы и не ломает интерфейс компонент.

1. Где гарантия, что аргументы в декораторе расположат в том же порядке, что и в render. Когда их больше 2х-3х, это может больно ударить. Все это напоминает старый добрый require.js.

2. Точки расширения тут (переопределяемые компоненты) надо проектировать сразу (перечислять аргументы в render), они не получаются автоматически. Лучше наоборот, по аналогии с классами, где мы если явно не указываем private методы, то можем их переопределить в наследнике.

3. Метод render не имеет в flowtype или typescript аргументов. В некоторых клонах реакта, вроде preact, туда приходят 2 аргумента: props и state. А тут еще одна самопальная спецификация: жестко прибиваем к cn, Button и Popup.

Для компонент можно попробовать сделать DI через подмену createElement, тогда декораторов не надо. createElement можно использовать как service locator.
const aliasMap = new Map([
  [Button, MyButton]
])

function h(el, ...args) {
  return React.createElement(aliasMap.get(el) || el, ...args)
}

Еще можно использовать метаданные, сгенерированные бабелом для поиска зависимостей и кидать в контекст реакта.
class A { name = 'test' }
function MyComponent(props, {a}: {a: A}) {
  return <div>{a.name}</div>
}


Можно генерить что-то вроде MyComponent.deps = [{a: A}], а createElement уже по этим данным найдет нужную зависимость. Есть даже плагины вроде babel-plugin-flow-react-proptypes, который подобным занимается, только для других целей.

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

Вот, кстати да, с переменными. Где та грань, когда достаточно переменную поменять в css, а когда менять целиком стиль? Много раз задавал себе этот вопрос. Ведь можно рассуждать так: css тесно связан с версткой, а структурные изменения в стилях == структурные изменения в верстке == новый компонент.

Я экспериментировал с css-in-js применительно к dependency injection и идее атомов от vintage. У меня стиль — функция с зависимостями от других функций. Причем css реактивно пересоздается при изменении зависимостей.
Пример подмены стилей
...
class ThemeVars {
  @mem red = 100
}

function TodoListTheme(themeVars) {
  return {
    wrapper: {
      background: `rgb(${themeVars.red}, 0, 0)`
    }
  }
}
TodoListTheme.theme = true
TodoListTheme.deps = [ThemeVars]

function TodoListView({todoList}, {theme, themeVars}) {
  return <div className={theme.wrapper}>
    Color via css {store.red}: <input
      type="range"
      min="0"
      max="255"
      value={themeVars.red}
      onInput={({target}) => { themeVars.red = Number(target.value) }}
    />

    <ul>
      {todoList.todos.map(todo => 
         <TodoView
             todo={todo}
             key={todo.id} />
      )}
    </ul>
    Tasks left: {todoList.unfinishedTodoCount}
  </div>
}
TodoListView.deps = [{theme: TodoListTheme, themeVars: ThemeVars}]
const store = new TodoList();

ReactDOM.render(<TodoListView todoList={store} />, document.getElementById('mount'));
fiddle

Для подмены компонентов не обязательно их объявлять в декораторе и прокидывать в render. Еще можно идентифицировать зависимость не по позиции, а ассоциативно, что на мой взгляд, выглядит более понятно и легче типы проверять.
Пример подмены компонент с reactive-di
...
function SomeView() {
  return 'SomeView'
}

function TodoView({todo}) {
    return <li>
        <input
            type="checkbox"
            checked={todo.finished}
            onClick={() => todo.finished = !todo.finished}
        />{todo.title} #{todo.id}
        <br/><SomeView/>
    </li>
}

function MySomeView() {
  return 'MySomeView'
}


const ClonedTodoView = cloneComponent(TodoView, [
  [SomeView, MySomeView]
])

const TodoListViewCloned = cloneComponent(TodoListView, [
  [TodoView, ClonedTodoView]
])
const todoList = new TodoList();
ReactDOM.render(<TodoListViewCloned todoList={todoList} />, document.getElementById('mount'));

fiddle

Еще непонятно, почему к реакту прибивается гвоздями то, что не имеет отношения к компонентам. Тот же DI, алиасинг — никакого отношения к компонентам не имеет.

И как такую штуку заставить работать с типами, ведь строковые ключи в декораторах убивают возможность типизации напрочь. Можно конечно поиграться с Symbol, но по мне, это не совсем то.
Не очень понял разницу «редакс или экшен/редьюсер».
Да, я ошибся, имелось в виду setState или экшен/редьюссер. Но теперь я понял про recompose с withState. Для меня важен подход, когда есть прозрачность интерфейсов, везде и точно работает выведение типов без костылей вроде utility types. Когда типизация помогает, а не мешает, когда есть единообразие и простота реализации SOLID (хотя бы SO) — это индикатор того, что путь верный. Как только появляются строки для работы со стейтом, возникают затруднение с выведением — это плохой признак.

По мне, даже setState был бы лучше этого. Напомитает подход, который был в React.createClass, описание класса через фабрику, нестандартную спецификацию библиотеки.

Даже если забить на выведение типов, которое тут никогда не заработает, вы действительно считаете, что вот это

Пример на compose
const enhance = compose(
  renameProp('imageUrl', 'thumbnailUrl'),
  withState('name', 'setName', props => props.name),
  withState('favourites', 'setFavourites', []),
  withState('favouriteText', 'setFavouriteText', ''),
  withHandlers({
    updateName: ({ setName }) => event => {
      setName(event.target.value);
    },
    updateFavouriteText: ({ setFavouriteText }) => event => {
      setFavouriteText(event.target.value);
    },
    addListEntry: ({
      setFavourites,
      favourites,
      favouriteText,
      setFavouriteText
    }) => event => {
      event.preventDefault();
      setFavourites([favouriteText, ...favourites]);
      setFavouriteText('');
    }
  })
);

const FavoritesExt = enhance(Favourites);


Проще и понятнее, чем это?

Пример на setState
class FavoritesExt extends Component {
  constructor(props, context) {
    super(props, context)
    this.state = {
      name: props.name,
      favourites: [],
      favouriteText: ''
    }
  }

  updateName = ({target}) => { this.setState({name: target.value}) }
  updateFavouriteText = ({target}) => { this.setState({favouriteText: target.value}) }

  addListEntry = (e) => {
    e.preventDefault()
    this.setState({
      favourites: [this.state.favouriteText, ...this.state.favourites],
      favouriteText: ''
    })
  }

  render() {
    return Favorites({
      ...this.props,
      thumbnailUrl: this.props.imageUrl,
      ...this.state,
      addListEntry: this.addListEntry,
      updateName: this.updateName,
      updateFavouriteText: this.updateFavouriteText
    })
  }
}


Если уж пытаться добавлять сахар, то по мне лучше так:

Пример на reactive-di
class FavoritesService {
  @mem favouriteText = ''
  @mem favourites: string[] = []
  @mem name = ''
  @props set props({name}: {name: string}) {
      this.name = name
  }
  addListEntry = (e: Event) => {
    e.preventDefault()
    this.favourites = [...this.favoriteText, this.favourites]
    this.favoriteText = ''
  }
  updateName = ({target}: Event) => { this.name = target.value }
  updateFavouriteText = ({target}: Event) => { this.favouriteText = target.value }
}

function FavoritesHoc(_: {name: string}, service: FavoritesService) {
  return Favorites({...service})
}


Заметьте, последний пример вообще от реакта не зависит, только от JSX и HOC-ов там в принципе нет. compose же зависит, в нем внутри обычный container-компонент на реакте.

то делается хок (пусть хок#1) сразу для компонента 5-го уровня, который хранит состояние этой галочки и передаёт его как пропс.
HOC — это всегда привязка к конкретной реализации чего-то. В реакте выбора особо нет. Если мало HOC — куча пропсов тянутся до ближайшего HOC, повышая сложность рефакторинга. Много HOC — возрастает сложность переиспользования и появляется копипаст, когда берут тот же чистый компонент и лепят поверх новый HOC. Завязки на реализации проникают на все уровни иерархии приложения.

Предположим галочка теперь приходит из апишки и кладётся в стор сагой, тогда для конкретной вьюшки обновляется селектор (чистая функция)
Возможна только модификация существующего кода, вместо расширения. Вот если бы так работало Хок2(Хок1(Компонент)), как при наследовании. Получается следуем одному принципу в ущерб другому (либо хок, либо возможность расширения), подобная дихотомия много где в реакте встречается.

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

Не очень понял, какие точки входа знают про саги. Сага вещь в себе, она работает только со стором, через стандартные actions и стандартные selectors (эти же экшены и селекторы используются для компонентов).
У вас же центральный стейт, так? Где-то в index.js есть место, где регистрируются все редьюссеры, все саги. Это — детали реализации конкретных частей приложения, утекающие в index.js. По какому событию вы загружаете начальные данные очередного компонента в процессе работы с приложением, где bootstrap происходит? А точно это должно быть в том месте и в то время?

Хорошо, если там есть поддержка id-шников, то вы готовы в поддерживаемых приложениях для каждого дива проставить айдишник?
mol_jsx proof of concept. Я ж написал, что JSX плохой инструмент для идентификации кодовых блоков. Да и много ньюансов есть, не везде надо их проставлять, только если в компоненте ветвления и т.п. Да и так ли страшно это пересоздание, это ж не во всех кейсах происходит. В choo например, на это забили, ну да в тестах он не блещет, но вполне работоспособен для нетяжелых данных. Есть гораздо более легковесные real dom diff алгоритмы, например nanomorph. У винтажа в mol свой diff, с блекджеком.

P.S. И вообще мы далеко отошли от основного вопроса — бессмысленности реакта при наличии mobx
Если развивать идею mobx и под нее делать экосистему, то да, vdom не нужным становится, а diff алгоритм сильно упрощается. Тот вброс Дмитрия в любом случае приведет к обсуждению целого ряда недостатков реакта и экосистемы вокруг него, это системная проблема. Я думаю, со временем архитектурная критика будет только нарастать, например как в «React-стек: скрытые угрозы», Илья Климов
.

Предлагаю либо закругляться, либо в личку перейти)
Да, давайте прервемся и как-нибудь в другой раз продолжим. Слишком много направлений, в комментах тяжко это обсуждать.
Но они не забыли про ручную оптимизацию)
Это как повернуть. На мой взгляд это следствие того, что vDOM не спасал в сложных компонентах и на больших глубинах вложенности. Естественно никто не скажет, мы такие растяпы, не учли это сразу, поэтому простите нас, вот хотя бы shouldComponentUpdate, в ангуларе с его update strategies еще похлеще.

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

Из того issue в vue:
The fully framework-compliant way to deal with it (and is also what I would suggest even in React) is to have a separate piece of state that represents the input content, which can then be out of sync with the model state and can be updated only when you want to.
Я вот согласен с этим и это вовсе не «не нужен shouldComponentUpdate… потому что не нужен». Если выбирать между shouldComponentUpdate и более детальным разбиением на компоненты, лучше последнее.

Здесь и далее много неточностей.
Много, что б было меньше, не могли бы вы привести пример, как вы готовите редакс с сагой? Если на 5м уровне вложенности вам надо галочку на интерфейсе отобразить, тоже через редакс делаете или клепаете экшен/редьюссер? У вас один центральный стор или много инстансов редакса в приложении? Иерархия модулей плоская или фрактальная? Кто делает bootstrap начальных данных саги очередного компонента мини-приложения? Как решается, какой компонент обмазывать HOC-ами и сагами, а какой использовать чистым? Как достигается типобезопасность в HOC, что стейт пришедший в connect, имеет свойство blaBla?

Мне нравится реакт, но не нравятся многие подходы, принятые в мейнстриме реакт-сообщества
Вот-вот, реакт отличается множеством способов выстрелить в ногу, т.к. основа не дает цельного, масштабируемого инструмента и спецификации для комфортной разработки. Поэтому в экосистеме столько решений с дублирующейся функциональностью. Дело конечно еще и в громадном накопленном опыте решений, ради сохранения совместимости костыли неизбежны, неизвестно что еще в дальнейшем произойдет с vue и т.д. Мне интересно, кто оказажется прозорливее и угадает лучше. Вроде vue пока многие вещи пока лучше угадывали.

react-helmet. Суть в том, что на компонентах пытаются программировать не только верстку. Все эти конструкции в верстке, нарушают разделение ответственности, изоляцию и размывают границы между слоями. Уже непонятно, что верстка, а что управляющая конструкция. react-helmet — ничего не выводит в компонент, он сайд-эффект, меняющий title, поэтому я и назвал его контроллером. Эта хрень не должна быть в компонентах. Все это напоминает олдскул php, с его запросами к базе посреди шаблона, только замаскированный под красивый xml-синтаксис.

Кстати, какие же хоки биндинги? Это же просто high orders functions. У нас нет трёх типов компонентов — всё просто функциональные stateless компоненты.
Ох, тут много можно сказать, раз уж пошла речь о HOC. HOC как раз и будет трансферить пропсы в дерево нижележащихся компонентов. Поэтому я и говорил биндинги, намекая на его функцию. Название high orders functions вводит в заблуждение, т.к. это не единственный способ делать связывание компонент с логикой, а задача то именно эта решается.

В ангуларе например, есть нативный конструктор класса, типы и DI для этих же целей. При этом HOC редакса рождает намертво прибитый к редаксу, путям в стейте и экшенам компонент и в этом заключается его непереиспользуемость, нарушается OCP: нельзя расширить компонент без рефакторинга.

Сага с редаксом, правда, дает возможность большую часть логики убрать из HOC, сделав его очень тонким. Правда при этом нарушается принцип инверсии контроля, когда зависимости верхнего уровня (точка входа) знают о деталях нижних, в виде редьюсеров и саг. И это не избавляет от шаблонного кода для связывания, вроде dispatch(bla-bla). Инверсия контроля же позволяет декларативно на любом уровне иерархии переопределять эту связь и для любого компонента вообще, у винтажа похоже, кстати, с его контекстами.

Еще HOC усложняют вывод типов, т.к. всякие connect могут по сложной схеме менять контракт компонентов. Маппинг реальных пропсов в то, что получается на выходе connect не однозначен. Это может быть поправят, т.к. в flow, наряду с костылями «спешал фор реакт», добавили костылей «спешал фор редакс», вроде $Diff или $ObjMap. Правда пока в flow-typed их почему-то не используют. Это не говоря о таких вещах, как вывод типов state в mapStateToProps, что вообще не представляется возможным с текущим flow.
Вывод типов в redux connect
function A(props: {a: number, b: string, onClick: () => void}) { return null }

const mapStateToProps = (state, props) => {
  return {
    active: props.c + props.a
  }
}

const mapDispatchToProps = (dispatch, props) => {
  return {
    onClick: () => {
      dispatch({some: props.filter})
    }
  }
}

const AHoc = connect(mapStateToProps, mapDispatchToProps)(A)
flow/try

Возможно я нахожусь в каком-то локальном экстремуме, но я совершенно не вижу, как можно большой объём переиспользуемой логики (продукт — личный кабинет, высокодинамичный SPA с SSR) представить в виде вороха компьютед и обсёрваблов
Если обсервабл выглядит как обычный класс с методами и свойствами, зависимости которого в конструктор инжектятся, то тестировать и мокать легко. Это кому что удобнее, вечный холивор ООП vs ФП. Никто не лучше, инструментов и там и там достаточно. У меня другая жизненная школа, я за ООП, SOLID и т.д, поэтому экспериментирую с этим в реакт, т.к. уверен, что результат будет понятнее и проще ООП-шникам.

Ну вот в Ангуляре есть те же самые обсёрваблы. Что-то там не очень упростилось. В чём подвох?
О, не те же самые. Все дело в акцентах. Попробую систематизировать сорта «обсерваблов»:

RxJS, beacon, kefir, most, pull-stream, ramda, отчасти es observable — разной степени удачности и монстроидальности попытки сделать ФП в js. Отличаются навязчивым API. Т.е. приложения на них — это вкрапления нативного синхронного кода в апи этих библиотек. Вы выстраиваете поток простых вычислений, вставляя их в огромное кол-во точек расширения библиотеки или заменяете все операции на функции. Грубо c = add(a, b) вместо c = a + b. Для ООП-шников это выглядит как слишком много мусора и, в целом, сложно для освоения. Лично я считаю, если в языке нет из коробки средств для ФП, то не надо их туда тащить, это не в философии языка.

Baobab и д.р. переходные формы, популярные пару лет назад решения на основе single state observable дерева, концепция проще чем у стримов, т.к. в основном есть только источник observable и производная computable.

mobx — дальнейшее упрощение концепции стримов, когда стримы замаскировали под нативные классы и объекты, избавив ООП-шников, коих большинство, от изучения новых концепций и дав свободу от архитектурных стилей, можно самому лепить из классов что угодно. Фишка mobx в автоматическом связывании частей вычисления, когда вы пишите c = a + b и этого достаточно, что б при обновлении a, обновился и c. Т.к. это упрощение, многие, но не все задачи на нем красиво и просто решаются, поэтому Мишель в своем в своем faq и написал When to use RxJS instead of MobX.

mol_atom от vintage, cellx от Riim и lom_atom моя имплементация атомов Дмитрия для реакта — Еще более упрощенный mobx, есть только одна сущность — атом. Причем данные сами синхронизируют себя с асинхронным стором по факту обращения к ним. Это ключевой момент, отличающий их от всего, что мне известно. То, с чем vintage носится столько лет и говорит как про pull, вытягивание. Напоминает идею инверсии контроля, только для observable-данных.

Используете вы RxJS, mobx (хотя и в нем можно через хелперы, но как я выше говорил, все дело в акцентах), редакс с сагой или еще что, ответственность по актуализации лежит за потребителем данных, componentDidMount, bootstrap саги с ручным явным вызовом или не важно как там это замаскировать.

Нарушается инкапсуляция и SRP, т.к. факт необходимости загрузки — это деталь, которая просачивается в вышележащий уровень. В сложных приложениях, при рефакторинге нижнего уровня, неизбежно надо рефачить верхний, что черевато багами. Это винтаж имеет в виду, когда говорит про багоемкость.

То, что технология не популярна, может также означать, что у автора нет соотвествующих качеств/ресурсов для ее популяризации. Первоначальную идею я встретил у винтажа в его jin.atom в 2014 году и никакого mobx-а тогда небыло. Идея c = a + b теперь такая же в mobx. Ноосфера, она такая, тут Мишель верно угадал и смог продать.

Насчёт ближе к нативному js — ой как спорно, ведь что может быть проще, чем просто функции и просто объекты
Спорно, так спорно. Это не просто функции и объекты. Важен смысл, который за ними, в mobx этих смысловых сущностей меньше и они выглядят проще, иными словами более простой core concept, который ближе к нативным классам. user.name = 'test' вместо dispatch({type: 'CHANGE_USER_NAME', name: 'test'})

Вообще, я имел в виду приведенную вами статью Мишеля:
Probably, it is more accurate to talk about MobX as a data flow library, that enables you to roll your own state management architecture with minimal effort.

Верно, id-шника нет — значит пересоздаём, потому что неизвестно какая нода
Ваше заявление было категорично: «Нет, не так, mol_jsx их пересоздаёт.». Это не так, если id-шник есть. По мне, малая плата для достижения эффекта точечного обновления DOM без vDOM и реконсиляции. Вот то, что JSX для идентифкации кодовых блоков подходит хреново и совсем без ручной расстановки id-шников не обойтись, это другой вопрос. В идеале бы свойство языка таким сделать. Кроме этого, идентификация помогает идеально следовать принципу open/close во всем приложении, чего пока нет нигде, кроме mol. Вот поэтому vintage и выдумал свой tree и пытается его продать. В этом есть рациональное зерно.
Модифицируйте пример с треугольником Серпинского так, чтобы все кружочки всегда попадали на экран — мол очень сильно просядет.
Пока не понял что модифицировать, вроде столько же кружочков, у меня не тормозит.

Во Vue со второй версии тоже есть vDOM, и есть shallowCompare, но все равно есть проблемы с производительностью, к которым подойдёт ручная настройка.
Чем меньше деталей о реакте приложение знает, тем лучше, зачем лишний инфраструктурный код. По мне, лучше сперва научить фреймворк проблему решать автоматически, только когда уже все способы исчерпаны, дать еще один способ выстрелить в ногу, ручной оптимизацией. Возможно автор vue считает также.

Я счастлив, я меняю состояние компонентов через пропсы, и далеко не всё храню в глобальном сторе. И не использую зоопарк
Пропсы никто не отменяет. Отказ от сырых данных в пропсах для всех компонент это неудобно, да. В реакте пока мы видим компромисс, когда только некоторые компоненты являются observables, а дальше задача ложится на чистые компоненты и vDOM. Но это из-за того, что реакт изначально не предполагалось использовать с такими структурами данных. Vue, angular и пресловутый mol как раз более приспособлены для них, хотя в первом пока тоже vDOM на edge-cases используется.

Зоопарк бывает не только библиотек, но и идей, когда нет единообразия в смыслах и одной ответственности. Кроме чистых компонент вы используете HOC-и и компоненты с setState, если не все храните все в глобальном сторе. По мне, так уже зоопарк из 3х типов компонентов, появившихся из-за компромиссов, на которые пошли, когда менять основу было уже поздно.

Чистые кастомизируются хорошо, HOC плохо, т.к. к предоставляют биндинги к стейту на редаксе и прибиты к бизнес логике, с setState тоже самое, но без редакса и стейт с логикой уже нельзя отделить от компонента и от реакта. Понятие компонент вообще размыто. Есть presentational компоненты (View), container-компоненты с данными и логикой (Model), компоненты-контроллеры (react-router, react-helmet), компоненты-биндинги (HOC).

Mobx же позволяет больше компонентов сделать в единообразном стиле хотя бы. Почти везде будет чистый компонент с observable оберткой. Будет логика отделена от верстки, что лучше для SRP.

А подход, который вы описали, на самом деле приводит к ещё одному конкурирующему стандарту.
Я имел в виду, что если бы mobx был в основе react, то многое упростилось. Как раз меньше было бы стандартов и дублирующих функциональность библиотек, т.к. более мощное решение в основе. Да и потом, вроде на фронтенде нормально, что кто-то всегда будет искать лучшее решение. Судя по этой активности, приблизиться к идеалу ни у кого пока не получилось.

Не понял, почему? Вот у меня все компоненты берут всё из пропсов.
Компоненты — только часть приложения. А как же всякие actions, reducers, middlewares — это все opionated, т.к. навязывает определенный стиль, функциональный подход, свои соглашения по модификации состояния и т.д. У mobx эти соглашения проще и ближе к нативному js, в статье об этом говорится, причем на этой абстракции можно строить сложные, вроде mst.

Да, так, будут. Нет, не так, mol_jsx их пересоздаёт.
Не пойму, почему вы так решили. В том комменте говорится, будут пересозданы, если id-шника нет. Судя по реализации там есть переспользование и в примере видно, что не пересоздается нода. Может мы о разных нодах говорим?
Который был написан самими фейсбуковцами для рекламы файбера. Это не «мои бенчмарки».

Анимацию он хорошо показывает по-сравнению со старым реактом и производит вау-эффект, для этого наверное и создавался. Примеров мало, что б делать выводы какие-то.
Возможно имеет смысл обновить знания про файбер, поковырять исходники, выглядит несколько умнее, чем вам кажется.

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

А пример с серпинским устаревшим выглядит, других пока нет, к сожалению.

Information

Rating
Does not participate
Location
Россия
Registered
Activity