Size: a a a

Reatom — стейт-менеджер

2021 January 13

I

Ilyas Kabirov in Reatom — стейт-менеджер
если бы была ленивость, то перед получением стейта все что было должно бы обработаться
источник

a

artalar in Reatom — стейт-менеджер
Ilyas Kabirov
а в чем ее важность? если экшн диспатчится, то по идее и обработаться должен
Если автоматически не отписываться, станет намного сложнее создавать фабрики атомов и вообще реюзать логику атомов, потому что это все будет течь. А сейчас добиться меморилика с реатомом довольно тяжело
источник

a

artalar in Reatom — стейт-менеджер
Ilyas Kabirov
все таки ленивость и игнорирование вещи разные
В нашем контексте это абсолютно связанные вещи. Мы не подписать атом на экшены так, что бы он автоматически отписался когда стал не нужен
источник

a

artalar in Reatom — стейт-менеджер
Автоматическая отписка важна, потому что с мануальной отпиской нужно будет решать не только отписывать ли текущий атом, но и зависимые. Типа есть у атома несколько зависимостей, как понять какие нужно их при отписке оставлять в сторе или нет, вдруг они еще кому-то нужны или не нужны?
источник

Б

Богдан in Reatom — стейт-менеджер
artalar
Ну и по смыслу $ подходит - это функция внутренней подписки на атом / экшен. При этом, повторюсь, можно и свой нейминг использовать
А зачем такой неудобный апи когда мы должны обернуть в вызов функции которую получаем через аргумент? Почему не сделать как я делал через mobx? В мобиксе можно создать атом/обзервабл у которого есть методы .get() и .set(). Но поскольку писать каждый раз .get() неудобно то я написал хелпер (<10 строк) который позволяет вместо atom.get() вызвать как функцию atom()
//old 
const computedAtom = computed(()=> atom1.get() + atom2.get());
//new
const computedAtom = computed(()=> atom1() + atom2());
Ну а чтобы установить значение то вызываем с аргументом значения
//old
atom1.set(newValue);
//new
atom1(newValue);
А теперь сравним с новой версией реатома
//reatom
const computedAtom = Atom($ => $(atom1) + $(atom2));
//mobx
const computedAtom = Atom(() => atom1() + atom2());

