Pull to refresh

Comments 27

Посмотрите WPF с его точками расширения/слотами/темплейтами и DataContext'ами.
Вот там можно переопределить всё и даже довольно удобно.

WPF все-таки другая экосистема, мне была важна поддержка react, flowtype. Поэтому где-то конструкции приходилось упрощать или жертвовать гибкостью. Главное проверить, что подход работает и более-менее просто выглядит.
Все же посмотрите на WPF, все ваши вопросы там уже решены с помощью DataTemplate. Получается значительно проще и чище.
А пример можно? Глянул бы.
WPF тут к чему? Это если наложить возможности WPF на оригинальный HTML, но никто на это не пойдет, порог вхождения станет слишком высок, и модные пацанчики тупо не осилят. А к React это никак и не прицепить, увы, инфраструктура не та совсем.

Я имею ввиду посмотреть саму идею расширяемости wpf; понятно, что это не перетащишь в веб просто так (хотя попытки были http://bridge.net/ http://www.cshtml5.com )
У меня просто стойкое ощущение, что всё это было там уже довольно давно.

Сложно читать статью из-за нестандартного синтаксиса JavaScript. Какие babel-transform нужно использовать для того, чтобы преобразовать код из примеров в ES6+JSX?

Вы какой-то конкретный пример имели в виду? Вроде вполне стандартный js с flow-типами и JSX.

Примеры 3, 10, 11 и 14. Ничего из перечисленного не встречал даже в будущих версиях стандарта ECMAScript:


  • Конструкция @mem
  • Объявление параметров объекта в теле класса
  • Типы аргументов функций
Столкнулся с похожей проблемой, когда имеется несколько крупных Container-ов, бизнес хочет с десяток копий с разным поведением, но рука не поднимается дублировать столько кода и ставить крест на проекте.

DI удобная вещь, но если используется redux подход используемый в redux-form мне кажется более логичным. Идея в том что Container, обернутый в connect, вместе со стилями css-in-js, prop-types и селекторами оборачивается еще одним HOC вида createMyAwesomeComponent. В итоге получается функция-конструктор, формирующая с помощью параметров произвольное поведение самого Container и его дочерних Presentationals.

В такой конструктор можно на вход подать кастомные селекторы, которые прогонят через требуемую логику данные из store. Можно в виде параметров передавать мелкие кастомизированные компоненты, чтобы не перегружать сигнатуру конструктора. В store для каждого инстанса Container создается свой state, и соответственно в экшенах в разделе meta указывается имя инстанса.

Если нужна кастомизация side effects, например тулбар с другими кнопками, то проще для тулбара создать отдельный Container и передать его в качестве параметра в основной Container.

DI мне интересно развивать, как фундамент для связывания частей приложения, без привязки к каким либо фреймворкам. Еще была важна ненавязчивость, в коде нету import some from 'reactive-di' (кроме cloneComponent), только чистые функции + метаданные.

Redux немного другие задачи решает, навязывает определенный стиль программирования (экшены, редьюсеры и т.д.) не избавляет от HOC и этого connect в коде. Если нужны транзакции, версионирование и т.д., то решения вроде redux можно накрутить и поверх DI. Например, mobx-state-tree — это как раз пример того, как выглядел бы redux поверх mobx.
В TypeScript строковые литералы не считаются нетипобезопасными, так как есть строковые литеральные типы.
Redux предназначен не для хранения состояния компонентов. Redux это слой между хранилищем данных и react. Плюс он играет роль менеджера состояния ПРИЛОЖЕНИЯ. Но нужно помнить, что определять в нем состояние приложения стоит только в том случае, если эта логика таковой является.
Без lifecycle очень плохо. наоборот, чем больше lifecycle тем было бы круче. представьте что помимо constructor в js был бы ещё destructor, update, write, read. Жизнь стала бы сказкой.

Я тоже пробовал внедрить DI в react и так как пишу на typescript, выбран был InversifyJS.
Затем использовал mobx. Затем перешел на angular 4 и просто нарадоваться не могу. Все о чем можно мечтать уже сделано…
В TypeScript строковые литералы не считаются нетипобезопасными, так как есть строковые литеральные типы.
В ts да, такая штука будет типобезопасной:
import { InjectionToken } from '@angular/core';
interface AppConfig {
  title: string;
}
export let APP_CONFIG = new InjectionToken<AppConfig>('app.config');
//...
constructor(@Inject(APP_CONFIG) config: AppConfig) {
  this.title = config.title;
}

Однако такой подход все-равно требует расстановки декораторов, это не нативно из-за InjectionToken. Второй момент — заранее надо продумывать точки расширения, расставляя эти декораторы.

Redux предназначен не для хранения состояния компонентов.
Redux лучше впишется туда, где используются транзакции, timetravel, распределенная работа с данными. Если эти навороты не нужны, то есть более простые способы менять состояние, поэтому многие перешли на mobx.
Задачу связывания redux решает не очень хорошо: redux-thunk, reselect, connect — низкоуровневые инструменты. Если основа DI, можно все связывание построить на типах, не привнося фреймворко-специфичных конструкций.
Сравните примеры Hello world, кода сильно меньше при сравнимом уровне абстракции:
На redux
import { connect } from 'react-redux'
import React, { PropTypes } from 'react'

const HELLO_WORLD = 'HELLO_WORLD'

const helloWorld = (state = { message: 'Hello' }, action) => {
  switch (action.type) {
    case HELLO_WORLD:
      return Object.assign({}, state, { message: 'Hello, World!' })
    default:
      return state
  }
}

const Hello = ({ onClick, message }) => {
  return (
    <div>
      <h1>{ message }</h1>
      <button onClick={onClick}>Click</button>
    </div>
  )
}

const mapStateToProps = (state, ownProps) => {
  return {
    message: state.helloWorld.message
  }
}

const mapDispatchToProps = (dispatch, ownProps) => {
  return {
    onClick: () => {
      dispatch({ type: HELLO_WORLD })
    }
  }
}

const HelloWorld = connect(
  mapStateToProps,
  mapDispatchToProps
)(Hello)
На reactive-di
import {mem} from 'lom_atom'
class HelloWorldStore {
  @mem message = 'Hello'
   onClick() {
      this.message = 'Hello, World!'
   }
}

function HelloWord(_: {}, store: HelloWorldStore) {
  return (
    <div>
      <h1>{ store.message }</h1>
      <button onClick={() => store.onClick()}>Click</button>
    </div>
  )
}

Через алиасинг HelloWorldStore также можно разделить компонент и реакцию на событие.

Без lifecycle очень плохо.
Тут бы глянуть конкретные примеры. Есть мнение, что если правильно построить работу с состоянием, большинство lifecycle не нужными становятся. Я об этом хотел написать в следующей статье.
Например, hello world с загрузкой, обработкой ошибок и крутилкой:
На reactive-di
function fetchName() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      // reject(new Error('Fetch error'))
      resolve({name: 'John'})
    }, 500)
  })
}

