SPA baseado em navegador independente de estrutura

1. Mas ... por quĂȘ?

  1. Existe um grande nĂșmero de frameworks para o desenvolvimento de  SPA  (Single Page Application).





  2. Hå uma grande quantidade de documentação que ilustra como criar um aplicativo baseado em uma estrutura específica.





  3. 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.





  1. Um novo desenvolvedor pode entender o propĂłsito de um aplicativo com uma rĂĄpida olhada na estrutura do cĂłdigo.





  2. A separação de interesses é promovida e, portanto, a modularidade do código para que:





    • MĂłdulos sĂŁo fĂĄceis de testar





    •   (boundaries)          .       Â« »





  3.     .  ,       .





  4. . ( )     , .





  5.      .





  6.     . ,     .





:





 .   (  ) 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)   ,  ,     .





:





  1.  â€” .





    • goods-list      , .





    • filters    ,   .





  2. (    )    â€”   «_».     .





    • _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 .   -.





:   asm.jsblazor  elm     





6.  

, : HTML, CSS, JavaScript. ,   4: , .





  [6.1]  HTML  CSS    .





HTML     . ,  underscore.jshandlebars.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 ,  ReactPreact and Inferno. Tsx   HTML,     / HTML.  tsx ..     HTML, .





  ,   , ,   JSX .        React.





:         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.

  , / .   6        .   .





«» , ( )   /, .   (slicing function). , ,   ?





 8: ( /slicing function)





  (  ):





type TViewModelFacade = <TViewModel, TOwnPropsTVMProps>(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:  reducersaction 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)








All Articles