//reatom
const displayName = Atom($ =>
 $(firstName).length < 10
 ? $(firstName)
 : $(fullName)

//mobx
const displayName = Atom(() =>
 firstName().length < 10
 ? firstName()
 : fullName()
Разве вторая версия не проще? Меньше дополнительных знаков и проще набирать - для автокомплита достаточно набрать пару букв имени атома в то время как с реатомом нужно сначала набрать доллар с открывающей скобкой и только потом уже первые буквы имени атома.
И главное проще читать (как минимум для новичков) - апи где получаем непонятно что через параметры а потом передаем туда атом чтобы его прочитать "Atom($ => $(atom1) + $(atom2)" читается сложнее из-за большей семантической нагрузки (больше действий в коде) чем просто вызов как функции для чтения "Atom(() => atom1() + atom2())"
источник

a

artalar in Reatom — стейт-менеджер
Богдан
А зачем такой неудобный апи когда мы должны обернуть в вызов функции которую получаем через аргумент? Почему не сделать как я делал через mobx? В мобиксе можно создать атом/обзервабл у которого есть методы .get() и .set(). Но поскольку писать каждый раз .get() неудобно то я написал хелпер (<10 строк) который позволяет вместо atom.get() вызвать как функцию atom()
//old 
const computedAtom = computed(()=> atom1.get() + atom2.get());
//new
const computedAtom = computed(()=> atom1() + atom2());
Ну а чтобы установить значение то вызываем с аргументом значения
//old
atom1.set(newValue);
//new
atom1(newValue);
А теперь сравним с новой версией реатома
//reatom
const computedAtom = Atom($ => $(atom1) + $(atom2));
//mobx
const computedAtom = Atom(() => atom1() + atom2());

//reatom
const displayName = Atom($ =>
 $(firstName).length < 10
 ? $(firstName)
 : $(fullName)

//mobx
const displayName = Atom(() =>
 firstName().length < 10
 ? firstName()
 : fullName()
Разве вторая версия не проще? Меньше дополнительных знаков и проще набирать - для автокомплита достаточно набрать пару букв имени атома в то время как с реатомом нужно сначала набрать доллар с открывающей скобкой и только потом уже первые буквы имени атома.
И главное проще читать (как минимум для новичков) - апи где получаем непонятно что через параметры а потом передаем туда атом чтобы его прочитать "Atom($ => $(atom1) + $(atom2)" читается сложнее из-за большей семантической нагрузки (больше действий в коде) чем просто вызов как функции для чтения "Atom(() => atom1() + atom2())"
Это на маленьких примерах выглядит удобно, но на деле тут никакой янвости нет - когда трек пишется, а когда нет.
Плюс, потенциально, с моим апи не будет проблемы в описании асинхронных экшенов, а это одна из главных болей мобыкса
источник

Б

Богдан in Reatom — стейт-менеджер
artalar
Это на маленьких примерах выглядит удобно, но на деле тут никакой янвости нет - когда трек пишется, а когда нет.
Плюс, потенциально, с моим апи не будет проблемы в описании асинхронных экшенов, а это одна из главных болей мобыкса
А что ты имеешь ввиду под асинхронными экшенами? Никогда не испытывал этих проблем с mobx-ом
источник

Б

Богдан in Reatom — стейт-менеджер
artalar
Это на маленьких примерах выглядит удобно, но на деле тут никакой янвости нет - когда трек пишется, а когда нет.
Плюс, потенциально, с моим апи не будет проблемы в описании асинхронных экшенов, а это одна из главных болей мобыкса
Как по мне то одинаково - трек пишется когда вызываешь функцию, только с реатомом нужно вызвать "$(atom)" а в варианте который предлагаю я достаточно вызвать как "atom()"
В любом случае если мне нужно будет писать на реатоме то я сразу же напишу хелпер вокруг Atom чтобы вместо вызова через доллар "$(atom)" вызвать атом как функцию "atom()" потому что это блин удобнее - проще читается и проще писать с автокомплитом. Поэтому я предлагаю тебе сразу встать на удобную сторону чтобы народ не писал своих хелперов :)
Кстати, а как апи нового реатома будет выглядеть в шаблонах компонентов? В mobx-ом это просто
const AppState = {
count: Atom(0)
}

const CounterComponent = observer(() => {
return (
 <div>
   <div>count: ${AppState.count()}</div>
 </div>
)
})
и если допустим компонент Todo получает через пропсы объект тодошки то обращаться к полям (которые являются атомами) вполне себе удобно через вызов как функцию
const TodoComponent = observer((todo) => {
return (
  <div>
    <div>title: {todo.title()}</div>
    <div>description: {todo.desc()}</div>
    <div>author: {todo.author().firstName()}</div>
    <div>folder: {todo.folder().name()}</div>
    <div>board: {todo.folder().board().name()}</div>
  </div>
)
})
А как шаблон такого компонента запишется с апи нового реатома?
источник

Б

Богдан in Reatom — стейт-менеджер
Богдан
Как по мне то одинаково - трек пишется когда вызываешь функцию, только с реатомом нужно вызвать "$(atom)" а в варианте который предлагаю я достаточно вызвать как "atom()"
В любом случае если мне нужно будет писать на реатоме то я сразу же напишу хелпер вокруг Atom чтобы вместо вызова через доллар "$(atom)" вызвать атом как функцию "atom()" потому что это блин удобнее - проще читается и проще писать с автокомплитом. Поэтому я предлагаю тебе сразу встать на удобную сторону чтобы народ не писал своих хелперов :)
Кстати, а как апи нового реатома будет выглядеть в шаблонах компонентов? В mobx-ом это просто
const AppState = {
count: Atom(0)
}

const CounterComponent = observer(() => {
return (
 <div>
   <div>count: ${AppState.count()}</div>
 </div>
)
})
и если допустим компонент Todo получает через пропсы объект тодошки то обращаться к полям (которые являются атомами) вполне себе удобно через вызов как функцию
const TodoComponent = observer((todo) => {
return (
  <div>
    <div>title: {todo.title()}</div>
    <div>description: {todo.desc()}</div>
    <div>author: {todo.author().firstName()}</div>
    <div>folder: {todo.folder().name()}</div>
    <div>board: {todo.folder().board().name()}</div>
  </div>
)
})
А как шаблон такого компонента запишется с апи нового реатома?
Кстати в последнем примере можно сразу увидеть почему апи реатома (когда оборачиваем другой функцией как "$(atom)") хуже чем вызов через функцию сразу как "atom()". Вот есть у нас строчка в шаблоне
const TodoComponent = observer(() => {
...
<div>board: {todo.folder().board().name()}</div>
...
});

То с реатомом подобная запись будет требовать написания множества вызовов "$($($(" что совершенно нечитаемо и легко запутаться
const TodoComponent = observer($ => (todo) => {
...
<div>board: {$($($(todo.folder).board).name)}</div>
...
}));

Для тех кто не понял объясню что здесь происходит - запись "todo.folder().board().name()" отображает в шаблоне имя борда в котором находится тодошка.
Это пример приложения где юзер может создавать "борды", например "работа" или "дом" а внутри уже этих бордов создавать папки в которых уже можно создавать тодо-задачи. И в данном случае компонент TodoComponent получает через пропсы объект todo в котором поле ".folder" является атомом и ссылается на родительский объект папки а объект папки в свою очередь ссылается на объект борда через атом ".board".
Зачем их делать атомами? Если значение никогда не меняется то очевидно незачем, но представим что юзер может перетаскивать задачи из одной папки в другую. В этом случае у нас ссылка на родительский объект папки ".folder" перезапишется ссылкой на объект новой папки после перетаскивания. И соотвественно мы должны заврапить в атом чтобы получить подписку компонента на изменение этого поля и вызвать перерендер. И точно так же должны заврапить в атом поле ".board" если захотим перетаскивать папки между бордами.
источник

IA

Ilya Agarkov in Reatom — стейт-менеджер
чет не понятно каким образом cAtom перестичается. кто(и как) определяет от каких атомов он зависит?
источник

IA

Ilya Agarkov in Reatom — стейт-менеджер
или это работает по принципу computed во Vue?
источник

a

artalar in Reatom — стейт-менеджер
Богдан
Как по мне то одинаково - трек пишется когда вызываешь функцию, только с реатомом нужно вызвать "$(atom)" а в варианте который предлагаю я достаточно вызвать как "atom()"
В любом случае если мне нужно будет писать на реатоме то я сразу же напишу хелпер вокруг Atom чтобы вместо вызова через доллар "$(atom)" вызвать атом как функцию "atom()" потому что это блин удобнее - проще читается и проще писать с автокомплитом. Поэтому я предлагаю тебе сразу встать на удобную сторону чтобы народ не писал своих хелперов :)
Кстати, а как апи нового реатома будет выглядеть в шаблонах компонентов? В mobx-ом это просто
const AppState = {
count: Atom(0)
}

