Предупреждение: я средний программист на React. Поэтому я не уверен, что мои примеры идеальны или не содержат ошибок.

В моем последнем посте я объяснил основные функции и концепции GState. В этой статье я буду следовать своим мнениям с помощью шаблонов для решения некоторых проблем управления состоянием:
- Однонаправленный поток данных
- Государственные декларативные компоненты (в духе Relay / Apollo).
- Инкапсуляция в едином хранилище состояний (Redux issue).
Однонаправленный поток данных

GState может применять однонаправленный поток данных, такой как Flux или Redux. Возьмем классический пример Todo от Redux и перепишем его с помощью GState (Исходный код):
Команды: Запишите сторону в шаблоне CQRS.
Команды такие же, как и в редюсерах Redux. Это могут быть чистые функции, принимающие состояние в качестве параметра и использующие state.get для чтения, state.set для изменения. Это важно, потому что вы можете повторно использовать бизнес-логику команды для любого контекста / пути состояния. Также легко проверить:
const state = new GState();
state.set({ todos: {} });
addToDo(state, "a");
expect(state.get({ todos: { _: 1 }}))
.toMatchObject({ todos:[{text: "a", completed: false}]});
Запросы: чтение сайта в шаблоне CQRS.
В запросах используется компонент высокого порядка декларативного состояния gstate (то же самое, что и в Appollo graphql или Relay createFragmentContainer), которые используют state.watch для запроса информации и визуализации компонентов без состояния.
Государственные декларативные компоненты
Еще важнее то, что эти компоненты являются декларативными: они позволяют разработчикам указывать как должен выглядеть пользовательский интерфейс для данного состояния, и им не нужно беспокоиться о том, как , чтобы показать этот пользовательский интерфейс - Ретрансляционное мышление
В нашем идеальном мире компонент должен заботиться только о рендеринге состояния , не зная, как, когда и откуда.
// Expects the props to have the following shape:
// {
// text,
// isComplete
// }
const TodoItem = ({ text, isComplete }) => {
return (
<View>
<Checkbox checked={isComplete} />
<Text>{text}</Text>
</View>
);
}
}
// gstate is an HOC watch/get declarative state for the component.
const TodoItemWithState = gstate({
text: 1,
isComplete: 1
}, (props, data) => {
return <TodoItem text={data.text} isCompleted={data.isCompleted} />
})
Gstate HOC
Это моя тривиальная версия gstate HOC. Это должно быть основной функцией декларативного шаблона состояния.
Компонентный состав
Сложные декларативные компоненты с большим состоянием могут быть созданы путем компоновки более мелких компонентов. Есть 2 подхода:
- Родительский компонент может объявлять форму данных как для детей, так и для себя.
- Родитель использует state.path (key) для создания подсостояния для детей, которые декларируют свои собственные требования к состоянию.
// Expects a `list` with a string `title`, as well as the information
// for the `<TodoItem>`s (we'll get that next).
const TodoList = ({ state, title, todos }) {
render() {
return (
<View>
<Text>{title}</Text>
{todos.map(key => <TodoItem state={state.path(["todos", key])} />)}
</View>
);
}
}
const TodoListWithState = gstate({
title: 1,
todos: {
_: "$key" // todos.map((item, key) => key)
}
}, (props, data) => (
<TodoList state={props.state} title={data.title} todos={data.todos}/>
))
Примечание: это тот же образец, что и для композита вида / модели вяз.
view : Model -> Html Msg
view model =
div
[ class "todomvc-wrapper"
, style [ ( "visibility", "hidden" ) ]
]
[ section
[ class "todoapp" ]
[ lazy viewInput model.field // child component with sub state
, lazy2 viewEntries model.visibility model.entries // child component with sub state
, lazy2 viewControls model.visibility model.entries // child component with sub state
]
, infoFooter
]
Инкапсуляция в едином государственном хранилище
Есть пара проблем с инкапсуляцией Redux, которые я пытаюсь решить с помощью GState. В целом GState решает проблему инкапсуляции с помощью:
- Изолированное подсостояние с помощью API state.path.
- Команды чистой функции с состоянием в качестве параметра.
Проблема №1: два состояния, один редуктор
Команды GState - это чистые функции, принимающие состояние в качестве параметра, поэтому нет проблем с повторным использованием с любым деревом состояний / контекстом.
function incrementCounter(state){
const counter = state.get("counter") || 0;
state.set({ counter: counter+1})
}
Создайте два компонента Counter с разными путями состояния
<Counter state={state.path("top")}/>
<Counter state={state.path("bottom")}/>
Компонент счетчика применяет incrementCounter к своему собственному состоянию
const Counter = ({state}) => (
<button onClick={() => incrementCounter(state)}>+</button>
)
Проблема # 2: прислушиваемся к другим редукторам
Вы можете перейти в родительское состояние, чтобы наблюдать за обоими состояниями счетчиков.
<Counter state={state.path("top")}/>
<Counter state={state.path("bottom")}/>
state.watch({
top:{ counter: 1 },
bottom: { counter: 1}
}, res => { total = res.top.counter + res.bottom.counter })
Проблема # 5: экземпляры динамического состояния
Создать новое дерево состояний для динамического компонента очень просто.
const AddDynamicCounters = ({ state, numberOfCounters}) => {
const counterArr = [];
for (let i = 0; i < numberOfCounters; i++) {
counterArr.push(i);
}
return (
<div>{counterArr.map(id => <Counter state={state.path(i)}/>)}</div>
)
}
Инкапсулируйте команды с компонентами.
Первый раз пробуя Redux, я задаюсь вопросом, как повторно использовать мои компоненты и редукторы. Вскоре я понимаю, что Redux не пытается этого сделать.

Дело в том, что Flux (Redux) перемещает состояние приложения из состояния компонента и бизнес-логику из обработки событий. Здорово, что можно писать состояние приложения + бизнес-логику без каких-либо компонентов. И вы можете повторно использовать свой код для любых UI-фреймворков.
Но в некоторых случаях допустимо инкапсулировать как представление, так и состояние. Например, разделите сложное приложение на модули.
Вы можете просто добавить команду createCounter в модуль counter.js. Затем вы можете повторно использовать модуль для создания компонента Counter.
// add a create component command
function createCounter(state){
//init state
//return component with query
return gstate({ counter: 1 },() => (
<button onClick={() => incrementCounter(state)}>+</button>
))
}
function incrementCounter(state){
const counter = state.get("counter") || 0;
state.set({ counter: counter+1})
}
Финал
В следующий раз я сначала изучу шаблоны выборки данных, связи между компонентами, путешествий во времени, SSR и автономного режима для PWA.