Vue.js * How to make front-end / vuejs how to make front-end

E37b4344ef4bfd0fc4826c04971e54fb?s=47 nrs
September 25, 2019

Vue.js * How to make front-end / vuejs how to make front-end

E37b4344ef4bfd0fc4826c04971e54fb?s=128

nrs

September 25, 2019
Tweet

Transcript

  1. Vue.js * How to make front-end Masanobu Naruse

  2. Sample https://github.com/nrslib/vue-sample-assortment 2

  3. 諸注意 3 CSRF 対策とかやってなかったり 取り回しが甘かったりしてます express, vuetify はじめて触りました Vue.js ですけど

    ts です
  4. Agenda 1. つつましいエラーハンドリング 2. 認証認可の戦略 3. 全体的なコンポーネント構造 4

  5. Agenda 1. つつましいエラーハンドリング 2. 認証認可の戦略 3. 全体的なコンポーネント構造 5

  6. エラー 6

  7. 好きですか? エラー 7

  8. エラーハンドリング 8

  9. 好きですか? エラーハンドリング 9

  10. エラーハンドリングは ソフトウェアの使い勝手を 決定づける重要な要素 10

  11. その上でもう一度 11

  12. 好きですか? エラーハンドリング 12

  13. 僕はあんまり好きじゃないです 13

  14. 本質的じゃないから 14

  15. どうすれば? 15

  16. エラーを整理してみよう 16

  17. ユーザが回復できるエラー システムエラー 17

  18. ユーザが回復できるエラー システムエラー 拾うべきは? 18

  19. ユーザが回復できるエラー システムエラー 19

  20. 更にエラーを分ける 20

  21. クライアントエラー サーバーエラー 21

  22. クライアントの システムエラー クライアントの 回復可能エラー サーバーの システムエラー サーバーの 回復可能エラー サ ー

    バ ク ラ イ ア ン ト システムエラー 回復可能エラー 22
  23. クライアントの システムエラー クライアントの 回復可能エラー サーバーの システムエラー サーバーの 回復可能エラー サ ー

    バ ク ラ イ ア ン ト システムエラー 回復可能エラー 23
  24. クライアントの システムエラー クライアントの 回復可能エラー サーバーの システムエラー サーバーの 回復可能エラー サ ー

    バ ク ラ イ ア ン ト システムエラー 回復可能エラー 24
  25. nrs@@example.com メールアドレス 25

  26. nrs@@example.com メールアドレス メールアドレスの形式 ではありません 26

  27. ******** パスワード ******** パスワードの確認 27

  28. ******** パスワード ******** パスワードの確認 パスワードが 一致しません 28

  29. 29

  30. 画面ごとに異なるハンドリングのため それぞれの画面でハンドリング 30

  31. 画面ごとに異なるハンドリングのため それぞれの画面でハンドリング ディレクティブなどで共通化 31

  32. クライアントの システムエラー クライアントの 回復可能エラー サーバーの システムエラー サーバーの 回復可能エラー サ ー

    バ ク ラ イ ア ン ト システムエラー 回復可能エラー 32
  33. naruse ユーザ名 naruse@example.com メールアドレス ******** パスワード ******** パスワードの確認 33

  34. naruse ユーザ名 naruse@example.com メールアドレス ******** パスワード ******** パスワードの確認 すでに登録されています すでに登録されています

    34
  35. 35

  36. Mail: naruse@example.com Name: naruse Password: ******** 36

  37. Mail: naruse@example.com Name: naruse Password: ******** duplicated-email, duplicated-username 37

  38. Mail: naruse@example.com Name: naruse Password: ******** duplicated-email, duplicated-username duplicated-email だからメール欄に

    エラー表示して 38
  39. Mail: naruse@example.com Name: naruse Password: ******** duplicated-email, duplicated-username duplicated-email だからメール欄に

    エラー表示して duplicated-username だからユーザ名欄に エラー表示して 39
  40. duplicated-email だからメール欄に エラー表示して duplicated-username だからユーザ名欄に エラー表示して 40

  41. duplicated-email だからメール欄に エラー表示して duplicated-username だからユーザ名欄に エラー表示して 画面ごとに ハンドリング? 41

  42. 42

  43. 43

  44. 登録済みのメールアドレスです このユーザ名はすでに取得されています naruse ユーザ名 naruse@example.com メールアドレス ******** パスワード ******** パスワードの確認

    44
  45. 登録済みのメールアドレスです このユーザ名はすでに取得されています naruse ユーザ名 naruse@example.com メールアドレス ******** パスワード ******** パスワードの確認

    これなら? 45
  46. どう実現する? 46

  47. 通信ライブラリを ラップする 47

  48. 48 export default class WraxiosCore { ... public get<T>(url: string,

    params?: IOptionParameter<T>): Promise<WraxiosResponse<T>> { return this.request('get', url, null, params); } public post<T>(url: string, data?: any, params?: IOptionParameter<T>): Promise<WraxiosResponse<T>> { return this.request('post', url, data, params); } }
  49. 49 export default class WraxiosCore { ... public get<T>(url: string,

    params?: IOptionParameter<T>): Promise<WraxiosResponse<T>> { return this.request('get', url, null, params); } public post<T>(url: string, data?: any, params?: IOptionParameter<T>): Promise<WraxiosResponse<T>> { return this.request('post', url, data, params); } }
  50. 50 export default class WraxiosCore { ... public get<T>(url: string,

    params?: IOptionParameter<T>): Promise<WraxiosResponse<T>> { return this.request('get', url, null, params); } public post<T>(url: string, data?: any, params?: IOptionParameter<T>): Promise<WraxiosResponse<T>> { return this.request('post', url, data, params); } } request メソッドへ
  51. export default class WraxiosCore { ... public request<T>(method: Method, url:

    string, data?: any, aParams?: IOptionParameter<T>): Pr const params = aParams === null || aParams === void 0 ? {} : aParams!; const requestConfig: AxiosRequestConfig = !params || !params.config ? {} : params.config; requestConfig.method = method; requestConfig.url = url; requestConfig.data = data; const calledQueueNum = this.queueNum; return new Promise<WraxiosResponse<T>>((resolve, reject) => { axios.request<T>(requestConfig) .then(res => { if (calledQueueNum !== this.queueNum) { return; } const response = new WraxiosResponse(res.data); resolve(response); }) .catch((e: AxiosError) => { if (calledQueueNum !== this.queueNum) { 51 request 本体
  52. export default class WraxiosCore { ... public request<T>(method: Method, url:

    string, data?: any, aParams?: IOptionParameter<T>): Pr const params = aParams === null || aParams === void 0 ? {} : aParams!; const requestConfig: AxiosRequestConfig = !params || !params.config ? {} : params.config; requestConfig.method = method; requestConfig.url = url; requestConfig.data = data; const calledQueueNum = this.queueNum; return new Promise<WraxiosResponse<T>>((resolve, reject) => { axios.request<T>(requestConfig) .then(res => { if (calledQueueNum !== this.queueNum) { return; } const response = new WraxiosResponse(res.data); resolve(response); }) .catch((e: AxiosError) => { if (calledQueueNum !== this.queueNum) { 52 axios (通信ライブラリ)をラップ
  53. export default class WraxiosCore { ... public request<T>(method: Method, url:

    string, data?: any, aParams?: IOptionParameter<T>): Pr const params = aParams === null || aParams === void 0 ? {} : aParams!; const requestConfig: AxiosRequestConfig = !params || !params.config ? {} : params.config; requestConfig.method = method; requestConfig.url = url; requestConfig.data = data; const calledQueueNum = this.queueNum; return new Promise<WraxiosResponse<T>>((resolve, reject) => { axios.request<T>(requestConfig) .then(res => { if (calledQueueNum !== this.queueNum) { return; } const response = new WraxiosResponse(res.data); resolve(response); }) .catch((e: AxiosError) => { if (calledQueueNum !== this.queueNum) { 53 axios の取り回しを 確認してみよう
  54. axios.request<T>(requestConfig) .then(res => { ... }) .catch((e: AxiosError) => {

    ... if (response.status === 400) { const clientErrorResponse = this.makeResponse(response.data); const errorHandler = this.selectErrorHandler(params.onError); errorHandler(clientErrorResponse); if (!!params.onErrored) { params.onErrored(); } } else { const unExpectedErrorHandler = this.selectUnexpectedErrorHandler(params.onUnexpectedE unExpectedErrorHandler(resolve, reject, e); } }); 54
  55. axios.request<T>(requestConfig) .then(res => { ... }) .catch((e: AxiosError) => {

    ... if (response.status === 400) { const clientErrorResponse = this.makeResponse(response.data); const errorHandler = this.selectErrorHandler(params.onError); errorHandler(clientErrorResponse); if (!!params.onErrored) { params.onErrored(); } } else { const unExpectedErrorHandler = this.selectUnexpectedErrorHandler(params.onUnexpectedE unExpectedErrorHandler(resolve, reject, e); } }); 55 エラーハンドラー選定
  56. 56 export default class WraxiosCore { ... private selectErrorHandler<T>(handler?: IErrorHandler):

    IErrorHandler { if (handler === null || handler === void 0) { return notifyErrorToMessage; } return handler; } } const notifyErrorToMessage: IErrorHandler = (response: IErrorResponse) => { response.errors .map((x) => x.message) .forEach((message) => ServiceLocator.instance.messageService .addMessage(MessageType.Error, message)); };
  57. 57 export default class WraxiosCore { ... private selectErrorHandler<T>(handler?: IErrorHandler):

    IErrorHandler { if (handler === null || handler === void 0) { return notifyErrorToMessage; } return handler; } } const notifyErrorToMessage: IErrorHandler = (response: IErrorResponse) => { response.errors .map((x) => x.message) .forEach((message) => ServiceLocator.instance.messageService .addMessage(MessageType.Error, message)); }; デフォルト実装
  58. 58 export default class WraxiosCore { ... private selectErrorHandler<T>(handler?: IErrorHandler):

    IErrorHandler { if (handler === null || handler === void 0) { return notifyErrorToMessage; } return handler; } } const notifyErrorToMessage: IErrorHandler = (response: IErrorResponse) => { response.errors .map((x) => x.message) .forEach((message) => ServiceLocator.instance.messageService .addMessage(MessageType.Error, message)); }; デフォルト=メッセージシステムに通知
  59. 59 export default class WraxiosCore { ... private selectErrorHandler<T>(handler?: IErrorHandler):

    IErrorHandler { if (handler === null || handler === void 0) { return notifyErrorToMessage; } return handler; } } const notifyErrorToMessage: IErrorHandler = (response: IErrorResponse) => { response.errors .map((x) => x.message) .forEach((message) => ServiceLocator.instance.messageService .addMessage(MessageType.Error, message)); }; デフォルト=メッセージシステムに通知 具体的なメッセージは?
  60. axios.request<T>(requestConfig) .then(res => { ... }) .catch((e: AxiosError) => {

    ... if (response.status === 400) { const clientErrorResponse = this.makeResponse(response.data); const errorHandler = this.selectErrorHandler(params.onError); errorHandler(clientErrorResponse); if (!!params.onErrored) { params.onErrored(); } } else { const unExpectedErrorHandler = this.selectUnexpectedErrorHandler(params.onUnexpectedE unExpectedErrorHandler(resolve, reject, e); } }); 60 さきほどのコード
  61. axios.request<T>(requestConfig) .then(res => { ... }) .catch((e: AxiosError) => {

    ... if (response.status === 400) { const clientErrorResponse = this.makeResponse(response.data); const errorHandler = this.selectErrorHandler(params.onError); errorHandler(clientErrorResponse); if (!!params.onErrored) { params.onErrored(); } } else { const unExpectedErrorHandler = this.selectUnexpectedErrorHandler(params.onUnexpectedE unExpectedErrorHandler(resolve, reject, e); } }); 61 レスポンスからエラー用メッセージの生成
  62. 62 export default class WraxiosCore { ... private makeResponse<T>(data: any):

    IErrorResponse { const response = data as IErrorResponse; if (!!response && !!response.errors) { for (const error of response.errors) { const errorMessageKey = 'error.api.' + error.code; const errorMessage = i18n.t(errorMessageKey).toString(); if (!errorMessage || errorMessage === errorMessageKey) { error.message = i18n.t( 'common.wraxios.unregistered-error-message’, { errorCode : "code:¥"" + error.code + "¥""} ).toString(); } else { error.message = errorMessage; } } } return data; } } エラーメッセージ生成処理
  63. 63 export default class WraxiosCore { ... private makeResponse<T>(data: any):

    IErrorResponse { const response = data as IErrorResponse; if (!!response && !!response.errors) { for (const error of response.errors) { const errorMessageKey = 'error.api.' + error.code; const errorMessage = i18n.t(errorMessageKey).toString(); if (!errorMessage || errorMessage === errorMessageKey) { error.message = i18n.t( 'common.wraxios.unregistered-error-message’, { errorCode : "code:¥"" + error.code + "¥""} ).toString(); } else { error.message = errorMessage; } } } return data; } } エラーコードからキーを生成
  64. 64 export default class WraxiosCore { ... private makeResponse<T>(data: any):

    IErrorResponse { const response = data as IErrorResponse; if (!!response && !!response.errors) { for (const error of response.errors) { const errorMessageKey = 'error.api.' + error.code; const errorMessage = i18n.t(errorMessageKey).toString(); if (!errorMessage || errorMessage === errorMessageKey) { error.message = i18n.t( 'common.wraxios.unregistered-error-message’, { errorCode : "code:¥"" + error.code + "¥""} ).toString(); } else { error.message = errorMessage; } } } return data; } } メッセージのデータファイルから読み取り
  65. 65 export default class WraxiosCore { ... private makeResponse<T>(data: any):

    IErrorResponse { const response = data as IErrorResponse; if (!!response && !!response.errors) { for (const error of response.errors) { const errorMessageKey = 'error.api.' + error.code; const errorMessage = i18n.t(errorMessageKey).toString(); if (!errorMessage || errorMessage === errorMessageKey) { error.message = i18n.t( 'common.wraxios.unregistered-error-message’, { errorCode : "code:¥"" + error.code + "¥""} ).toString(); } else { error.message = errorMessage; } } } return data; } } メッセージのデータファイルから読み取り { "ja" : { "common" : { "routing" : { "no-authority" : "権限が足りません" }, "wraxios" : { "unregistered-error-message" : "予期せぬエラー({errorCode})", "role-changed" : "権限が不足しています" } }, "error" : { "api" : { "/user" : { "POST" : { "too-long-name" : "ユーザー名が長すぎます" } } } } } }
  66. 66 export default class WraxiosCore { ... private makeResponse<T>(data: any):

    IErrorResponse { const response = data as IErrorResponse; if (!!response && !!response.errors) { for (const error of response.errors) { const errorMessageKey = 'error.api.' + error.code; const errorMessage = i18n.t(errorMessageKey).toString(); if (!errorMessage || errorMessage === errorMessageKey) { error.message = i18n.t( 'common.wraxios.unregistered-error-message’, { errorCode : "code:¥"" + error.code + "¥""} ).toString(); } else { error.message = errorMessage; } } } return data; } } エラーメッセージが見つからない時は デフォルトメッセージ
  67. これで基本的に 何もしなくてOK 67

  68. 細かく制御したい ときもある 68

  69. 入力画面の難易度が高い エラー発生個所がわかりづらい たとえば 69

  70. 70 export default class WraxiosCore { ... public get<T>(url: string,

    params?: IOptionParameter<T>): Promise<WraxiosResponse<T>> { return this.request('get', url, null, params); } public post<T>(url: string, data?: any, params?: IOptionParameter<T>): Promise<WraxiosResponse<T>> { return this.request('post', url, data, params); } }
  71. 71 export default class WraxiosCore { ... public get<T>(url: string,

    params?: IOptionParameter<T>): Promise<WraxiosResponse<T>> { return this.request('get', url, null, params); } public post<T>(url: string, data?: any, params?: IOptionParameter<T>): Promise<WraxiosResponse<T>> { return this.request('post', url, data, params); } }
  72. 72 export interface IOptionParameter<T> { onError?: IErrorHandler; onErrored?: () =>

    void; onUnexpectedError?: UnexpectedErrorHandler<T>; config?: AxiosRequestConfig; } export default class WraxiosCore { ... public get<T>(url: string, params?: IOptionParameter<T>): Promise<WraxiosResponse<T>> { return this.request('get', url, null, params); } public post<T>(url: string, data?: any, params?: IOptionParameter<T>): Promise<WraxiosResponse<T>> { return this.request('post', url, data, params); } }
  73. 73 export interface IOptionParameter<T> { onError?: IErrorHandler; onErrored?: () =>

    void; onUnexpectedError?: UnexpectedErrorHandler<T>; config?: AxiosRequestConfig; } export default class WraxiosCore { ... public get<T>(url: string, params?: IOptionParameter<T>): Promise<WraxiosResponse<T>> { return this.request('get', url, null, params); } public post<T>(url: string, data?: any, params?: IOptionParameter<T>): Promise<WraxiosResponse<T>> { return this.request('post', url, data, params); } } エラーハンドラー登録用
  74. 74 export default class UserAddConfirmPage extends Vue { ... public

    onSubmitButtonClick() { wraxios.post<IUserCreateResponse>( 'user', this.model, { onError: (res) => { pushNextWithError(res, this.backLocation()); }}) .then(res=> { const data = res.data; this.$router.push({name: "user-detail", params: { id: data.createdUuid }}) }); } private backLocation(): RawLocation { return {name: 'user-add-input', params: <any>this.model}; } }
  75. 75 export default class UserAddConfirmPage extends Vue { ... public

    onSubmitButtonClick() { wraxios.post<IUserCreateResponse>( 'user', this.model, { onError: (res) => { pushNextWithError(res, this.backLocation()); }}) .then(res=> { const data = res.data; this.$router.push({name: "user-detail", params: { id: data.createdUuid }}) }); } private backLocation(): RawLocation { return {name: 'user-add-input', params: <any>this.model}; } } ページ遷移してエラー通知する
  76. 76

  77. 77

  78. エラーハンドリングで重要なのは 場合分け 78

  79. できごと ハンドリング 成功 失敗 例外 セッション切れ 権限変更 (パーミッション変更) 79

  80. できごと ハンドリング 成功 する 失敗 例外 セッション切れ 権限変更 (パーミッション変更) 80

  81. できごと ハンドリング 成功 する 失敗 する or しない 例外 セッション切れ

    権限変更 (パーミッション変更) 81
  82. できごと ハンドリング 成功 する 失敗 する or しない 例外 する

    or しない セッション切れ 権限変更 (パーミッション変更) 82
  83. できごと ハンドリング 成功 する 失敗 する or しない 例外 する

    or しない セッション切れ ログイン画面へ 権限変更 (パーミッション変更) 83
  84. できごと ハンドリング 成功 する 失敗 する or しない 例外 する

    or しない セッション切れ ログイン画面へ 権限変更 (パーミッション変更) しない(権限エラー通知) 84
  85. できごと ハンドリング 成功 する 失敗 する or しない 例外 する

    or しない セッション切れ ログイン画面へ 権限変更 (パーミッション変更) しない(権限エラー通知) 85
  86. 86 できごと ハンドリング 成功 する 失敗 する or しない 例外

    する or しない セッション切れ ログイン画面へ 権限変更 (パーミッション変更) しない(権限エラー通知) export interface IOptionParameter<T> { onError?: IErrorHandler; onErrored?: () => void; onUnexpectedError?: UnexpectedErrorHandler<T>; config?: AxiosRequestConfig; }
  87. 87 できごと ハンドリング 成功 する 失敗 する or しない 例外

    する or しない セッション切れ ログイン画面へ 権限変更 (パーミッション変更) しない(権限エラー通知) export interface IOptionParameter<T> { onError?: IErrorHandler; onErrored?: () => void; onUnexpectedError?: UnexpectedErrorHandler<T>; config?: AxiosRequestConfig; }
  88. 基本は何もしなくてよくて 必要なときに差し込める というのが大事 88

  89. Agenda 1. つつましいエラーハンドリング 2. 認証認可の戦略 3. 全体的なコンポーネント構造 89

  90. こんなシステムは嫌だ 90

  91. 認証してないのにログインできちゃった 91

  92. 権限がないはずなのに操作できちゃった 92

  93. 認証認可を担保するには? 93

  94. 強力な仕組みが必要 94

  95. 人力を排除せよ 95

  96. 認証認可の戦略 1. 認証と認証切れ 2. ルートの認可 3. リソースの認可 96

  97. 認証認可の戦略 1. 認証と認証切れ 2. ルートの認可 3. リソースの認可 97

  98. 98 認証が必要でないページ

  99. 99 import LoginRootPage from "@/views/login/LoginRootPage.vue"; export default { path: '/login',

    name: 'login', component: LoginRootPage, meta: { noAuth: true } };
  100. 100 import LoginRootPage from "@/views/login/LoginRootPage.vue"; export default { path: '/login',

    name: 'login', component: LoginRootPage, meta: { noAuth: true } }; ページに認証不要を設定 (未設定=認証必要)
  101. 101 認証確認をしているのは

  102. 102 export default async function globalBeforeEach(to: Route, from: Route, next:

    Function) { if (await transferLoginWhenNoAuth(to, from, next)) { return; } if (await transferWhenNoRole(to, from, next)) { return; } next(); } ルート変更時のグローバルフック
  103. 103 export default async function globalBeforeEach(to: Route, from: Route, next:

    Function) { if (await transferLoginWhenNoAuth(to, from, next)) { return; } if (await transferWhenNoRole(to, from, next)) { return; } next(); }
  104. 104 async function transferLoginWhenNoAuth(to: Route, from: Route, next: Function) :

    Promise<boolean> { if (isAuthorised(to)) { if (!accountStoreFacade.hasInfo()) { await accountStoreFacade.loadAccountInfo(); if (!accountStoreFacade.hasInfo()) { if (!ServiceLocator.instance.routeService.endure) { ServiceLocator.instance.routeService.releaseOverwrite(); ServiceLocator.instance.routeService.saveBackRoute(to); } next({name: 'login'}); return true; } } } return false; }
  105. 105 async function transferLoginWhenNoAuth(to: Route, from: Route, next: Function) :

    Promise<boolean> { if (isAuthorised(to)) { if (!accountStoreFacade.hasInfo()) { await accountStoreFacade.loadAccountInfo(); if (!accountStoreFacade.hasInfo()) { if (!ServiceLocator.instance.routeService.endure) { ServiceLocator.instance.routeService.releaseOverwrite(); ServiceLocator.instance.routeService.saveBackRoute(to); } next({name: 'login'}); return true; } } } return false; } 遷移先は認証が必要か
  106. 106 async function transferLoginWhenNoAuth(to: Route, from: Route, next: Function) :

    Promise<boolean> { if (isAuthorised(to)) { if (!accountStoreFacade.hasInfo()) { await accountStoreFacade.loadAccountInfo(); if (!accountStoreFacade.hasInfo()) { if (!ServiceLocator.instance.routeService.endure) { ServiceLocator.instance.routeService.releaseOverwrite(); ServiceLocator.instance.routeService.saveBackRoute(to); } next({name: 'login'}); return true; } } } return false; } 認証の確認
  107. 107 async function transferLoginWhenNoAuth(to: Route, from: Route, next: Function) :

    Promise<boolean> { if (isAuthorised(to)) { if (!accountStoreFacade.hasInfo()) { await accountStoreFacade.loadAccountInfo(); if (!accountStoreFacade.hasInfo()) { if (!ServiceLocator.instance.routeService.endure) { ServiceLocator.instance.routeService.releaseOverwrite(); ServiceLocator.instance.routeService.saveBackRoute(to); } next({name: 'login'}); return true; } } } return false; } 認証していなかったらセッション確認
  108. 108 async function transferLoginWhenNoAuth(to: Route, from: Route, next: Function) :

    Promise<boolean> { if (isAuthorised(to)) { if (!accountStoreFacade.hasInfo()) { await accountStoreFacade.loadAccountInfo(); if (!accountStoreFacade.hasInfo()) { if (!ServiceLocator.instance.routeService.endure) { ServiceLocator.instance.routeService.releaseOverwrite(); ServiceLocator.instance.routeService.saveBackRoute(to); } next({name: 'login'}); return true; } } } return false; } 未認証ならログイン画面へ
  109. 109

  110. 110 未認証

  111. 111 未認証 認証

  112. 112 async function transferLoginWhenNoAuth(to: Route, from: Route, next: Function) :

    Promise<boolean> { if (isAuthorised(to)) { if (!accountStoreFacade.hasInfo()) { await accountStoreFacade.loadAccountInfo(); if (!accountStoreFacade.hasInfo()) { if (!ServiceLocator.instance.routeService.endure) { ServiceLocator.instance.routeService.releaseOverwrite(); ServiceLocator.instance.routeService.saveBackRoute(to); } next({name: 'login'}); return true; } } } return false; }
  113. 113 async function transferLoginWhenNoAuth(to: Route, from: Route, next: Function) :

    Promise<boolean> { if (isAuthorised(to)) { if (!accountStoreFacade.hasInfo()) { await accountStoreFacade.loadAccountInfo(); if (!accountStoreFacade.hasInfo()) { if (!ServiceLocator.instance.routeService.endure) { ServiceLocator.instance.routeService.releaseOverwrite(); ServiceLocator.instance.routeService.saveBackRoute(to); } next({name: 'login'}); return true; } } } return false; } 遷移先を保存
  114. 114 export default class LoginRootPage extends Vue { @Action("account/loadAccountInfo") public

    loadAccountInfo!: () => Promise<void>; public async onLoggedIn() { await this.loadAccountInfo(); const routeService = ServiceLocator.instance.routeService; if (routeService.hasBackRoute) { const nextRoute = routeService.pathToBack(); routeService.clear(); this.$router.push(nextRoute); } else { this.$router.push({name: 'top'}); } } } ログイン画面
  115. 115 export default class LoginRootPage extends Vue { @Action("account/loadAccountInfo") public

    loadAccountInfo!: () => Promise<void>; public async onLoggedIn() { await this.loadAccountInfo(); const routeService = ServiceLocator.instance.routeService; if (routeService.hasBackRoute) { const nextRoute = routeService.pathToBack(); routeService.clear(); this.$router.push(nextRoute); } else { this.$router.push({name: 'top'}); } } } ログイン後、保存された遷移先へ
  116. 認証認可の戦略 1. 認証と認証切れ 2. ルートの認可 3. リソースの認可 116

  117. 基本的にすべてのルートは 最高権限を必要とする 117

  118. 118 export default { path: '/user', component: UserRootPage, children: [

    { path: '', name: 'user-index', component: UserIndexPage, meta: {role: AccountRole.Advanced}, }, { path: ':id', name: 'user-detail', component: UserDetailPage, meta: {role: AccountRole.Advanced}, }, { path: 'add/input', name: 'user-add-input', component: UserAddInputPage, }, ... ] } User 関連
  119. 119 export default { path: '/user', component: UserRootPage, children: [

    { path: '', name: 'user-index', component: UserIndexPage, meta: {role: AccountRole.Advanced}, }, { path: ':id', name: 'user-detail', component: UserDetailPage, meta: {role: AccountRole.Advanced}, }, { path: 'add/input', name: 'user-add-input', component: UserAddInputPage, }, ... ] } 最高権限が必要
  120. 120 export default { path: '/user', component: UserRootPage, children: [

    { path: '', name: 'user-index', component: UserIndexPage, meta: {role: AccountRole.Advanced}, }, { path: ':id', name: 'user-detail', component: UserDetailPage, meta: {role: AccountRole.Advanced}, }, { path: 'add/input', name: 'user-add-input', component: UserAddInputPage, }, ... ] } Advanced 権限
  121. 121 export default { path: '/', component: TopRootPage, meta: {role:

    'any'}, children: [ { path: '', name: 'top', component: TopIndexPage } ] }; Top ページ
  122. 122 export default { path: '/', component: TopRootPage, meta: {role:

    'any'}, children: [ { path: '', name: 'top', component: TopIndexPage } ] }; 権限不要
  123. 実際の処理 123

  124. 124 export default async function globalBeforeEach(to: Route, from: Route, next:

    Function) { if (await transferLoginWhenNoAuth(to, from, next)) { return; } if (await transferWhenNoRole(to, from, next)) { return; } next(); } ルート変更時のグローバルフック
  125. 125 export default async function globalBeforeEach(to: Route, from: Route, next:

    Function) { if (await transferLoginWhenNoAuth(to, from, next)) { return; } if (await transferWhenNoRole(to, from, next)) { return; } next(); } 認証後に認可の確認がある
  126. 126 async function transferWhenNoRole(to: Route, from:Route, next: Function): Promise<boolean> {

    if (!isAuthorised(to)) { return false; } if (isAnonymous(to)) { return false; } if (isAllowed(to)) { return false; } const showMessage = from.name === 'top' ? (message: string) => ServiceLocator.instance.messageService.addMessage(MessageType.Erro : (message: string) => ServiceLocator.instance.messageService.reserve(MessageType.Error, const mesasge = i18n.t("common.routing.no-authority").toString(); showMessage(mesasge); next({name: "top"}); return true; }
  127. 127 async function transferWhenNoRole(to: Route, from:Route, next: Function): Promise<boolean> {

    if (!isAuthorised(to)) { return false; } if (isAnonymous(to)) { return false; } if (isAllowed(to)) { return false; } const showMessage = from.name === 'top' ? (message: string) => ServiceLocator.instance.messageService.addMessage(MessageType.Erro : (message: string) => ServiceLocator.instance.messageService.reserve(MessageType.Error, const mesasge = i18n.t("common.routing.no-authority").toString(); showMessage(mesasge); next({name: "top"}); return true; }
  128. 128 async function transferWhenNoRole(to: Route, from:Route, next: Function): Promise<boolean> {

    if (!isAuthorised(to)) { return false; } if (isAnonymous(to)) { return false; } if (isAllowed(to)) { return false; } const showMessage = from.name === 'top' ? (message: string) => ServiceLocator.instance.messageService.addMessage(MessageType.Erro : (message: string) => ServiceLocator.instance.messageService.reserve(MessageType.Error, const mesasge = i18n.t("common.routing.no-authority").toString(); showMessage(mesasge); next({name: "top"}); return true; } function isAuthorised(to: Route): boolean { return to.matched.every(record => !record.meta.noAuth); } そもそも認証が要求されるか確認
  129. 129 async function transferWhenNoRole(to: Route, from:Route, next: Function): Promise<boolean> {

    if (!isAuthorised(to)) { return false; } if (isAnonymous(to)) { return false; } if (isAllowed(to)) { return false; } const showMessage = from.name === 'top' ? (message: string) => ServiceLocator.instance.messageService.addMessage(MessageType.Erro : (message: string) => ServiceLocator.instance.messageService.reserve(MessageType.Error, const mesasge = i18n.t("common.routing.no-authority").toString(); showMessage(mesasge); next({name: "top"}); return true; }
  130. 130 async function transferWhenNoRole(to: Route, from:Route, next: Function): Promise<boolean> {

    if (!isAuthorised(to)) { return false; } if (isAnonymous(to)) { return false; } if (isAllowed(to)) { return false; } const showMessage = from.name === 'top' ? (message: string) => ServiceLocator.instance.messageService.addMessage(MessageType.Erro : (message: string) => ServiceLocator.instance.messageService.reserve(MessageType.Error, const mesasge = i18n.t("common.routing.no-authority").toString(); showMessage(mesasge); next({name: "top"}); return true; } function isAnonymous(to: Route): boolean { return to.matched.some((record => !!record.meta && record.meta.role === 'any')); } 認可が不要か確認
  131. 131 async function transferWhenNoRole(to: Route, from:Route, next: Function): Promise<boolean> {

    if (!isAuthorised(to)) { return false; } if (isAnonymous(to)) { return false; } if (isAllowed(to)) { return false; } const showMessage = from.name === 'top' ? (message: string) => ServiceLocator.instance.messageService.addMessage(MessageType.Erro : (message: string) => ServiceLocator.instance.messageService.reserve(MessageType.Error, const mesasge = i18n.t("common.routing.no-authority").toString(); showMessage(mesasge); next({name: "top"}); return true; }
  132. 132 async function transferWhenNoRole(to: Route, from:Route, next: Function): Promise<boolean> {

    if (!isAuthorised(to)) { return false; } if (isAnonymous(to)) { return false; } if (isAllowed(to)) { return false; } const showMessage = from.name === 'top' ? (message: string) => ServiceLocator.instance.messageService.addMessage(MessageType.Erro : (message: string) => ServiceLocator.instance.messageService.reserve(MessageType.Error, const mesasge = i18n.t("common.routing.no-authority").toString(); showMessage(mesasge); next({name: "top"}); return true; } function isAllowed(to: Route): boolean { return to.matched.some(record => { if (!record || !record.meta) { return false; } const metaRole = record.meta.role; if (metaRole instanceof Array) { const allowedRoles = <AccountRole[]>metaRole; return accountStoreFacade.isAllowedAny(allowedRoles); } else { const allowedRole = !!metaRole ? <AccountRole>metaRole : AccountRole.Administrator; return accountStoreFacade.isAllowed(allowedRole); } }) } 自身の権限と照らし合わせる
  133. 133 async function transferWhenNoRole(to: Route, from:Route, next: Function): Promise<boolean> {

    if (!isAuthorised(to)) { return false; } if (isAnonymous(to)) { return false; } if (isAllowed(to)) { return false; } const showMessage = from.name === 'top' ? (message: string) => ServiceLocator.instance.messageService.addMessage(MessageType.Erro : (message: string) => ServiceLocator.instance.messageService.reserve(MessageType.Error, const mesasge = i18n.t("common.routing.no-authority").toString(); showMessage(mesasge); next({name: "top"}); return true; } 権限がないのでTOPへ遷移しエラー表示
  134. 認証認可の戦略 1. 認証と認証切れ 2. ルートの認可 3. リソースの認可 134

  135. まずサーバー側で 403 を発行する 135

  136. 136 router.get('', authorize("advanced"), (req, res) => { const json =

    debugDataLoader("user", "get"); res.json(json); }); router.post('', authorize(), (req, res) => { const json = debugDataLoader("user", "post"); res.json(json); }); router.get('/:id', authorize("advanced"), (req, res) => { const json = debugDataLoader("user", "get-detail"); res.json(json); });
  137. 137 router.get('', authorize("advanced"), (req, res) => { const json =

    debugDataLoader("user", "get"); res.json(json); }); router.post('', authorize(), (req, res) => { const json = debugDataLoader("user", "post"); res.json(json); }); router.get('/:id', authorize("advanced"), (req, res) => { const json = debugDataLoader("user", "get-detail"); res.json(json); });
  138. 403 のハンドリング 138

  139. 139 const notifyMessageWhenUnexpectedErrorHandler: IUnexpectedErrorHandler = <T>( resolve: (value?: WraxiosResponse<T> |

    PromiseLike<WraxiosResponse<T>>) => void, reject: (reason?: any) => void, error: AxiosError<T> ) => { ... const statusCode = response.status; switch (statusCode) { case 401: router.push({name: 'login'}); break; case 403: const message = i18n.t('common.wraxios.role-changed').toString(); ServiceLocator.instance.messageService.reserve(MessageType.Info, message); router.push({name: 'top'}); break; default: router.push({name: 'error-server'}); break; } };
  140. 140 const notifyMessageWhenUnexpectedErrorHandler: IUnexpectedErrorHandler = <T>( resolve: (value?: WraxiosResponse<T> |

    PromiseLike<WraxiosResponse<T>>) => void, reject: (reason?: any) => void, error: AxiosError<T> ) => { ... const statusCode = response.status; switch (statusCode) { case 401: router.push({name: 'login'}); break; case 403: const message = i18n.t('common.wraxios.role-changed').toString(); ServiceLocator.instance.messageService.reserve(MessageType.Info, message); router.push({name: 'top'}); break; default: router.push({name: 'error-server'}); break; } }; 権限エラーメッセージを予約してTOPへ遷移
  141. Agenda 1. つつましいエラーハンドリング 2. 認証認可の戦略 3. 全体的なコンポーネント構造 141

  142. シンプルな オブジェクト同士の構造は? 142

  143. ObjectA ObjectB 143

  144. ObjectA ObjectB 144

  145. ObjectA ObjectB Method Call 145

  146. ObjectA ObjectB Method Call Method Call 146

  147. ObjectA ObjectB Method Call Method Call 147

  148. class ObjectA { private objectB = new ObjectB(this); public method()

    { this.objectB.methodForA(); } public methodForB() { ... } } class ObjectB { public constructor( private readonly objectA: ObjectA) { } public method() { this.objectA.methodForB(); } public methodForA() { ... this.objectA.methodForB(); } 148
  149. class ObjectA { private objectB = new ObjectB(this); public method()

    { this.objectB.methodForA(); } public methodForB() { ... } } class ObjectB { public constructor( private readonly objectA: ObjectA) { } public method() { this.objectA.methodForB(); } public methodForA() { ... this.objectA.methodForB(); } 149
  150. class ObjectA { private objectB = new ObjectB(this); public method()

    { this.objectB.methodForA(); } public methodForB() { ... } } class ObjectB { public constructor( private readonly objectA: ObjectA) { } public method() { this.objectA.methodForB(); } public methodForA() { ... this.objectA.methodForB(); } 150
  151. class ObjectA { private objectB = new ObjectB(this); public method()

    { this.objectB.methodForA(); } public methodForB() { ... } } class ObjectB { public constructor( private readonly objectA: ObjectA) { } public method() { this.objectA.methodForB(); } public methodForA() { ... this.objectA.methodForB(); } 151 相互参照
  152. class ObjectA { private objectB = new ObjectB(this); public method()

    { this.objectB.methodForA(); } public methodForB() { ... } } class ObjectB { public constructor( private readonly objectA: ObjectA) { } public method() { this.objectA.methodForB(); } public methodForA() { ... this.objectA.methodForB(); } 152 相互参照 コンストラクタに this を渡してると 怪しいかも
  153. class ObjectA { private objectB = new ObjectB(this); public method()

    { this.objectB.methodForA(); } public methodForB() { ... } } class ObjectB { public constructor( private readonly objectA: ObjectA) { } public method() { this.objectA.methodForB(); } public methodForA() { ... this.objectA.methodForB(); } 153
  154. class ObjectA { private objectB = new ObjectB(this); public method()

    { this.objectB.methodForA(); } public methodForB() { ... } } class ObjectB { public constructor( private readonly objectA: ObjectA) { } public method() { this.objectA.methodForB(); } public methodForA() { ... this.objectA.methodForB(); } 154
  155. class ObjectA { private objectB = new ObjectB(this); public method()

    { this.objectB.methodForA(); } public methodForB() { ... } } class ObjectB { public constructor( private readonly objectA: ObjectA) { } public method() { this.objectA.methodForB(); } public methodForA() { ... this.objectA.methodForB(); } 155
  156. class ObjectA { private objectB = new ObjectB(this); public method()

    { this.objectB.methodForA(); } public methodForB() { ... } } class ObjectB { public constructor( private readonly objectA: ObjectA) { } public method() { this.objectA.methodForB(); } public methodForA() { ... this.objectA.methodForB(); } 156
  157. class ObjectA { private objectB = new ObjectB(this); public method()

    { this.objectB.methodForA(); } public methodForB() { ... } } class ObjectB { public constructor( private readonly objectA: ObjectA) { } public method() { this.objectA.methodForB(); } public methodForA() { ... this.objectA.methodForB(); } 157
  158. ObjectA ObjectB 158

  159. class ObjectA { private objectB = new ObjectB(this); public method()

    { this.objectB.methodForA(); } public methodForB() { ... } public methodForB2() { ... this.objectB.methodForA2(); } public methodForB3() { this.objectB.methodForA3(); ... this.objectB.methodForA4(); } public methodForB4() { ... class ObjectB { public constructor( private readonly objectA: ObjectA) { } public method() { this.objectA.methodForB(); } public methodForA() { this.objectA.methodForB(); ... this.objectA.methodForB2(); } public methodForA2() { ... this.objectA.methodForB3(); } public methodForA3() { ... } public methodForA4() { 159
  160. class ObjectA { private objectB = new ObjectB(this); public method()

    { this.objectB.methodForA(); } public methodForB() { ... } public methodForB2() { ... this.objectB.methodForA2(); } public methodForB3() { this.objectB.methodForA3(); ... this.objectB.methodForA4(); } public methodForB4() { ... class ObjectB { public constructor( private readonly objectA: ObjectA) { } public method() { this.objectA.methodForB(); } public methodForA() { this.objectA.methodForB(); ... this.objectA.methodForB2(); } public methodForA2() { ... this.objectA.methodForB3(); } public methodForA3() { ... } public methodForA4() { 160
  161. class ObjectA { private objectB = new ObjectB(this); public method()

    { this.objectB.methodForA(); } public methodForB() { ... } public methodForB2() { ... this.objectB.methodForA2(); } public methodForB3() { this.objectB.methodForA3(); ... this.objectB.methodForA4(); } public methodForB4() { ... class ObjectB { public constructor( private readonly objectA: ObjectA) { } public method() { this.objectA.methodForB(); } public methodForA() { this.objectA.methodForB(); ... this.objectA.methodForB2(); } public methodForA2() { ... this.objectA.methodForB3(); } public methodForA3() { ... } public methodForA4() { 161
  162. class ObjectA { private objectB = new ObjectB(this); public method()

    { this.objectB.methodForA(); } public methodForB() { ... } public methodForB2() { ... this.objectB.methodForA2(); } public methodForB3() { this.objectB.methodForA3(); ... this.objectB.methodForA4(); } public methodForB4() { ... class ObjectB { public constructor( private readonly objectA: ObjectA) { } public method() { this.objectA.methodForB(); } public methodForA() { this.objectA.methodForB(); ... this.objectA.methodForB2(); } public methodForA2() { ... this.objectA.methodForB3(); } public methodForA3() { ... } public methodForA4() { 162
  163. class ObjectA { private objectB = new ObjectB(this); public method()

    { this.objectB.methodForA(); } public methodForB() { ... } public methodForB2() { ... this.objectB.methodForA2(); } public methodForB3() { this.objectB.methodForA3(); ... this.objectB.methodForA4(); } public methodForB4() { ... class ObjectB { public constructor( private readonly objectA: ObjectA) { } public method() { this.objectA.methodForB(); } public methodForA() { this.objectA.methodForB(); ... this.objectA.methodForB2(); } public methodForA2() { ... this.objectA.methodForB3(); } public methodForA3() { ... } public methodForA4() { 163
  164. class ObjectA { private objectB = new ObjectB(this); public method()

    { this.objectB.methodForA(); } public methodForB() { ... } public methodForB2() { ... this.objectB.methodForA2(); } public methodForB3() { this.objectB.methodForA3(); ... this.objectB.methodForA4(); } public methodForB4() { ... class ObjectB { public constructor( private readonly objectA: ObjectA) { } public method() { this.objectA.methodForB(); } public methodForA() { this.objectA.methodForB(); ... this.objectA.methodForB2(); } public methodForA2() { ... this.objectA.methodForB3(); } public methodForA3() { ... } public methodForA4() { 164
  165. class ObjectA { private objectB = new ObjectB(this); public method()

    { this.objectB.methodForA(); } public methodForB() { ... } public methodForB2() { ... this.objectB.methodForA2(); } public methodForB3() { this.objectB.methodForA3(); ... this.objectB.methodForA4(); } public methodForB4() { ... class ObjectB { public constructor( private readonly objectA: ObjectA) { } public method() { this.objectA.methodForB(); } public methodForA() { this.objectA.methodForB(); ... this.objectA.methodForB2(); } public methodForA2() { ... this.objectA.methodForB3(); } public methodForA3() { ... } public methodForA4() { 165
  166. class ObjectA { private objectB = new ObjectB(this); public method()

    { this.objectB.methodForA(); } public methodForB() { ... } public methodForB2() { ... this.objectB.methodForA2(); } public methodForB3() { this.objectB.methodForA3(); ... this.objectB.methodForA4(); } public methodForB4() { ... class ObjectB { public constructor( private readonly objectA: ObjectA) { } public method() { this.objectA.methodForB(); } public methodForA() { this.objectA.methodForB(); ... this.objectA.methodForB2(); } public methodForA2() { ... this.objectA.methodForB3(); } public methodForA3() { ... } public methodForA4() { 166
  167. class ObjectA { private objectB = new ObjectB(this); public method()

    { this.objectB.methodForA(); } public methodForB() { ... } public methodForB2() { ... this.objectB.methodForA2(); } public methodForB3() { this.objectB.methodForA3(); ... this.objectB.methodForA4(); } public methodForB4() { ... class ObjectB { public constructor( private readonly objectA: ObjectA) { } public method() { this.objectA.methodForB(); } public methodForA() { this.objectA.methodForB(); ... this.objectA.methodForB2(); } public methodForA2() { ... this.objectA.methodForB3(); } public methodForA3() { ... } public methodForA4() { 167
  168. class ObjectA { private objectB = new ObjectB(this); public method()

    { this.objectB.methodForA(); } public methodForB() { ... } public methodForB2() { ... this.objectB.methodForA2(); } public methodForB3() { this.objectB.methodForA3(); ... this.objectB.methodForA4(); } public methodForB4() { ... class ObjectB { public constructor( private readonly objectA: ObjectA) { } public method() { this.objectA.methodForB(); } public methodForA() { this.objectA.methodForB(); ... this.objectA.methodForB2(); } public methodForA2() { ... this.objectA.methodForB3(); } public methodForA3() { ... } public methodForA4() { 168
  169. class ObjectA { private objectB = new ObjectB(this); public method()

    { this.objectB.methodForA(); } public methodForB() { ... } public methodForB2() { ... this.objectB.methodForA2(); } public methodForB3() { this.objectB.methodForA3(); ... this.objectB.methodForA4(); } public methodForB4() { ... class ObjectB { public constructor( private readonly objectA: ObjectA) { } public method() { this.objectA.methodForB(); } public methodForA() { this.objectA.methodForB(); ... this.objectA.methodForB2(); } public methodForA2() { ... this.objectA.methodForB3(); } public methodForA3() { ... } public methodForA4() { 169
  170. class ObjectA { private objectB = new ObjectB(this); public method()

    { this.objectB.methodForA(); } public methodForB() { ... } public methodForB2() { ... this.objectB.methodForA2(); } public methodForB3() { this.objectB.methodForA3(); ... this.objectB.methodForA4(); } public methodForB4() { ... class ObjectB { public constructor( private readonly objectA: ObjectA) { } public method() { this.objectA.methodForB(); } public methodForA() { this.objectA.methodForB(); ... this.objectA.methodForB2(); } public methodForA2() { ... this.objectA.methodForB3(); } public methodForA3() { ... } public methodForA4() { 170
  171. class ObjectA { private objectB = new ObjectB(this); public method()

    { this.objectB.methodForA(); } public methodForB() { ... } public methodForB2() { ... this.objectB.methodForA2(); } public methodForB3() { this.objectB.methodForA3(); ... this.objectB.methodForA4(); } public methodForB4() { ... class ObjectB { public constructor( private readonly objectA: ObjectA) { } public method() { this.objectA.methodForB(); } public methodForA() { this.objectA.methodForB(); ... this.objectA.methodForB2(); } public methodForA2() { ... this.objectA.methodForB3(); } public methodForA3() { ... } public methodForA4() { 171
  172. class ObjectA { private objectB = new ObjectB(this); public method()

    { this.objectB.methodForA(); } public methodForB() { ... } public methodForB2() { ... this.objectB.methodForA2(); } public methodForB3() { this.objectB.methodForA3(); ... this.objectB.methodForA4(); } public methodForB4() { ... class ObjectB { public constructor( private readonly objectA: ObjectA) { } public method() { this.objectA.methodForB(); } public methodForA() { this.objectA.methodForB(); ... this.objectA.methodForB2(); } public methodForA2() { ... this.objectA.methodForB3(); } public methodForA3() { ... } public methodForA4() { 172
  173. class ObjectA { private objectB = new ObjectB(this); public method()

    { this.objectB.methodForA(); } public methodForB() { ... } public methodForB2() { ... this.objectB.methodForA2(); } public methodForB3() { this.objectB.methodForA3(); ... this.objectB.methodForA4(); } public methodForB4() { ... class ObjectB { public constructor( private readonly objectA: ObjectA) { } public method() { this.objectA.methodForB(); } public methodForA() { this.objectA.methodForB(); ... this.objectA.methodForB2(); } public methodForA2() { ... this.objectA.methodForB3(); } public methodForA3() { ... } public methodForA4() { 173
  174. class ObjectA { private objectB = new ObjectB(this); public method()

    { this.objectB.methodForA(); } public methodForB() { ... } public methodForB2() { ... this.objectB.methodForA2(); } public methodForB3() { this.objectB.methodForA3(); ... this.objectB.methodForA4(); } public methodForB4() { ... class ObjectB { public constructor( private readonly objectA: ObjectA) { } public method() { this.objectA.methodForB(); } public methodForA() { this.objectA.methodForB(); ... this.objectA.methodForB2(); } public methodForA2() { ... this.objectA.methodForB3(); } public methodForA3() { ... } public methodForA4() { 174
  175. class ObjectA { private objectB = new ObjectB(this); public method()

    { this.objectB.methodForA(); } public methodForB() { ... } public methodForB2() { ... this.objectB.methodForA2(); } public methodForB3() { this.objectB.methodForA3(); ... this.objectB.methodForA4(); } public methodForB4() { ... class ObjectB { public constructor( private readonly objectA: ObjectA) { } public method() { this.objectA.methodForB(); } public methodForA() { this.objectA.methodForB(); ... this.objectA.methodForB2(); } public methodForA2() { ... this.objectA.methodForB3(); } public methodForA3() { ... } public methodForA4() { 175
  176. class ObjectA { private objectB = new ObjectB(this); public method()

    { this.objectB.methodForA(); } public methodForB() { ... } public methodForB2() { ... this.objectB.methodForA2(); } public methodForB3() { this.objectB.methodForA3(); ... this.objectB.methodForA4(); } public methodForB4() { ... class ObjectB { public constructor( private readonly objectA: ObjectA) { } public method() { this.objectA.methodForB(); } public methodForA() { this.objectA.methodForB(); ... this.objectA.methodForB2(); } public methodForA2() { ... this.objectA.methodForB3(); } public methodForA3() { ... } public methodForA4() { 176
  177. class ObjectA { private objectB = new ObjectB(this); public method()

    { this.objectB.methodForA(); } public methodForB() { ... } public methodForB2() { ... this.objectB.methodForA2(); } public methodForB3() { this.objectB.methodForA3(); ... this.objectB.methodForA4(); } public methodForB4() { ... class ObjectB { public constructor( private readonly objectA: ObjectA) { } public method() { this.objectA.methodForB(); } public methodForA() { this.objectA.methodForB(); ... this.objectA.methodForB2(); } public methodForA2() { ... this.objectA.methodForB3(); } public methodForA3() { ... } public methodForA4() { 177
  178. class ObjectA { private objectB = new ObjectB(this); public method()

    { this.objectB.methodForA(); } public methodForB() { ... } public methodForB2() { ... this.objectB.methodForA2(); } public methodForB3() { this.objectB.methodForA3(); ... this.objectB.methodForA4(); } public methodForB4() { ... class ObjectB { public constructor( private readonly objectA: ObjectA) { } public method() { this.objectA.methodForB(); } public methodForA() { this.objectA.methodForB(); ... this.objectA.methodForB2(); } public methodForA2() { ... this.objectA.methodForB3(); } public methodForA3() { ... } public methodForA4() { 178
  179. class ObjectA { private objectB = new ObjectB(this); public method()

    { this.objectB.methodForA(); } public methodForB() { ... } public methodForB2() { ... this.objectB.methodForA2(); } public methodForB3() { this.objectB.methodForA3(); ... this.objectB.methodForA4(); } public methodForB4() { ... class ObjectB { public constructor( private readonly objectA: ObjectA) { } public method() { this.objectA.methodForB(); } public methodForA() { this.objectA.methodForB(); ... this.objectA.methodForB2(); } public methodForA2() { ... this.objectA.methodForB3(); } public methodForA3() { ... } public methodForA4() { 動作順序 把握できました? 179
  180. ObjectA ObjectB methodForA methodForB methodForB2 methodForA2 methodForB3 methodForA3 methodForA4 180

  181. ObjectA ObjectB methodForA methodForB methodForB2 methodForA2 methodForB3 methodForA3 methodForA4 把握できました?

    181
  182. どうするか? 182

  183. ObjectA ObjectB 183

  184. ObjectA ObjectB Parent ObjectA ObjectB 184

  185. class Parent { private readonly objectA = new ObjectA(); private

    readonly objectB = new ObjectB(); public method() { this.objectB.methodForA(); this.objectA.methodForB(); this.objectA.methodForB2(); this.objectB.methodForA2(); this.objectA.methodForB3(); this.objectB.methodForA3(); this.objectB.methodForA4(); } } 185
  186. class Parent { private readonly objectA = new ObjectA(); private

    readonly objectB = new ObjectB(); public method() { this.objectB.methodForA(); this.objectA.methodForB(); this.objectA.methodForB2(); this.objectB.methodForA2(); this.objectA.methodForB3(); this.objectB.methodForA3(); this.objectB.methodForA4(); } } class ObjectA { public methodForB() { ... } public methodForB2() { ... } public methodForB3() { ... } public methodForB4() { ... } } class ObjectB { public methodForA() { ... } public methodForA2() { ... } public methodForA3() { ... } public methodForA4() { ... } } 186
  187. class Parent { private readonly objectA = new ObjectA(); private

    readonly objectB = new ObjectB(); public method() { this.objectB.methodForA(); this.objectA.methodForB(); this.objectA.methodForB2(); this.objectB.methodForA2(); this.objectA.methodForB3(); this.objectB.methodForA3(); this.objectB.methodForA4(); } } class ObjectA { public methodForB() { ... } public methodForB2() { ... } public methodForB3() { ... } public methodForB4() { ... } } class ObjectB { public methodForA() { ... } public methodForA2() { ... } public methodForA3() { ... } public methodForA4() { ... } } オブジェクトは シンプルに 187
  188. class Parent { private readonly objectA = new ObjectA(); private

    readonly objectB = new ObjectB(); public method() { this.objectB.methodForA(); this.objectA.methodForB(); this.objectA.methodForB2(); this.objectB.methodForA2(); this.objectA.methodForB3(); this.objectB.methodForA3(); this.objectB.methodForA4(); } } 188
  189. class Parent { private readonly objectA = new ObjectA(); private

    readonly objectB = new ObjectB(); public method() { this.objectB.methodForA(); this.objectA.methodForB(); this.objectA.methodForB2(); this.objectB.methodForA2(); this.objectA.methodForB3(); this.objectB.methodForA3(); this.objectB.methodForA4(); } } コードを見れば 一目瞭然 189
  190. コンポーネントも 同じ 190

  191. TabButton TabButton TabButton TabButton 191

  192. TabButton TabButton TabButton TabButton Click 192

  193. TabButton TabButton TabButton TabButton Click Select 193

  194. TabButton TabButton TabButton TabButton Click Select 194

  195. TabButton TabButton TabButton TabButton Click unselect() Select 195

  196. TabButton TabButton TabButton TabButton Click unselect() Select 196

  197. TabButton TabButton TabButton TabButton Click unselect() Select 197

  198. TabButton TabButton TabButton TabButton TabButtonGroup 198

  199. TabButton TabButton TabButton TabButton TabButtonGroup Click 199

  200. TabButton TabButton TabButton TabButton TabButtonGroup Click Clicked 200

  201. TabButton TabButton TabButton TabButton TabButtonGroup Un Select Un Select Un

    Select Un Select 201
  202. TabButton TabButton TabButton TabButton TabButtonGroup Un Select Un Select Un

    Select Un Select 202
  203. TabButton TabButton TabButton TabButton TabButtonGroup Select 203

  204. TabButton TabButton TabButton TabButton TabButtonGroup Select 204

  205. TabButton TabButton TabButton TabButton TabContent Click 205

  206. TabButton TabButton TabButton TabButton TabContent Clicked TabButtonGroup 206

  207. TabButton TabButton TabButton TabButton TabContent TabButtonGroup Clicked Clicked (B) Tab

    207
  208. TabButton TabButton TabButton TabButton TabContent TabButtonGroup Tab Un Select 208

  209. TabButton TabButton TabButton TabButton TabContent TabButtonGroup Tab Un Select Un

    Select Un Select Un Select Un Select 209
  210. TabButton TabButton TabButton TabButton TabContent TabButtonGroup Tab Un Select Un

    Select Un Select Un Select Un Select 210
  211. TabButton TabButton TabButton TabButton TabContent TabButtonGroup Tab Select (B) 211

  212. TabButton TabButton TabButton TabButton TabContent TabButtonGroup Tab Select (B) Select

    212
  213. TabButton TabButton TabButton TabButton TabContent TabButtonGroup Tab Select (B) Select

    213
  214. TabButton TabButton TabButton TabButton TabContent TabButtonGroup Tab Change Content 214

  215. TabButton TabContent TabButtonGroup Tab 215

  216. TabButton TabContent TabButtonGroup Tab 216

  217. TabButton TabContent TabButtonGroup Tab メソッド 217

  218. TabButton TabContent TabButtonGroup Tab メソッド 218

  219. TabButton TabContent TabButtonGroup Tab メソッド イベント 219

  220. TabButton TabContent TabButtonGroup Tab メソッド イベント コンポーネントの基本構造 220

  221. TabButton TabButton TabButton TabButton TabContent TabButtonGroup Clicked Tab Clicked (B)

    221
  222. TabButton TabButton TabButton TabButton TabContent TabButtonGroup Clicked Tab イベント Clicked

    (B) 222
  223. TabButton TabButton TabButton TabButton TabContent TabButtonGroup Tab Select (B) Select

    223
  224. TabButton TabButton TabButton TabButton TabContent TabButtonGroup Tab Select (B) Select

    メソッド 224
  225. TabButton TabButton TabButton TabButton TabContent TabButtonGroup Tab Select (B) Select

    メソッド Vue などでは これをバインディングで 実現する 225
  226. たとえば 226

  227. 227 <template> <NoAuthorityTemplate> <Contents> <LoginForm :disabled="formDisabled" @submit="onSubmit" /> </Contents> </NoAuthorityTemplate>

    </template> <script lang="ts"> export default class LoginRootPage extends Vue { ... public formDisabled: boolean = false; public onSubmit(model: ILoginRequest) { ServiceLocator.instance.messageService.clearMessage(); this.formDisabled = true; wraxios.post('/login', model, { onErrored: () => { this.formDisabled = false; }}) .then(_ => { this.onLoggedIn(); }); } ...
  228. 228 <template> <NoAuthorityTemplate> <Contents> <LoginForm :disabled="formDisabled" @submit="onSubmit" /> </Contents> </NoAuthorityTemplate>

    </template> <script lang="ts"> export default class LoginRootPage extends Vue { ... public formDisabled: boolean = false; public onSubmit(model: ILoginRequest) { ServiceLocator.instance.messageService.clearMessage(); this.formDisabled = true; wraxios.post('/login', model, { onErrored: () => { this.formDisabled = false; }}) .then(_ => { this.onLoggedIn(); }); } ... 子コンポーネントのイベントをハンドリング
  229. 229 <template> <NoAuthorityTemplate> <Contents> <LoginForm :disabled="formDisabled" @submit="onSubmit" /> </Contents> </NoAuthorityTemplate>

    </template> <script lang="ts"> export default class LoginRootPage extends Vue { ... public formDisabled: boolean = false; public onSubmit(model: ILoginRequest) { ServiceLocator.instance.messageService.clearMessage(); this.formDisabled = true; wraxios.post('/login', model, { onErrored: () => { this.formDisabled = false; }}) .then(_ => { this.onLoggedIn(); }); } ...
  230. 230 <template> <NoAuthorityTemplate> <Contents> <LoginForm :disabled="formDisabled" @submit="onSubmit" /> </Contents> </NoAuthorityTemplate>

    </template> <script lang="ts"> export default class LoginRootPage extends Vue { ... public formDisabled: boolean = false; public onSubmit(model: ILoginRequest) { ServiceLocator.instance.messageService.clearMessage(); this.formDisabled = true; wraxios.post('/login', model, { onErrored: () => { this.formDisabled = false; }}) .then(_ => { this.onLoggedIn(); }); } ... 子コンポーネントの操作は あくまでそれを保持するコンポーネント
  231. ページ遷移や通信を行う箇所 231

  232. PageA PageB ComponentA Button 232

  233. PageA PageB ComponentA Button Click 233

  234. PageA PageB ComponentA Button Click 234

  235. PageA PageB ComponentA Button 235

  236. PageA PageB ComponentA Button 236

  237. PageA PageB ComponentA Button Click 237

  238. PageA PageB ComponentA Button Click 238

  239. PageA PageB ComponentA Button Click 239

  240. PageA PageB ComponentA Button 240

  241. PageA PageB ComponentA Button ページ遷移は ページコンポーネントを見れば 分かるようにすること 241

  242. PageA PageB Link Component これはページを見れば分かる (ページに遷移先情報がある) ので OK Parameter Link

    to PageB 242
  243. <template> <ContentsWithBreadcrumbs> ... <v-container> <List> <ListItem v-for="(user, index) in users"

    :key="index"> <ListTitle> <LinkComponent :to="{name:'user-detail', params: {id: user.id}}"> {{user.name}} </LinkComponent> </ListTitle> </ListItem> </List> </v-container> ... </template> <script lang="ts"> ... export default class UserIndexPage extends Vue {
  244. <template> <ContentsWithBreadcrumbs> ... <v-container> <List> <ListItem v-for="(user, index) in users"

    :key="index"> <ListTitle> <LinkComponent :to="{name:'user-detail', params: {id: user.id}}"> {{user.name}} </LinkComponent> </ListTitle> </ListItem> </List> </v-container> ... </template> <script lang="ts"> ... export default class UserIndexPage extends Vue {
  245. <template> <ContentsWithBreadcrumbs> ... <v-container> <List> <ListItem v-for="(user, index) in users"

    :key="index"> <ListTitle> <LinkComponent :to="{name:'user-detail', params: {id: user.id}}"> {{user.name}} </LinkComponent> </ListTitle> </ListItem> </List> </v-container> ... </template> <script lang="ts"> ... export default class UserIndexPage extends Vue {
  246. 通信も同じ 246

  247. Page Component Data 247

  248. Page Component Http Data 248

  249. Page Component Http Http Data 249

  250. Page Component Http Http コンポーネントの使いまわしが必要ない限り 通信も基本はページ (データもページが保持してバインディング) Data 250

  251. 251 <NoAuthorityTemplate> <Contents> <LoginForm :disabled="formDisabled" @submit="onSubmit" /> </Contents> </NoAuthorityTemplate> </template>

    <script lang="ts"> ... export default class LoginRootPage extends Vue { @Action("account/loadAccountInfo") public loadAccountInfo!: () => Promise<void>; public formDisabled: boolean = false; public onSubmit(model: ILoginRequest) { ServiceLocator.instance.messageService.clearMessage(); this.formDisabled = true; wraxios.post('/login', model, { onErrored: () => { this.formDisabled = false; }}) .then(_ => { this.onLoggedIn(); }); } ... }
  252. 252 <NoAuthorityTemplate> <Contents> <LoginForm :disabled="formDisabled" @submit="onSubmit" /> </Contents> </NoAuthorityTemplate> </template>

    <script lang="ts"> ... export default class LoginRootPage extends Vue { @Action("account/loadAccountInfo") public loadAccountInfo!: () => Promise<void>; public formDisabled: boolean = false; public onSubmit(model: ILoginRequest) { ServiceLocator.instance.messageService.clearMessage(); this.formDisabled = true; wraxios.post('/login', model, { onErrored: () => { this.formDisabled = false; }}) .then(_ => { this.onLoggedIn(); }); } ... }
  253. 253 <NoAuthorityTemplate> <Contents> <LoginForm :disabled="formDisabled" @submit="onSubmit" /> </Contents> </NoAuthorityTemplate> </template>

    <script lang="ts"> ... export default class LoginRootPage extends Vue { @Action("account/loadAccountInfo") public loadAccountInfo!: () => Promise<void>; public formDisabled: boolean = false; public onSubmit(model: ILoginRequest) { ServiceLocator.instance.messageService.clearMessage(); this.formDisabled = true; wraxios.post('/login', model, { onErrored: () => { this.formDisabled = false; }}) .then(_ => { this.onLoggedIn(); }); } ... } 通信はフォームで行わずルートで行っている
  254. まとめ 254

  255. 重要なこと 255

  256. 無駄な努力はせず 必要な努力をする 256

  257. チームのメンバーが 注力すべきところはどこか 考えながらコア部分を作っていこう 257

  258. Auther Masanobu Naruse HomePage https://nrslib.com Twitter @nrslib