1.しかし...なぜですか?
SPA (シングルページアプリケーション)を開発するためのフレームワークは多数あります 。
特定のフレームワークに基づいてアプリケーションを作成する方法を説明する大量のドキュメントがあります。
ただし、そのようなドキュメントはフレームワークを最前線に置きます。したがって、フレームワークを実装の詳細から決定要因に変えます。したがって、コードの重要な部分は、ビジネスのニーズを満たすためではなく、フレームワークのニーズを満たすために記述されています。
今日の誇大広告主導のソフトウェア開発がいかに進んでいるかを考えると、数年以内にフロントエンド開発のための新しいファッショナブルなフレームワークがあることを確信できます。アプリケーションを構築するためのフレームワークが時代遅れになった瞬間、レガシーコードベースを維持するか、アプリケーションを新しいフレームワークに転送するプロセスを開始する必要があります。
どちらのオプションもビジネスに悪影響を及ぼします。古いコードベースを維持することは、新しい開発者を雇い、現在の開発者をやる気にさせることに問題があることを意味します。アプリケーションを新しいフレームワークに転送するには、時間(したがってお金)がかかりますが、ビジネス上のメリットはありません。
この記事は、高レベルのアーキテクチャ設計原則を使用してSPAを構築する例です。そうすることで、特定のライブラリとフレームワークが、目的のアーキテクチャによって定義された責任を満たすように選択されます。
2.アーキテクチャの目標と制限
目的:
新しい開発者は、コードの構造をざっと見るだけで、アプリケーションの目的を理解できます。
関心の分離が促進され、コードのモジュール性が促進され、次のようになります。
モジュールは簡単にテストできます
(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 . . .
mobx-state-treeとの比較 :ビューモデルは通常のクラスであり、サードパーティライブラリの関数を使用する必要はなく、サードパーティフレームワークで定義されたインターフェイスを満たす必要もありません。 mobx-state-tree内の型定義は、このパッケージの特定の関数に依存しています。TypeScriptと組み合わせてmobx-state-treeを使用すると、情報の重複が発生します。タイプフィールドは個別のTypeScriptインターフェイスとして宣言されますが、タイプの定義に使用されるオブジェクトにリストする必要があります。