-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Add mobx models #8
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Оставил несколько предложений там, где я сделал бы по-другому. Надеюсь, мои комментарии будут полезны)
src/models/ItemModel.ts
Outdated
get isRemoving() { | ||
return this._isRemoving | ||
} | ||
|
||
set isRemoving(isRemoving) { | ||
this._isRemoving = isRemoving; | ||
} |
There was a problem hiding this comment.
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'
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
А где ты видишь 4 булевых переменных?) Это не то что ты подумал кажется, это индикатор открыта ли модалка)
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Поэтому предлагаю переименовать стор, например, в ItemListStore
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Я не понимаю что ты понимаешь под моделью. И не понимаю концепцию стора в Mobx, нафига он там нужен) Гляну, почитаю.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Стор – просто любая observable-сущность с данными, которая создается через makeAutoObservable
или ее аналог
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
А модель?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Модель, вью (пропертис и их компьютеды), и экшны – части стора.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
А это разве будет работать без runInAction
(src)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Кажется, да, но будет спамить ворнинги в консоль, ведь это другая функция, не экшн.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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} |
There was a problem hiding this comment.
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} ... />
There was a problem hiding this comment.
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} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Тут тоже надо менять состояние стора через экшн.
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}/> | ||
) | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Я понимаю, что код демонстративный, но тут точно надо хэндлить две асинхронных операции одновременно: из ItemModel
и useQuery
? Немного не понимаю, что здесь происходит =/
There was a problem hiding this comment.
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 ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Кстати, если передаем стор ниже, то можно на уровне страницы положить его в провайдер, чтобы не проп дриллить. Но это уже по ситуации. Если вложенность небольшая, то легче будет пару раз передать как проп.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Все так, зависит от ситуации. Тут простое правило - заменять props drilling на провайдер когда глубина больше двух) А сразу смысла нет - потому что ты никогда не видел новогоднюю елку из провайдеров? Когда там аля 10 вложенных провайдеров? Я видел)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Такое колличество провайдеров держать в голове нереально) Кароч провайдеры нужно юзать когда это действительно необходимо
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
}; | ||
|
||
export const LoaderProvider: FC<Props> = (props) => { | ||
const loaderModel = useMemo(() => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Тут тоже можно заменить на useState
:)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Вкусовщина) Просто разные подходы
src/models/ItemListModel.ts
Outdated
} | ||
|
||
add(item: Item) { | ||
this._items = [...this._items, new ItemModel({ item })]; |
There was a problem hiding this comment.
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 }))
, ведь одна из фишек мобикса – мутабельность.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Вот помню раньше когда-то так плохо работало) Проверю
No description provided.