1. Aber ... warum?
Es gibt eine Vielzahl von Frameworks fĂŒr die Entwicklung von SPA (Single Page Application).
Es gibt eine Vielzahl von Dokumentationen, die veranschaulichen, wie eine Anwendung basierend auf einem bestimmten Framework erstellt wird.
Eine solche Dokumentation stellt jedoch das Framework in den Vordergrund. So wird das Framework von einem Implementierungsdetail zu einem bestimmenden Faktor. Daher ist ein wesentlicher Teil des Codes nicht so geschrieben, dass er den Anforderungen des Unternehmens entspricht, sondern den Anforderungen des Frameworks.
Wenn man bedenkt, wie hype-getriebene Softwareentwicklung heutzutage ist, können Sie sicher sein, dass es in einigen Jahren neue modische Frameworks fĂŒr die Front-End-Entwicklung geben wird. In dem Moment, in dem das Framework, auf dessen Grundlage die Anwendung erstellt wird, aus der Mode kommt, mĂŒssen Sie entweder die Legacy-Codebasis beibehalten oder den Prozess der Ăbertragung der Anwendung auf ein neues Framework starten.
Beide Optionen wirken sich nachteilig auf das GeschĂ€ft aus. Die Aufrechterhaltung einer veralteten Codebasis bedeutet Probleme bei der Einstellung neuer und motivierender aktueller Entwickler. Das Ăbertragen einer Anwendung auf ein neues Framework kostet Zeit (und damit Geld), bringt jedoch keinen geschĂ€ftlichen Nutzen.
Dieser Artikel ist ein Beispiel fĂŒr die Erstellung eines SPA unter Verwendung von Architekturprinzipien auf hoher Ebene. Dabei werden bestimmte Bibliotheken und Frameworks ausgewĂ€hlt, um die durch die gewĂŒnschte Architektur definierten Verantwortlichkeiten zu erfĂŒllen.
2. Architektonische Ziele und Grenzen
Ziele:
Ein neuer Entwickler kann den Zweck einer Anwendung mit einem flĂŒchtigen Blick auf die Struktur des Codes verstehen.
Die Trennung von Bedenken wird gefördert und damit die ModularitÀt des Codes, so dass:
Module sind einfach zu testen
(boundaries) . « »
-
. ( ) , .
.
. , .
:
. ( ) HTML+CSS JavaScript .
3.
. : (layered), (onion) (hexagonal). .
/ SPA . (domain) (application) . , â .
, .
( Ports and Adapters) . localStorage TodoMVC ( boundaries/local-storage).
4. . SPA ?
. :
1: ,
? 2 .
2: , 1
âsharedâ UI , , , .
( ) . ââ âpartsâ. ( 3).
3: âpartsâ
, âgoods catalogueâ. âgoods-catalogue/parts/goods-list/parts/good-details.jsâ . â .
«parts» . 4.
4: âpartsâ
âgoods-catalogue/goods-listâ . goods-list.js () â , . , - (js, html, css) , , .
:
â .
goods-list , .
filters , .
( ) â «_». .
_goods-list folder goods-catalogue .
goods-list.js _goods-list .
_good-details.js _goods-list .
5: «_»
! , . . pages components 5. HTML component. components , «» .
5. . JavaScript?
JavaScript. . ( 1-20), ...
, . . 4- . , 4 . . , 2015 , . , , .
JavaScript (babel) JavaScript, « » JavaScript. â , .
, â TypeScript :
- JavaScript, JavaScript
(typings) JavaScript . , npm . , TypeScript . -.
6.
, : HTML, CSS, JavaScript. , 4: , .
[6.1] HTML CSS .
HTML . , underscore.js, handlebars.js. , .
[6.2] TypeScript , (). .
UI . HTML HTML . . . . , .
[6.3] . .
[6.4] :
, .
. .
Domain Application. , Dependency Injection. .
â . . , , ----html-. . , .
, , . , . :
, .. .
.. .
, [6.5] â TypeScript . , .
, :
(Components) â HTML + CSS
(ViewModels) â , , ( ).
(ViewModel facades) â , .
6:
- . .
().
â . / . «shared».
â . /.
? 6 . () . , .
[6.6] â .
7:
7.
. â .
7.1.
- tsx ( jsx). tsx , React, Preact and Inferno. Tsx HTML, / HTML. tsx .. HTML, .
: React. react hooks - . API React , .
, . UI=F(S)
UI â
F â
S â ( â )
:
interface ITodoItemAttributes {
name: string;
status: TodoStatus;
toggleStatus: () => void;
removeTodo: () => void;
}
const TodoItemDisconnected = (props: ITodoItemAttributes) => {
const className = props.status === TodoStatus.Completed ? 'completed' : '';
return (
<li className={className}>
<div className="view">
<input className="toggle" type="checkbox" onChange={props.toggleStatus} checked={props.status === TodoStatus.Completed} />
<label>{props.name}</label>
<button className="destroy" onClick={props.removeTodo} />
</div>
</li>
)
}
todo TodoMVC .
â JSX. . , «».
[6.1] [6.2].
: react TodoMVC .
7.2. ()
, TypeScript -:
.
domain/application dependency injection.
, , .
(reactive UI). . WPF (C#) Model-View-ViewModel. JavaScript , (observable) (stores) flux. , :
.
, .
.
, .
:
, , .
, .
mobx , . :
class TodosVM {
@mobx.observable
private todoList: ITodoItem[];
// use "poor man DI", but in the real applications todoDao will be initialized by the call to IoC container
constructor(props: { status: TodoStatus }, private readonly todoDao: ITodoDAO = new TodoDAO()) {
this.todoList = [];
}
public initialize() {
this.todoList = this.todoDao.getList();
}
@mobx.action
public removeTodo = (id: number) => {
const targetItemIndex = this.todoList.findIndex(x => x.id === id);
this.todoList.splice(targetItemIndex, 1);
this.todoDao.delete(id);
}
public getTodoItems = (filter?: TodoStatus) => {
return this.todoList.filter(x => !filter || x.status === filter) as ReadonlyArray<Readonly<ITodoItem>>;
}
/// ... other methods such as creation and status toggling of todo items ...
}
mobx , .
mobx . mobx. .
{status: TodoStatus}
. [6.6]. . :
interface IVMConstructor<TProps, TVM extends IViewModel<TProps>> {
new (props: TProps, ...dependencies: any[]) : TVM;
}
interface IViewModel<IProps = Record<string, unknown>> {
initialize?: () => Promise<void> | void;
cleanup?: () => void;
onPropsChanged?: (props: IProps) => void;
}
. :
(-).
, ( statefull). .
7, . DOM(mounted) (unmounted). (higher order components).
:
type TWithViewModel = <TAttributes, TViewModelProps, TViewModel> ( moduleRootComponent: Component<TAttributes & TViewModelProps>, vmConstructor: IVMConstructor<TAttributes, TViewModel>, ) => Component<TAttributes>
moduleRootComponent, :
(mount) .
() (unmount).
TodoMVC . .. IoC , .
:
const TodoMVCDisconnected = (props: { status: TodoStatus }) => {
return <section className="todoapp">
<Header />
<TodoList status={props.status} />
<Footer selectedStatus={props.status} />
</section>
};
const TodoMVC = withVM(TodoMVCDisconnected, TodosVM);
( , ), <TodoMVC status={statusReceivedFromRouteParameters} />
. , TodosVM
- TodoMVC
.
, , withVM.
TodoMVCDisconnected
TodoMVC ,
TodosVM . , , mobx .
: , withVM react context API. . , â connectFn .
7.3.
«» , ( ) /, . (slicing function). , , ?
8: ( /slicing function)
( ):
type TViewModelFacade = <TViewModel, TOwnProps, TVMProps>(vm: TViewModel, ownProps?: TOwnProps) => TVMProps
connect Redux. mapStateToProps
, mapDispatchToActions
mergeProps
â , . TodoItemDisconnected
TodosVM
.
const sliceTodosVMProps = (vm: TodosVM, ownProps: {id: string, name: string, status: TodoStatus; }) => {
return {
toggleStatus: () => vm.toggleStatus(ownProps.id),
removeTodo: () => vm.removeTodo(ownProps.id),
}
}
: , âOwnPropsâ - react/redux.
â . withVM
. , , â , :
type connectFn = <TViewModel, TVMProps, TOwnProps = {}> ( ComponentToConnect: Component<TVMProps & TOwnProps>, mapVMToProps: TViewModelFacade<TViewModel, TOwnProps, TVMProps>, ) => Component<TOwnProps> const TodoItem = connectFn(TodoItemDisconnected, sliceTodosVMProps);
todo : <TodoItem id={itemId} name={itemName} status={itemStatus} />
connectFn
:
TodoItemDisconnected
sliceTodosVMProps
â JSX.
, , , .
connectFn TodoMVC , .
8.
, , . TypeScript , , TSX â .
SPA . SPA « » « ».
, ?
- mobx, react mobx-react , :
mobx
- , . TodoMVC react-router react-router-dom.
, , JSX.
, .
, .
. React , .
P.S. SPA:
React/Redux: reducers, action creators middlewares. ( stateful). time-travel. . connect . Redux-dirven connected . , .
vue: TSX. , , . Vue.js âdataâ,âmethodsâ, .. vue- .
angular: TSX. angular- . (two-way data binding). : , , .
react (hooks, useState/useContext): . , - . :
.
useEffect âdepsâ .
.
.
, ( â useEffect) . , «», « (mental model)» « (best practices)». react. :
react-mobx . react-mobx . . .
Im Vergleich zu mobx-state-tree : Viewmodels sind regulĂ€re Klassen und erfordern weder die Verwendung von Funktionen aus Bibliotheken von Drittanbietern noch mĂŒssen sie die von Frameworks von Drittanbietern definierte Schnittstelle erfĂŒllen. Die Typdefinition im mobx-state-tree hĂ€ngt von den spezifischen Funktionen dieses Pakets ab. Die Verwendung von mobx-state-tree in Verbindung mit TypeScript fĂŒhrt zu einer Duplizierung von Informationen. Typfelder werden als separate TypeScript-Schnittstelle deklariert, mĂŒssen jedoch in dem Objekt aufgelistet werden, das zum Definieren des Typs verwendet wird.
Der Originalartikel in englischer Sprache im Blog des Autors (ich)