interface HelloModel {
  name: string;
}

class HelloService {
    @mem set hello(next: HelloModel | Error) {}
    @mem get hello(): HelloModel {
      fetchName()
        .then((model) => { this.hello = model })
        .catch((error) => { this.hello = error })

      throw new mem.Wait()
    }
}

function HelloView(props: {greet: string}, service: HelloService) {
    return <div>
        {props.greet}, {service.hello.name}
        <br/><input value={service.hello.name} onChange={(e) => { service.hello = {...service.hello, name: e.target.value} }} />
    </div>
}

ReactDOM.render(<HelloView greet="Hello"/>, document.getElementById('mount'))
fiddle
На angular4
import {Component} from '@angular/core';

function fetchName() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      // reject(new Error('Fetch error'))
      resolve({name: 'John'})
    }, 500)
  })
}

interface HelloModel {
  name: string;
}

export class Hello implements HelloModel {
  name = 'Vasian'
}


@Component({
  selector: 'my-app',
  template: `
    <div>
    <div *ngIf="error; else elseBlock">
      {{error.message}}
    </div>
    <ng-template #elseBlock>
  {{greet}}, {{hello.name}}
  <br/><input [(ngModel)]="hello.name">
    </ng-template>
    </div>
`
})
export class AppComponent  {
  greet = 'Hello';
  error: any = '';
  hello: Hello = new Hello()

  ngOnInit() {
    fetchName()
      .then(hello => this.hello = hello)
      .catch(error => this.error = error)
  }
}
plnkr

Как минимум, загрузку и ошибки в Angular придется обрабатывать вручную. Красивый биндинг данных работает внутри компонента, если нужна декомпозиция Hello (например расшарить между двумя компонентами, ничего не знающими друг о друге), то уже надо тащить стримы, подписываться/отписываться.
Что бы использовать сервис, надо объявить его в provide и помечать Injectable. А typescript работает внутри Angular template, как в JSX?
Объем кода тоже можно отметить, Angular4 core+common+forms = 13459 SLOC, rdi+lom_atom+preact, на котором мои примеры, около 2048 SLOC.
Тут конечно простые примеры рассмотрены, если подкинете сложные, я их запилю на rdi.
Redux вполне может использоваться без React. А состояние приложения есть суперпозиция состояний всех его компонентов. Redux в пределе стремится к тому, чтобы у компонентов своего внутреннего состояния не было и всё состояние приложения хранилось в одном-единственном хранилище под управлением Redux. Считать это хранилище частью Redux или чем-то отдельным — вопрос теоретически спорный, но на практике, по-моему, нет смысла не считать.

