Управляем состоянием приложения без шаблонного кода и магии

image

Хочу поделиться с сообществом своей реализацией концепции flux как единого источника данных и видением построения веб-приложений. Мотивом к созданию своего решения послужило желание избавиться от большого количества шаблонного кода и сделать взаимодействие с источником данных удобным. Я работал над большим приложением (10 команд + 1 архитектурная) с использованием связки React + Redux как архитектор и как лид команды разработки и вынес для себя моменты, которые доставляли большие неудобства в процессе написания кода:

  • большое количество шаблонного кода
  • как следствие многословности — перенос небольших кусков логики в представление
  • сложность динамического добавления/удаления бизнес-логики модулей
  • возможность подписаться только на обновления всего стора (утомительные селекторы + возможны неожиданные перерисовки)

3 пункт особенно важен в контексте архитектуры микро-фронтендов, которая используется на проекте (и на многих других проектах).

Решение


Библиотека называется falx.

Создание бизнес логики модуля


const reducer = {
    state: [],
    actions: {
        add(state, text) {
          const todo = {
            id: getNextId(),
            done: false,
            text
          }
          return state.concat(todo)
        },
        done(state, id) {
            return state.map(todo => {
              if (todo.id == id) {
                return {
                  ...todo,
                  done: !todo.done
                }
              }
              return todo
            })
        },
        remove(state, id) {
            return state.filter(todo => todo.id != id)
        }
    }
}


При таком подходе проще будет использовать экшены редюсера чем стейт react компонента демо

Регистрация в сторе


import {register} from 'falx'
register('todos', reducer);

Подписка на обновления


import {subscribe} from 'falx'
subscribe('todos', state => {
    const html = state.todos.map(todo => `
        
  • `); todoList.innerHTML = html.join('') });

    Доступ к бизнес-логике через стор


    import {store} from 'falx'
    const input = document.querySelector('#todo-text');
    const todos = document.querySelector('#todos');
    
    input.addEventListener('keyup', event => {
       if (event.which == 13 && event.target.value) {
      	store.todos.add(event.target.value);
        event.target.value = ''
      }
    });
    todos.addEventListener('change', event => {
    	store.todos.done(event.target.id)
    });
    todos.addEventListener('click', event => {
      if (event.target.className == 'destroy') {
      	store.todos.remove(event.target.id)
      }
    });
    

    Удаление модуля из стора


    import {remove} from 'falx'
    remove('todos')
    

    живой пример

    Middleware


    Так же есть слой middleware для таких вещей как централизованная обработка ошибок, валидация и т.п.

    import {use} from 'falx'
    const middleware = (store, statePromise, action) => {
        console.log('action', action);
        return statePromise.then(state => {
            console.log('next state', state);
            return state
        })
    }
    use(middleware);
    //...
    unuse(middleware)
    

    Использование с React


     Для React есть HOC для подписки на изменения

    import React, {PureComponent} from 'react'
    import {subscribeHOC} from 'falx-react'
    
    
    const reducer = {
        state: {
            value: 0
        },
        actions: {
            up(state) {
                return {
                    ...state,
                    value: state.value + 1
                }
            },
            down(state) {
                return {
                    ...state,
                    value: state.value - 1
                }
            }
        }
    };
    
    const COUNTER = 'counter';
    
    register(COUNTER, reducer);
    
    @subscribeHOC(COUNTER)
    class Counter extends PureComponent {
        render() {
            return (
                
    {this.props.counter.value}
    ) } }


    живой пример

    Дебаг


    Есть коннектор для Redux devtools

    import {connectDevtools} from 'falx-redux-devtools'
    
    
    connectDevtools(
        window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
    );
    

    Заключение


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

    © Habrahabr.ru