1. Mais ... pourquoi?
Il existe un grand nombre de frameworks pour développer SPA (Single Page Application).
Il existe une énorme quantité de documentation qui illustre comment créer une application basée sur un cadre spécifique.
Cependant, une telle documentation place le cadre au premier plan. Ainsi, transformer le cadre d'un dĂ©tail de mise en Ćuvre en un facteur dĂ©terminant. Ainsi, une partie importante du code est Ă©crite non pas pour rĂ©pondre aux besoins de l'entreprise, mais pour rĂ©pondre aux besoins du framework.
Compte tenu de la façon dont le dĂ©veloppement de logiciels axĂ© sur le battage publicitaire est de nos jours, vous pouvez ĂȘtre sĂ»r que dans quelques annĂ©es, il y aura de nouveaux cadres Ă la mode pour le dĂ©veloppement front-end. Au moment oĂč le framework sur la base duquel l'application est construite se dĂ©mode, vous ĂȘtes obligĂ© de maintenir la base de code hĂ©ritĂ©e ou de dĂ©marrer le processus de transfert de l'application vers un nouveau framework.
Les deux options sont préjudiciables à l'entreprise. Le maintien d'une base de code obsolÚte entraßne des problÚmes d'embauche de nouveaux développeurs actuels et de motivation. Le transfert d'une application vers un nouveau framework coûte du temps (et donc de l'argent) mais n'apporte aucun avantage commercial.
Cet article est un exemple de création d'un SPA à l'aide de principes de conception d'architecture de haut niveau. Ce faisant, des bibliothÚques et des frameworks spécifiques sont choisis pour répondre aux responsabilités définies par l'architecture souhaitée.
2. Objectifs et limites de l'architecture
Objectifs:
Un nouveau dĂ©veloppeur peut comprendre le but d'une application avec un coup d'Ćil rapide sur la structure du code.
La séparation des préoccupations est favorisée et donc la modularité du code afin que:
Les modules sont faciles Ă tester
(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 . . .
ComparĂ© Ă mobx-state-tree : Les Viewmodels sont des classes rĂ©guliĂšres et ne nĂ©cessitent pas l'utilisation de fonctions de bibliothĂšques tierces, ni ne doivent satisfaire l'interface dĂ©finie par des frameworks tiers. La dĂ©finition de type Ă l'intĂ©rieur de l'arborescence d'Ă©tat de mobx repose sur les fonctions spĂ©cifiques de ce paquet. L'utilisation de mobx-state-tree en conjonction avec TypeScript provoque la duplication des informations - les champs de type sont dĂ©clarĂ©s comme une interface TypeScript distincte mais doivent ĂȘtre rĂ©pertoriĂ©s dans l'objet utilisĂ© pour dĂ©finir le type.
L'article original en anglais dans le blog de l'auteur (moi)