Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add mobx models #8

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open

Add mobx models #8

wants to merge 2 commits into from

Conversation

EgorKluch
Copy link
Owner

No description provided.

Copy link

@kitcat-dev kitcat-dev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Оставил несколько предложений там, где я сделал бы по-другому. Надеюсь, мои комментарии будут полезны)

Comment on lines 42 to 48
get isRemoving() {
return this._isRemoving
}

set isRemoving(isRemoving) {
this._isRemoving = isRemoving;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Я бы сделал так, чтобы не создавать 4 булевых переменных для каждого статуса и не пытаться жонглировать ими внутри экшнов:

type Status = 'idle' | 'pending' | 'success' | 'error'

class ItemModel {
  private removingStatus: Status;
  
  //  ...

  setRemovingStatus(status: Status) {
    this.removingStatus = status;
  }

  get isRemoving() {
    return this.removingStatus === 'pending'
  }
}

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А где ты видишь 4 булевых переменных?) Это не то что ты подумал кажется, это индикатор открыта ли модалка)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ок, если так, то достаточно:

// store:
public isRemoving: boolean;

setIsRemoving(isRemoving: boolean) {
  this.isRemoving = isRemoving;
}

// consumer:
ItemModel.isRemoving

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Как ты видишь, я не фанат лишних геттеров. Если он лишь отдает состояние приватной переменной, то незачем добавлять сложность :D

items: Item[];
}

export class ItemListModel {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Модель – это лишь часть стора. Что-то, что перечислено в следующих трех строчках, что можно записать в DTO-like интерфейс и что можно сделать базой для стора вот так:

interface ItemListModel {
  _items: ItemModel[];
  _onlyChecked: boolean;
  _removingId: number | null;
}

export class ItemListStore implements ItemListModel {
  protected _items = [];
  protected _onlyChecked = false;
  protected _removingId = null;
  
  ...
}

Mobx-state-tree (opinionated обертка над MobX) тоже считает так: https://mobx-state-tree.js.org/concepts/trees#creating-models

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Поэтому предлагаю переименовать стор, например, в ItemListStore

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Я не понимаю что ты понимаешь под моделью. И не понимаю концепцию стора в Mobx, нафига он там нужен) Гляну, почитаю.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Стор – просто любая observable-сущность с данными, которая создается через makeAutoObservable или ее аналог

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А модель?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Модель, вью (пропертис и их компьютеды), и экшны – части стора.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Почитаю и если че обсудим голосом. Это какая-то терминология из какой-то конкретной метологии конкретно) Видимо словарик нужен что бы не путаться

this._ids = [...this._ids, id];

return () => {
this._ids = this._ids.filter((checkingId) => checkingId !== id);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А это разве будет работать без runInAction (src)?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Кажется, да, но будет спамить ворнинги в консоль, ведь это другая функция, не экшн.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Спасибо, пропустил

private _ids: string[] = [];

constructor() {
makeAutoObservable(this);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Предлагаю сразу привязывать контекст для всех методов через makeAutoObservable(this, undefined, {autoBind: true}).

По моему опыту, у этого нет недостатков, и в следующем MobX 7 это, возможно, будет сделано по умолчанию: mobxjs/mobx#3796

Copy link
Owner Author

@EgorKluch EgorKluch Aug 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Это на усмотрение. В рамках демки это не важно)
Так звучит разумно, только что бы не помнить каждый раз и не писать в каждой модели обертку надо сделать просто типа makeAutoBindObservable

@@ -100,15 +93,40 @@ export const ListPage: FC = () => {
<li>Come back here and check that data will update</li>
</ul>
<ConfirmModal
show={!!removingItem}
show={!!itemListModel.removingId}
Copy link

@kitcat-dev kitcat-dev Aug 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Давай отдавать сюда компьютед, чтобы пользователь (ListPage) не видел внутренности стора, а пользовался понятным API. Что-то вроде:

// ItemListModel.ts
get canRemoveItem() {
  return this.removingId !== null;
}

// ListPage.tsx
<ConfirmModal show={itemListModel.canRemoveItem} ... />

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Это просто разный подходы) Можем отдельно подискутировать по этому поводу.

.finally(hideLoader);
}}
>Save</Button>
<Button
className='m-1'
variant='danger'
onClick={() => setRemoveConfirmationOpened(true)}
onClick={() => itemModel.isRemoving = true}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Тут тоже надо менять состояние стора через экшн.

Comment on lines 88 to 127
export const ItemPage: FC = observer(() => {
const { id } = useParams<{ id: string }>();
const loader = useLoaderModel();

const {data, refetch, isFetching, isError: isItemError} = useQuery({
queryKey: ['item', id],
queryFn: () => api.getItem(Number(id)),
});

useEffect(() => {
if (isFetching) {
return loader.show();
}
}, [isFetching, loader]);

const [itemModel, setItemModel] = useState<ItemModel | null>(null);
const [error, setError] = useState<string | null>(null);

useEffect(() => {
if(isItemError || isErrorItem(data)) {
setError((data as ItemResponseError)?.error || 'Item data loading error');
} else if(data) {
setItemModel(new ItemModel({ item: data }))
}
},[data, isItemError])

if (error) {
return (
<div style={{ color: 'red' }}>{error}</div>
)
}

if (!itemModel) {
return null;
}

return (
<ItemPageView itemModel={itemModel} refetchItem={refetch}/>
)
})
Copy link

@kitcat-dev kitcat-dev Aug 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Я понимаю, что код демонстративный, но тут точно надо хэндлить две асинхронных операции одновременно: из ItemModel и useQuery? Немного не понимаю, что здесь происходит =/

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Вот эта как раз часть приближена к реальному использованию. Обсудим голосом, это очень важно. Спасибо!

return new ItemListModel({ items: items || [] });
}, [items]);

return (

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Кстати, если передаем стор ниже, то можно на уровне страницы положить его в провайдер, чтобы не проп дриллить. Но это уже по ситуации. Если вложенность небольшая, то легче будет пару раз передать как проп.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Все так, зависит от ситуации. Тут простое правило - заменять props drilling на провайдер когда глубина больше двух) А сразу смысла нет - потому что ты никогда не видел новогоднюю елку из провайдеров? Когда там аля 10 вложенных провайдеров? Я видел)

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Такое колличество провайдеров держать в голове нереально) Кароч провайдеры нужно юзать когда это действительно необходимо

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

};

export const LoaderProvider: FC<Props> = (props) => {
const loaderModel = useMemo(() => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Тут тоже можно заменить на useState :)

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Вкусовщина) Просто разные подходы

}

add(item: Item) {
this._items = [...this._items, new ItemModel({ item })];
Copy link

@kitcat-dev kitcat-dev Aug 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Или просто this._items.push(new ItemModel({ item })), ведь одна из фишек мобикса – мутабельность.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Вот помню раньше когда-то так плохо работало) Проверю

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants