https://habrahabr.ru/post/349636/
Хочу поделиться с сообществом своей реализацией концепции
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 => `
<li ${todo.done ? 'class="completed"' : ''} >
<div class="view">
<input class="toggle" type="checkbox" id="${todo.id}" ${todo.done ? 'checked' : ''} />
<label>${todo.text}</label>
<button class="destroy" id="${todo.id}"></button>
</div>
<input class="edit" value="${todo.text}" />
</li>
`);
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 (
<div>
<div id="value">
{this.props.counter.value}
</div>
<button id="up" onClick={this.props.up} >up</button>
<button id="down" onClick={this.props.down} >down</button>
</div>
)
}
}
→
Живой примерДебаг
Есть коннектор для Redux devtools:
import {connectDevtools} from 'falx-redux-devtools'
connectDevtools(
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
Заключение
Надеюсь кому-нибудь такой подход покажется удобным и спасет от тонн шаблонного кода при создании нового приложения или добавления единого источника данных в существующее.