const CounterComponent = observer(() => {
return (
 <div>
   <div>count: ${AppState.count()}</div>
 </div>
)
})
и если допустим компонент Todo получает через пропсы объект тодошки то обращаться к полям (которые являются атомами) вполне себе удобно через вызов как функцию
const TodoComponent = observer((todo) => {
return (
  <div>
    <div>title: {todo.title()}</div>
    <div>description: {todo.desc()}</div>
    <div>author: {todo.author().firstName()}</div>
    <div>folder: {todo.folder().name()}</div>
    <div>board: {todo.folder().board().name()}</div>
  </div>
)
})
А как шаблон такого компонента запишется с апи нового реатома?
По поводу простоты не согласен))
Реакт использовать так не выйдет, потому что это не согласуется с концепцией атомарности глобального стейта, которую пропагандирует реатом
источник

a

artalar in Reatom — стейт-менеджер
Ilya Agarkov
или это работает по принципу computed во Vue?
Можно и так сказать
источник

a

artalar in Reatom — стейт-менеджер
Ilya Agarkov
чет не понятно каким образом cAtom перестичается. кто(и как) определяет от каких атомов он зависит?
К $ в аргументе привязан атом в котором вызывается функция с $ в аргументе и когда мы $ вызываем она записывает к этому атому то что было передано в $
источник

