cellx
cellx copied to clipboard
А нужен ли put?
Начнем с того, что при сохранение атома выглядит странно: set(some) вызывает fetch. Для синхронизации это может и годится. Сохранению обычно
- предшествуют какие-то изменения в интерфейсе, индикатор, ошибки валидации и т.д. А put получается навязывает подход, когда мы это все делаем не прямо, а опосредованно после вызова set.
- Сохранение чего-то вообще не подразумевает наличие модели, POST запрос, сохранивший todo может ничего не вернуть кроме 200 кода успеха, получается делаем атом, только что б вытянуть ошибки и isPending. Атом становится своего рода контроллером, сервисом, а изначальная его идея - только реактивное распространение данных.
- Модель на сохранение может не соответствовать модели на загрузку, например могут быть какие-то метаданные только для сервера, логирование и т.д.
- Не всегда реакция на fetch - изменение стейта, это может быть переход на новый location, посылка еще одно запроса куда-либо и т.д.
Есть форма добавления Todo. Данные формы и ее ошибки - 2 разных атома. При сохранении данных надо проверить данные, обновить атом с ошибками, если ошибок нет - выполнить сохранение на сервер. Причем кроме todo, надо сохранить еще кучу данных, не имеющих отношения к нему, ну апи такое. После этого надо еще todos обновить на клиенте.
type Todo = {title?: string}
const todos = new cellx.Cell((push, fail, oldValue) => {
fetch('/todos', {method: 'GET'})
.then((todos) => push(todos))
.catch(fail)
return oldValue || []
})
const todoErrors = new cellx.Cell({})
const todoOnServer = new cellx.Cell({}, {
put(t: {todos: Todo[], todo: Todo, counter: number, canAddEmpty: boolean}, push, fail) {
fetch('/todo', {method: 'POST', body: JSON.stringify(t)})
.then(() => {
push()
t.todos.set(t.todos.get().push(t.todo))
})
.catch(fail)
}
})
const editableTodo = new cellx.Cell({})
function editTitle(title: string) {
editableTodo.set({title})
}
let counter = 0
function saveTodo(canAddEmpty: boolean) {
conts o = {}
const todo = editableTodo.get()
if (!canAddEmpty && !todo.title) {
o.isErrors = true
o.title = 'Title is empty'
}
todoErrors.set(o)
if (!o.isError) {
todoOnServer.set({todo, todos, counter: counter++, canAddEmpty})
}
}
- Экшен saveTodo, который выполняет сохранение, формирует данные для передачи на сервер.
- Эти данные складываются из аргументов, с которым вызвали экшен (canAddEmpty) и контекста (editableTodo, counter, todos).
- А что если нет синглотнов и метод put ничего не знает о контексте. Получается, надо формировать объект всего-всего, что необходимо для передачи на сервер и, что важно, обновления стейта (нам же todos надо обновить)
В примере выше, видно, что todoOnServer - это уже не todo, а некая сущность с с todo, counter, canAddEmpty и на клиенте эта сущность нужна только для показа ошибок от сервера. При чтении у нас вообще отдельная коллекция из другого апи.
Вместо прямого fetch в saveTodo, мы упаковываем todo и canAddEmpty в один объект, сетим с ним атом, а атом уже делает fetch только ради состояния ошибок.
По мне, это и есть основная дырявость абстракции put: не очевидность сохранения, сценарий получается размазанным по saveTodo и put.
С прямым fetch в общем-то простой saveTodo получается, без лишних абстракций.
function saveTodo(canAddEmpty: boolean) {
// ...
if (!o.isError) {
const p = fetch('/todo', {method: 'POST', body: JSON.stringify({todo: editableTodo.get(), counter: counter++ cannAddEmpty})})
.then(() => {
todos.set(todos.get().push(t.todo))
})
todoState.setPromise(p)
}
}
предшествуют какие-то изменения в интерфейсе, индикатор, ошибки валидации и т.д. А put получается навязывает подход, когда мы это все делаем не прямо, а опосредованно после вызова set.
в put не нужно устанавливать индикаторы, ошибки или что-то подобное. Он в первую очередь для обратной связи вычисляемой ячейки с ячейками из которых она вычисляется:
let firstName = cellx('Вася');
let lastName = cellx('Пупкин');
let fullName = cellx(() => firstName() + ' ' + lastName(), {
put(value) {
value = value.split(' ');
firstName(value[0]);
lastName(value[1]);
}
});
console.log(fullName());
// => "Вася Пупкин"
fullName('Петя Запупкин'); // запись в вычисляемую ячейку
console.log(firstName());
// => "Петя"
console.log(lastName());
// => "Запупкин"
Ага, т.е. это способ не сохранять все подряд, а только синхронизация, достаточно редкая задача.
Да, синхронизация c мастер-ячейками или внешним хранилищем значения 1, 2. Для чего-то другого применять нет смысла.
У меня идея какая, для статуса сохранения идеально иметь 3 атома: данные, isPending, error. Для загрузки сейчас не так: атом 1, а isPending, error самим атомом данных управляется.
Я хотел универсальный способ, как при сохранении. но, что б не светить pending и error атомы компоненту.
Нужна возможность статус и error хранить в отдельных атомах, управлять ими со-стороны. Но, в случае загрузки нужна возможность "просачивать" их через computable, как сейчас есть isPending, и error (только они создаются и обрабатываются внутри, а не со стороны). Интересно было бы, если б cellx мог так делать.
PS: Пожалуй с сохранением плохая идея "просачивать" атомы, т.к. они смешаются с isPending загрузки