Очень тяжело читать статью.
Я вот с react-redux регулярно работаю и имею представление о SOLID-принципах и сложностях проектирования, но почти ничего не понял.
Какие-то отдельные часть — возможно, но сути я не уловил.
Как автор предлагает уменьшать сложность за счёт использования DI — ускользнуло от меня.

Это первая моя статья, буду пытаться улучшать подачу в следующих. Хотя бы пример с HelloWorld понятен? Могу для сравнения еще насоздавать, только задачу сформулируйте.

Суть такая примерно:
  • Развить идею контекста в реакте
  • Привнести SOLID на фронтенд наименьшей ценой
  • Дать возможность писать код, максимально свободный от каркаса, убрав vendor lock-in
  • Использовать типы и DI для связывания частей, вместо решений вроде connect, redux-thunk, reselect и т.д.

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

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

Думаю тут не хватает наглядного примера с картинками. Например — мини-профиль пользователя на хабре. Он появляется в 3 разных местах: при наведении на аватарку, под статьёй пользователя, на странице профиля пользователя. Каждый мини-профиль позволяет выполнить определённые действия: изменить карму, подписаться, написать сообщение. Но в зависимости от расположения имеет свои особенности: при наведении на аватар добавляется панелька со статистикой, раскладка элементов разная. Но это текущее состояние, после множества рефакторингов. А если откатиться к самому началу, когда этой кучи требований ещё не было, то как запроектировать "мини-профиль пользователя", чтобы его можно было переиспользовать в разных (пока не известных) местах не переписывая постоянно и не утопая в копипасте?

А что, по-вашему, должно быть в базовой версии, а какие возможности добавлять при ее расширении?

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

Удалить и заменить деталь внутри можно, а вот что бы добавить, надо делать композицию заново (правда, в отличие от чистого реакта, в rdi это можно проделать не для основного компонента, а для его внутренней детали). У вас, в tree, такая перекомпозиция происходит дешево в sub.

Пока не представляю как в jsx это также дешево сделать. Можно выделять детали на первом уровне в компоненты и делая компонент, который выстроит последовательность из этих деталей.

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

Полностью перенести этот принцип на JSX невозможно,

Почему же невозможно — можно воспользоваться динамическими пропсами


class Component extends React.Component {
 render(){
   return ( 
  <div class="..." {...this.props.rootProps}>
   {this.props.rootChildrenBefore}
   {this.props.rootChild1 || <div class="..." {...this.props.rootChild1Props}>
        {this.props.rootChild1ChildrenBefore}
        {this.props.rootChild1Child1 || <div .... <div/> }
        {this.props.rootChild1Child2 || <div .... <div/> }
        {...}
       {this.props.rootChild1ChildrenAfter}
   </div>}
   {this.props.rootChild2 || <div ... <div/>}
   {...}
   {this.props.rootChildrenAfter}
 </div>
  )
 }
}

Легко видеть что таким образом можно описать любую вложенность (включая добавление перед и после имеющимся контентом) и потом можно переопределить любой аспект компонента также гибко как и в $mol.


 const ComponentWithSomeChanges = ({...props})=> (
  <Component
   rootChild1Child1={
     <div>...</div>
   }
   {...props}
  />
)

Да, выглядит это не так лаконично — вместо xml иерархии получаем иерархию разметки в пропсах и нужно печатать чуть больше символов но в отличие от подхода с наследованием когда в методы выносится каждый кусочек разметки или пропсов и получается плоский список методов вместо дерева


class MyPanel extends React.Component {
    header() { return <div class="my-panel-header">{this.props.head}</div> }
    bodier() { return <div class="my-panel-bodier">{this.props.body}</div> }
    childs() { return [ this.header() , this.bodier() ] }
    render() { return <div class="my-panel">{this.childs()}</div>
}

здесь же сохраняется древовидная структура шаблона.

Кстати появляется закономерный вопрос — если проблема решаема с динамическими пропсами но приходится так много писать чтобы добавить слоты расширения — может пусть какой-то бейбл-плагин будет генерировать эти слоты расширения за нас? Заодно и избавимся от другой проблемы — необходимости придумывания имен. Будут автоматически генерироваться пропсы что-то вроде rootChildX...ChildX. и в шаблоне всего этого болерплейта не будет и будет только чистая верстка а тому кому это будет нужно сможет всегда переопределить любой аспект верстки
Sign up to leave a comment.

Articles