Update mvvm doc (#30872)
* doc: update mvvm doc * Apply suggestions from code review Co-authored-by: David Baker <dbkr@users.noreply.github.com> * doc: keep v1 version * doc: fix formatting --------- Co-authored-by: David Baker <dbkr@users.noreply.github.com>
This commit is contained in:
69
docs/MVVM-v1.md
Normal file
69
docs/MVVM-v1.md
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
# MVVM
|
||||||
|
|
||||||
|
_Deprecated_, see [MVVM.md](./MVVM.md) for the current version.
|
||||||
|
|
||||||
|
General description of the pattern can be found [here](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel). But the gist of it is that you divide your code into three sections:
|
||||||
|
|
||||||
|
1. Model: This is where the business logic and data resides.
|
||||||
|
2. View Model: This code exists to provide the logic necessary for the UI. It directly uses the Model code.
|
||||||
|
3. View: This is the UI code itself and depends on the view model.
|
||||||
|
|
||||||
|
If you do MVVM right, your view should be dumb i.e it gets data from the view model and merely displays it.
|
||||||
|
|
||||||
|
### Practical guidelines for MVVM in element-web
|
||||||
|
|
||||||
|
#### Model
|
||||||
|
|
||||||
|
This is anywhere your data or business logic comes from. If your view model is accessing something simple exposed from `matrix-js-sdk`, then the sdk is your model. If you're using something more high level in element-web to get your data/logic (eg: `MemberListStore`), then that becomes your model.
|
||||||
|
|
||||||
|
#### View Model
|
||||||
|
|
||||||
|
1. View model is always a custom react hook named like `useFooViewModel()`.
|
||||||
|
2. The return type of your view model (known as view state) must be defined as a typescript interface:
|
||||||
|
```ts
|
||||||
|
inteface FooViewState {
|
||||||
|
somethingUseful: string;
|
||||||
|
somethingElse: BarType;
|
||||||
|
update: () => Promise<void>
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
3. Any react state that your UI needs must be in the view model.
|
||||||
|
|
||||||
|
#### View
|
||||||
|
|
||||||
|
1. Views are simple react components (eg: `FooView`).
|
||||||
|
2. Views usually start by calling the view model hook, eg:
|
||||||
|
```tsx
|
||||||
|
const FooView: React.FC<IProps> = (props: IProps) => {
|
||||||
|
const vm = useFooViewModel();
|
||||||
|
....
|
||||||
|
return(
|
||||||
|
<div>
|
||||||
|
{vm.somethingUseful}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
3. Views are also allowed to accept the view model as a prop, eg:
|
||||||
|
```tsx
|
||||||
|
const FooView: React.FC<IProps> = ({ vm }: IProps) => {
|
||||||
|
....
|
||||||
|
return(
|
||||||
|
<div>
|
||||||
|
{vm.somethingUseful}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
4. Multiple views can share the same view model if necessary.
|
||||||
|
|
||||||
|
### Benefits
|
||||||
|
|
||||||
|
1. MVVM forces a separation of concern i.e we will no longer have large react components that have a lot of state and rendering code mixed together. This improves code readability and makes it easier to introduce changes.
|
||||||
|
2. Introduces the possibility of code reuse. You can reuse an old view model with a new view or vice versa.
|
||||||
|
3. Adding to the point above, in future you could import element-web view models to your project and supply your own views thus creating something similar to the [hydrogen sdk](https://github.com/element-hq/hydrogen-web/blob/master/doc/SDK.md).
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
We started experimenting with MVVM in the redesigned memberlist, you can see the code [here](https://github.com/vector-im/element-web/blob/develop/src/components/views/rooms/MemberList/MemberListView.tsx).
|
||||||
98
docs/MVVM.md
98
docs/MVVM.md
@@ -10,58 +10,80 @@ If you do MVVM right, your view should be dumb i.e it gets data from the view mo
|
|||||||
|
|
||||||
### Practical guidelines for MVVM in element-web
|
### Practical guidelines for MVVM in element-web
|
||||||
|
|
||||||
|
A first documentation and implementation of MVVM was done in [MVVM-v1.md](MVVM-v1.md). This v1 version is now deprecated and this document describes the current implementation.
|
||||||
|
|
||||||
#### Model
|
#### Model
|
||||||
|
|
||||||
This is anywhere your data or business logic comes from. If your view model is accessing something simple exposed from `matrix-js-sdk`, then the sdk is your model. If you're using something more high level in element-web to get your data/logic (eg: `MemberListStore`), then that becomes your model.
|
This is anywhere your data or business logic comes from. If your view model is accessing something simple exposed from `matrix-js-sdk`, then the sdk is your model. If you're using something more high level in element-web to get your data/logic (eg: `MemberListStore`), then that becomes your model.
|
||||||
|
|
||||||
#### View Model
|
|
||||||
|
|
||||||
1. View model is always a custom react hook named like `useFooViewModel()`.
|
|
||||||
2. The return type of your view model (known as view state) must be defined as a typescript interface:
|
|
||||||
```ts
|
|
||||||
inteface FooViewState {
|
|
||||||
somethingUseful: string;
|
|
||||||
somethingElse: BarType;
|
|
||||||
update: () => Promise<void>
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
3. Any react state that your UI needs must be in the view model.
|
|
||||||
|
|
||||||
#### View
|
#### View
|
||||||
|
|
||||||
1. Views are simple react components (eg: `FooView`).
|
1. Located in [`shared-components`](https://github.com/element-hq/element-web/tree/develop/src/shared-components). Develop it in storybook!
|
||||||
2. Views usually start by calling the view model hook, eg:
|
2. Views are simple react components (eg: `FooView`).
|
||||||
|
3. Views use [useSyncExternalStore](https://react.dev/reference/react/useSyncExternalStore) internally where the view model is the external store.
|
||||||
|
4. Views should define the interface of the view model they expect:
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
const FooView: React.FC<IProps> = (props: IProps) => {
|
// Snapshot is the return type of your view model
|
||||||
const vm = useFooViewModel();
|
interface FooViewSnapshot {
|
||||||
....
|
value: string;
|
||||||
return(
|
}
|
||||||
<div>
|
|
||||||
{vm.somethingUseful}
|
// To call function on the view model
|
||||||
</div>
|
interface FooViewActions {
|
||||||
);
|
doSomething: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ViewModel is a type defining the methods needed for `useSyncExternalStore`
|
||||||
|
// https://github.com/element-hq/element-web/blob/develop/src/shared-components/ViewModel.ts
|
||||||
|
type FooViewModel = ViewModel<FooViewSnapshot> & FooViewActions;
|
||||||
|
|
||||||
|
interface FooViewProps {
|
||||||
|
vm: FooViewModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
function FooView({ vm }: FooViewProps) {
|
||||||
|
// useViewModel is a helper function that uses useSyncExternalStore under the hood
|
||||||
|
const { value } = useViewModel(vm);
|
||||||
|
return (
|
||||||
|
<button type="button" onClick={() => vm.doSomething()}>
|
||||||
|
{value}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
3. Views are also allowed to accept the view model as a prop, eg:
|
|
||||||
```tsx
|
5. Multiple views can share the same view model if necessary.
|
||||||
const FooView: React.FC<IProps> = ({ vm }: IProps) => {
|
6. A full example is available [here](https://github.com/element-hq/element-web/blob/develop/src/shared-components/audio/AudioPlayerView/AudioPlayerView.tsx)
|
||||||
....
|
|
||||||
return(
|
#### View Model
|
||||||
<div>
|
|
||||||
{vm.somethingUseful}
|
1. A View model is a class extending [`BaseViewModel`](https://github.com/element-hq/element-web/blob/develop/src/viewmodels/base/BaseViewModel.ts).
|
||||||
</div>
|
2. Implements the interface defined in the view (e.g `FooViewModel` in the example above).
|
||||||
);
|
3. View models define a snapshot type that defines the data the view will consume. The snapshot is immutable and can only be changed by calling `this.snapshot.set(...)` in the view model. This will trigger a re-render in the view.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface Props {
|
||||||
|
propsValue: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
class FooViewModel extends BaseViewModel<FooViewSnapshot, Props> implements FooViewModel {
|
||||||
|
constructor(props: Props) {
|
||||||
|
// Call super with initial snapshot
|
||||||
|
super(props, { value: "initial" });
|
||||||
|
}
|
||||||
|
|
||||||
|
public doSomething() {
|
||||||
|
// Call this.snapshot.set to update the snapshot
|
||||||
|
this.snapshot.set({ value: "changed" });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
4. Multiple views can share the same view model if necessary.
|
|
||||||
|
4. A full example is available [here](https://github.com/element-hq/element-web/blob/develop/src/viewmodels/audio/AudioPlayerViewModel.ts)
|
||||||
|
|
||||||
### Benefits
|
### Benefits
|
||||||
|
|
||||||
1. MVVM forces a separation of concern i.e we will no longer have large react components that have a lot of state and rendering code mixed together. This improves code readability and makes it easier to introduce changes.
|
1. MVVM forces a separation of concern i.e we will no longer have large react components that have a lot of state and rendering code mixed together. This improves code readability and makes it easier to introduce changes.
|
||||||
2. Introduces the possibility of code reuse. You can reuse an old view model with a new view or vice versa.
|
2. Introduces the possibility of code reuse. You can reuse an old view model with a new view or vice versa.
|
||||||
3. Adding to the point above, in future you could import element-web view models to your project and supply your own views thus creating something similar to the [hydrogen sdk](https://github.com/element-hq/hydrogen-web/blob/master/doc/SDK.md).
|
3. Adding to the point above, in future you could import element-web view models to your project and supply your own views thus creating something similar to the [hydrogen sdk](https://github.com/element-hq/hydrogen-web/blob/master/doc/SDK.md).
|
||||||
|
|
||||||
### Example
|
|
||||||
|
|
||||||
We started experimenting with MVVM in the redesigned memberlist, you can see the code [here](https://github.com/vector-im/element-web/blob/develop/src/components/views/rooms/MemberList/MemberListView.tsx).
|
|
||||||
|
|||||||
Reference in New Issue
Block a user