1. Mas ... por quĂȘ?
Existe um grande nĂșmero de frameworks para o desenvolvimento de SPA (Single Page Application).
HĂĄ uma grande quantidade de documentação que ilustra como criar um aplicativo baseado em uma estrutura especĂfica.
No entanto, qualquer documentação coloca a estrutura na vanguarda. Assim, transformando o framework de um detalhe de implementação em um fator determinante. Assim, uma parte significativa do código é escrita não para atender às necessidades do negócio, mas para atender às necessidades da estrutura.
Considerando o quanto o desenvolvimento de software Ă© impulsionado pelo hype hoje em dia, vocĂȘ pode ter certeza de que em alguns anos haverĂĄ novos frameworks da moda para o desenvolvimento de front-end. No momento em que a estrutura com base na qual o aplicativo Ă© construĂdo sai de moda, vocĂȘ Ă© forçado a manter a base de cĂłdigo legado ou iniciar o processo de transferĂȘncia do aplicativo para uma nova estrutura.
Ambas as opçÔes sĂŁo prejudiciais para o negĂłcio. Manter uma base de cĂłdigo desatualizada significa problemas com a contratação de novos desenvolvedores e motivação para os atuais. Transferir um aplicativo para uma nova estrutura custa tempo (e, portanto, dinheiro), mas nĂŁo traz nenhum benefĂcio comercial.
Este artigo Ă© um exemplo de construção de um SPA usando princĂpios de design de arquitetura de alto nĂvel. Ao fazer isso, bibliotecas e estruturas especĂficas sĂŁo escolhidas para atender Ă s responsabilidades definidas pela arquitetura desejada.
2. Objetivos e limitaçÔes arquitetÎnicas
Objetivos.
Um novo desenvolvedor pode entender o propĂłsito de um aplicativo com uma rĂĄpida olhada na estrutura do cĂłdigo.
A separação de interesses é promovida e, portanto, a modularidade do código para que:
MĂłdulos sĂŁo fĂĄceis de testar
(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 . . .
Comparado com mobx-state-tree : Viewmodels sĂŁo classes regulares e nĂŁo requerem o uso de funçÔes de bibliotecas de terceiros, nem precisam satisfazer a interface definida por frameworks de terceiros. A definição de tipo dentro da ĂĄrvore mobx-state depende das funçÔes especĂficas deste pacote. Usar mobx-state-tree em conjunto com TypeScript provoca duplicação de informaçÔes - campos de tipo sĂŁo declarados como uma interface TypeScript separada, mas devem ser listados no objeto usado para definir o tipo.
O artigo original em inglĂȘs no blog do autor (eu)