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

Рабочий закомментированный код здесь https://github.com/freddi301/cinammon-redux

Примеры запуска здесь https://cinammon-redux.now.sh

// @flow

Прежде всего, давайте посмотрим на редуктор

Далее следует очень простая реализация

type CounterAction =
  { type: 'inc' }
| { type: 'add', payload: number }
;
type CounterState = number;
const CounterReducer: Reducer<CounterState, CounterAction> =
  (state, action) => // implement the logic
{
  switch (action.type) {
    case 'inc': return state + 1;
    case 'add': return state + action.payload;
    default: throw new Error(`unknown action: ${action.type}`);
  }
}

Вот и все, но теперь нам нужен изменяемый объект, который сохранит наше состояние.

Простой шаблон наблюдателя подойдет

export class Store<State, Action> {
  state: State;
  reducer: (state: State, action: Action) => State;
  replaceReducer(reducer: (state: State, action: Action) => State) {
    this.reducer = reducer;
  };
  listeners: Set<(state: State) => void> = new Set;
  constructor(state: State, reducer: (state: State, action: Action) => State) {
    this.state = state;
    this.reducer = reducer;
  }
  publish = (action: Action): void => {
   this.state = this.reducer(this.state, action);
   this.notify();
  }
  notify() {
    for (let listener of this.listeners) listener(this.state);
  }
  subscribe(listener: (state: State) => void): void {
    this.listeners.add(listener);
  }
  unsubscribe(listener: (state: State) => void): void {
    this.listeners.delete(listener);
  }
}

Поскольку архитектура потока в основном используется с реакцией, давайте создадим компонент высокого порядка для прослушивания хранилища. Эта конвенция будет соблюдаться.

import React from 'react';
export function connect<State, Action, Props>(
  store: Store<State, Action>,
  component: (props: Props, state: State
  publish: (action: Action) => void) => React.Element<*>
): Class<React.Component<void, Props, { state: State }>> {
  return class extends React.Component<void, Props, { state: State }> {
    store: Store<State, Action> = store;
    component: (
      props: Props,
      state: State,
      publish: (action: Action) => void
    ) => React.Element<*> = component;
    state = { state: store.state };
    render() {
      return this.component(
        this.props,
        this.state.state,
        this.store.publish
      );
    }
    listen = (state: State) => this.setState({ state });
    componentWillMount() { this.store.subscribe(this.listen); }
    componentWillUnmount() { this.store.unsubscribe(this.listen); }
  }
}

Мы не собираемся использовать внутреннее состояние, вместо этого мы реифицируем домен и состояние просмотра в хранилище. См. также вязовую архитектуру.

const counterDemoStore = new Store(0, CounterReducer);
const CounterComponent = (props, state, publish) => <div>
  counter = {state}<br/>
  <button onClick={() => publish({ type: 'inc' })}>inc</button><br/>
</div>;
export const CounterDemo = connect(
  counterDemoStore,
  CounterComponent
);

Продолжение следует :)