フレームワークに依存しないブラウザベースのSPA

1.しかし...なぜですか?

  1. SPA  (シングルページアプリケーション)を開発するためのフレームワークは多数あります 





  2. 特定のフレームワークに基づいてアプリケーションを作成する方法を説明する大量のドキュメントがあります。





  3. ただし、そのようなドキュメントはフレームワークを最前線に置きます。したがって、フレームワークを実装の詳細から決定要因に変えます。したがって、コードの重要な部分は、ビジネスのニーズを満たすためではなく、フレームワークのニーズを満たすために記述されています。





今日の誇大広告主導のソフトウェア開発がいかに進んでいるかを考えると、数年以内にフロントエンド開発のための新しいファッショナブルなフレームワークがあることを確信できます。アプリケーションを構築するためのフレームワークが時代遅れになった瞬間、レガシーコードベースを維持するか、アプリケーションを新しいフレームワークに転送するプロセスを開始する必要があります。





どちらのオプションもビジネスに悪影響を及ぼします。古いコードベースを維持することは、新しい開発者を雇い、現在の開発者をやる気にさせることに問題があることを意味します。アプリケーションを新しいフレームワークに転送するには、時間(したがってお金)がかかりますが、ビジネス上のメリットはありません。





この記事は、高レベルのアーキテクチャ設計原則を使用してSPAを構築する例です。そうすることで、特定のライブラリとフレームワークが、目的のアーキテクチャによって定義された責任を満たすように選択されます。





2.アーキテクチャの目標と制限

目的:





  1. 新しい開発者は、コードの構造をざっと見るだけで、アプリケーションの目的を理解できます。





  2. 関心の分離が促進され、コードのモジュール性が促進され、次のようになります。





    • モジュールは簡単にテストできます





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





  • mobx-state-treeとの比較 :ビューモデルは通常のクラスであり、サードパーティライブラリの関数を使用する必要はなく、サードパーティフレームワークで定義されたインターフェイスを満たす必要もありません。  mobx-state-tree内の型定義は、このパッケージの特定の関数に依存しています。TypeScriptと組み合わせてmobx-state-treeを使用すると、情報の重複が発生します。タイプフィールドは個別のTypeScriptインターフェイスとして宣言されますが、タイプの定義に使用されるオブジェクトにリストする必要があります。





著者(私)のブログの英語の元の記事








All Articles