cellx icon indicating copy to clipboard operation
cellx copied to clipboard

А нужен ли put?

Open zerkalica opened this issue 7 years ago • 4 comments

Начнем с того, что при сохранение атома выглядит странно: set(some) вызывает fetch. Для синхронизации это может и годится. Сохранению обычно

  1. предшествуют какие-то изменения в интерфейсе, индикатор, ошибки валидации и т.д. А put получается навязывает подход, когда мы это все делаем не прямо, а опосредованно после вызова set.
  2. Сохранение чего-то вообще не подразумевает наличие модели, POST запрос, сохранивший todo может ничего не вернуть кроме 200 кода успеха, получается делаем атом, только что б вытянуть ошибки и isPending. Атом становится своего рода контроллером, сервисом, а изначальная его идея - только реактивное распространение данных.
  3. Модель на сохранение может не соответствовать модели на загрузку, например могут быть какие-то метаданные только для сервера, логирование и т.д.
  4. Не всегда реакция на 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})
  }
}
  1. Экшен saveTodo, который выполняет сохранение, формирует данные для передачи на сервер.
  2. Эти данные складываются из аргументов, с которым вызвали экшен (canAddEmpty) и контекста (editableTodo, counter, todos).
  3. А что если нет синглотнов и метод 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)
  }
}

zerkalica avatar Nov 19 '16 13:11 zerkalica

предшествуют какие-то изменения в интерфейсе, индикатор, ошибки валидации и т.д. А 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());
// => "Запупкин"

Riim avatar Nov 21 '16 08:11 Riim

Ага, т.е. это способ не сохранять все подряд, а только синхронизация, достаточно редкая задача.

zerkalica avatar Nov 21 '16 08:11 zerkalica

Да, синхронизация c мастер-ячейками или внешним хранилищем значения 1, 2. Для чего-то другого применять нет смысла.

Riim avatar Nov 21 '16 08:11 Riim

У меня идея какая, для статуса сохранения идеально иметь 3 атома: данные, isPending, error. Для загрузки сейчас не так: атом 1, а isPending, error самим атомом данных управляется.

Я хотел универсальный способ, как при сохранении. но, что б не светить pending и error атомы компоненту.

Нужна возможность статус и error хранить в отдельных атомах, управлять ими со-стороны. Но, в случае загрузки нужна возможность "просачивать" их через computable, как сейчас есть isPending, и error (только они создаются и обрабатываются внутри, а не со стороны). Интересно было бы, если б cellx мог так делать.

PS: Пожалуй с сохранением плохая идея "просачивать" атомы, т.к. они смешаются с isPending загрузки

zerkalica avatar Nov 21 '16 08:11 zerkalica