Б

Богдан in Reatom — стейт-менеджер
artalar
По поводу простоты не согласен))
Реакт использовать так не выйдет, потому что это не согласуется с концепцией атомарности глобального стейта, которую пропагандирует реатом
Почему не получится использовать реакт таким образом? Как тогда предлагается записывать с реатомом шаблон компонента в том примере? Причем здесь концепция атомарности глобального состояния?

Вот представим что нужно написать приложение в котором юзер может создавать борды, а в бордах папки, а в папках тодо-задачи. Самым простым и удобным способом организации состояния будет вложенность - переменная AppState будет хранить дерево объектов поля которых будут атомами (чтобы происходил перерендер компонентов при изменении).  Вот пример AppState с каким-нибудь начальным состоянием
const AppState = {
boards: Atom([
 {
  name: Atom("board1),
  folders: Atom([
   {
    name: Atom("folder1"),
    tasks: Atom([
     {
      name: Atom("task 1"),
      completed: Atom(false)
     },
     ...
    ])
   }
  ...
  ])
 }
 ...
])
}

Дальше я рендерю это состояние передавая компонентам нужный объект через пропс
const App = observer(() => {
...
<div>
 {AppState.boards().map(board => <Board board={board}/>)}
</div>
...
});

const Board = observer((board) => {
...
<div>{board.name()}</div>
...
<div>
 {board.folders().map(folder => <Folder folder={folder}/>}
</div>
...
})

const Folder = observer((folder) => {
...
<div>{folder.name()}</div>
...
<div>
 {folder.tasks().map(task => <Task task={task}/>}
</div>
...
})

const Task = observer((task) => {
...
<div>{task.name()}</div>
...
})

Дальше компонент получив через пропсы свой объект может свободно его обновлять
const Task = observer((task) => {
const toogleComplete = () => {
 task.completed(!task.completed);
}
...
<div>completed: {task.completed()}</div>
<button onClick={toogleComplete}>toggle complete</button>
...
});

А благодаря тому что поле todo.completed является атомом то произойдет обновление компонента (который подписался на это поле).
И точно так же с созданием новой задачи или папки - просто обновляем атом поля в котором будет храниться список объектов
const Folder = observer(folder => {
const addTodo = () => {
  const newTodo = {
   name: Atom(""),
   completed: Atom(false)
  }
  folder.tasks([...folder.tasks(), newTodo]
}
...
<button onClick={addTodo}>add todo</div>
...
})

Разве с реатомом обновление данных будет выглядеть не так или реатом будет требовать другую организацию состояния? Зачем мне отказываться от удобного хранения через вложенность и передачей компонентам нужного объекта через пропсы? Я не хочу возиться с айдишниками и дополнительно мапить айдишники на объекты или данные прежде чем передать компоненту (как через mapStateToProps в редаксе).
Разве концепция реатома не в том чтобы хранить данные в атомах и организовать их иерархически чтобы можно было также гранулярно обновлять и вызвать перерендер только нужных компонентов? Собственно в примере выше я так и делаю - организовываю состояние приложения в виде дерева объектов с полями-атомами и каждый атом можно обновлять независимо от всего остального состояния.

Ну а если при создании объекта задачи или папки мы запишем обратную ссылку на родительский объект то сможем обращаться по цепочке к родительским объектам
<div>board: {task.folder().board().name()}</div>

и при этом благодаря атомам получить автоматическую подписку на изменения полей и перерендер компонента

Вопрос - что изменится с новым апи реатома? Я не вижу никаких кардинальных изменений разве что вместо мобиксовых атомов поля объектов будут завраплены в атомы реатома. Изменится разве что удобство обращения к атомам - вместо "todo.folder().board().name()" придется писать "$($($(todo.folder).board).name)" - поэтому я и предлагаю изменить апи для лучшего удобства пока еще не поздно и вышла только альфа-версия
источник

a

artalar in Reatom — стейт-менеджер
Богдан
Почему не получится использовать реакт таким образом? Как тогда предлагается записывать с реатомом шаблон компонента в том примере? Причем здесь концепция атомарности глобального состояния?

Вот представим что нужно написать приложение в котором юзер может создавать борды, а в бордах папки, а в папках тодо-задачи. Самым простым и удобным способом организации состояния будет вложенность - переменная AppState будет хранить дерево объектов поля которых будут атомами (чтобы происходил перерендер компонентов при изменении).  Вот пример AppState с каким-нибудь начальным состоянием
const AppState = {
boards: Atom([
 {
  name: Atom("board1),
  folders: Atom([
   {
    name: Atom("folder1"),
    tasks: Atom([
     {
      name: Atom("task 1"),
      completed: Atom(false)
     },
     ...
    ])
   }
  ...
  ])
 }
 ...
])
}

Дальше я рендерю это состояние передавая компонентам нужный объект через пропс
const App = observer(() => {
...
<div>
 {AppState.boards().map(board => <Board board={board}/>)}
</div>
...
});

const Board = observer((board) => {
...
<div>{board.name()}</div>
...
<div>
 {board.folders().map(folder => <Folder folder={folder}/>}
</div>
...
})

const Folder = observer((folder) => {
...
<div>{folder.name()}</div>
...
<div>
 {folder.tasks().map(task => <Task task={task}/>}
</div>
...
})

const Task = observer((task) => {
...
<div>{task.name()}</div>
...
})

Дальше компонент получив через пропсы свой объект может свободно его обновлять
const Task = observer((task) => {
const toogleComplete = () => {
 task.completed(!task.completed);
}
...
<div>completed: {task.completed()}</div>
<button onClick={toogleComplete}>toggle complete</button>
...
});

А благодаря тому что поле todo.completed является атомом то произойдет обновление компонента (который подписался на это поле).
И точно так же с созданием новой задачи или папки - просто обновляем атом поля в котором будет храниться список объектов
const Folder = observer(folder => {
const addTodo = () => {
  const newTodo = {
   name: Atom(""),
   completed: Atom(false)
  }
  folder.tasks([...folder.tasks(), newTodo]
}
...
<button onClick={addTodo}>add todo</div>
...
})

Разве с реатомом обновление данных будет выглядеть не так или реатом будет требовать другую организацию состояния? Зачем мне отказываться от удобного хранения через вложенность и передачей компонентам нужного объекта через пропсы? Я не хочу возиться с айдишниками и дополнительно мапить айдишники на объекты или данные прежде чем передать компоненту (как через mapStateToProps в редаксе).
Разве концепция реатома не в том чтобы хранить данные в атомах и организовать их иерархически чтобы можно было также гранулярно обновлять и вызвать перерендер только нужных компонентов? Собственно в примере выше я так и делаю - организовываю состояние приложения в виде дерева объектов с полями-атомами и каждый атом можно обновлять независимо от всего остального состояния.

Ну а если при создании объекта задачи или папки мы запишем обратную ссылку на родительский объект то сможем обращаться по цепочке к родительским объектам
<div>board: {task.folder().board().name()}</div>

и при этом благодаря атомам получить автоматическую подписку на изменения полей и перерендер компонента

Вопрос - что изменится с новым апи реатома? Я не вижу никаких кардинальных изменений разве что вместо мобиксовых атомов поля объектов будут завраплены в атомы реатома. Изменится разве что удобство обращения к атомам - вместо "todo.folder().board().name()" придется писать "$($($(todo.folder).board).name)" - поэтому я и предлагаю изменить апи для лучшего удобства пока еще не поздно и вышла только альфа-версия
Сторить так атомы можно и, наверное, даже нужно - тут я согласен. Не получится подписывать через observe(Component), потому что view - это сайд-эффект и все перевычисление должны батчится и происходит заранее, до уведомления view. Поэтому апишка будет выглядеть как-то так:

const {…data} = useAtom(() => Atom($ => $(props.board.name)), [props.board.name])
источник

Б

Богдан in Reatom — стейт-менеджер
artalar
Сторить так атомы можно и, наверное, даже нужно - тут я согласен. Не получится подписывать через observe(Component), потому что view - это сайд-эффект и все перевычисление должны батчится и происходит заранее, до уведомления view. Поэтому апишка будет выглядеть как-то так:

const {…data} = useAtom(() => Atom($ => $(props.board.name)), [props.board.name])
Я не понимаю почему не получится. Почему в mobx-е получается а тут не получится? Это ничем не отличается от автоподписок в самом атоме
const someObject = { field1: Atom(..), field2: Atom(..) }; 

const SomeAtom = Atom($ => {
return $(someObject.field1) + $(someObject.field2)
});

const SomeComponent = observer($ => () => {
...
<div>{someObject.field1()}</div>
...
<div>{someObject.field2()}</div>
}));
В случая компютед-атома SomeAtom мы сетапим некий пустой список подписчиков куда будем добавлять атомы при вызове через "$(someObject.field1)". Так почему же нельзя сделать также и с компонентом SomeComponent ? Будет некий HOC-враппер который в процессе рендера засетапит пустой массив атомов и вызовет коллбек самого компонента а после его окончания подпишется на эти атомы и вызовет перерендер компонента после срабатывания обновления атомов
источник

a

artalar in Reatom — стейт-менеджер
Богдан
Я не понимаю почему не получится. Почему в mobx-е получается а тут не получится? Это ничем не отличается от автоподписок в самом атоме
const someObject = { field1: Atom(..), field2: Atom(..) }; 

const SomeAtom = Atom($ => {
return $(someObject.field1) + $(someObject.field2)
});

const SomeComponent = observer($ => () => {
...
<div>{someObject.field1()}</div>
...
<div>{someObject.field2()}</div>
}));
В случая компютед-атома SomeAtom мы сетапим некий пустой список подписчиков куда будем добавлять атомы при вызове через "$(someObject.field1)". Так почему же нельзя сделать также и с компонентом SomeComponent ? Будет некий HOC-враппер который в процессе рендера засетапит пустой массив атомов и вызовет коллбек самого компонента а после его окончания подпишется на эти атомы и вызовет перерендер компонента после срабатывания обновления атомов
Мобыкс не поддерживает атомарность.
источник

Б

Богдан in Reatom — стейт-менеджер
artalar
Сторить так атомы можно и, наверное, даже нужно - тут я согласен. Не получится подписывать через observe(Component), потому что view - это сайд-эффект и все перевычисление должны батчится и происходит заранее, до уведомления view. Поэтому апишка будет выглядеть как-то так:

const {…data} = useAtom(() => Atom($ => $(props.board.name)), [props.board.name])
кстати если делать считывание атома через реактовские хуки например useAtom() то у нас появляется "double declaration"-проблема - я про это писал как-то в эффектор-чате - https://t.me/effector_ru/153744
источник

Б

Богдан in Reatom — стейт-менеджер
Переслано от Богдан
Вот смотри - на первом скрине когда понадобилось добавить в шаблоне считывание стора то нужно добавить две строчки а на втором скрине только одну строку
источник