Framework-unabhÀngiges browserbasiertes SPA

1. Aber ... warum?

  1. Es gibt eine Vielzahl von Frameworks fĂŒr die Entwicklung von  SPA  (Single Page Application).





  2. Es gibt eine Vielzahl von Dokumentationen, die veranschaulichen, wie eine Anwendung basierend auf einem bestimmten Framework erstellt wird.





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





  1. Ein neuer Entwickler kann den Zweck einer Anwendung mit einem flĂŒchtigen Blick auf die Struktur des Codes verstehen.





  2. Die Trennung von Bedenken wird gefördert und damit die ModularitÀt des Codes, so dass:





    • Module sind einfach zu testen





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





  • 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)








All Articles