Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

nrs
September 25, 2019

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

nrs

September 25, 2019
Tweet

More Decks by nrs

Other Decks in Programming

Transcript

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  6. エラー
    6

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  11. その上でもう一度
    11

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  15. どうすれば?
    15

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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









    システムエラー 回復可能エラー
    22

    View Slide

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









    システムエラー 回復可能エラー
    23

    View Slide

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









    システムエラー 回復可能エラー
    24

    View Slide

  25. [email protected]@example.com
    メールアドレス
    25

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  29. 29

    View Slide

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

    View Slide

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

    View Slide

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









    システムエラー 回復可能エラー
    32

    View Slide

  33. naruse
    ユーザ名
    [email protected]
    メールアドレス
    ********
    パスワード
    ********
    パスワードの確認
    33

    View Slide

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

    View Slide

  35. 35

    View Slide

  36. Mail: [email protected]
    Name: naruse
    Password: ********
    36

    View Slide

  37. Mail: [email protected]
    Name: naruse
    Password: ********
    duplicated-email,
    duplicated-username
    37

    View Slide

  38. Mail: [email protected]
    Name: naruse
    Password: ********
    duplicated-email,
    duplicated-username
    duplicated-email
    だからメール欄に
    エラー表示して
    38

    View Slide

  39. Mail: [email protected]
    Name: naruse
    Password: ********
    duplicated-email,
    duplicated-username
    duplicated-email
    だからメール欄に
    エラー表示して
    duplicated-username
    だからユーザ名欄に
    エラー表示して
    39

    View Slide

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

    View Slide

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

    View Slide

  42. 42

    View Slide

  43. 43

    View Slide

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

    View Slide

  45. 登録済みのメールアドレスです
    このユーザ名はすでに取得されています
    naruse
    ユーザ名
    [email protected]
    メールアドレス
    ********
    パスワード
    ********
    パスワードの確認
    これなら?
    45

    View Slide

  46. どう実現する?
    46

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  51. export default class WraxiosCore {
    ...
    public request(method: Method, url: string, data?: any, aParams?: IOptionParameter): 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>((resolve, reject) => {
    axios.request(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 本体

    View Slide

  52. export default class WraxiosCore {
    ...
    public request(method: Method, url: string, data?: any, aParams?: IOptionParameter): 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>((resolve, reject) => {
    axios.request(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 (通信ライブラリ)をラップ

    View Slide

  53. export default class WraxiosCore {
    ...
    public request(method: Method, url: string, data?: any, aParams?: IOptionParameter): 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>((resolve, reject) => {
    axios.request(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 の取り回しを
    確認してみよう

    View Slide

  54. axios.request(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

    View Slide

  55. axios.request(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
    エラーハンドラー選定

    View Slide

  56. 56
    export default class WraxiosCore {
    ...
    private selectErrorHandler(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));
    };

    View Slide

  57. 57
    export default class WraxiosCore {
    ...
    private selectErrorHandler(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));
    };
    デフォルト実装

    View Slide

  58. 58
    export default class WraxiosCore {
    ...
    private selectErrorHandler(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));
    };
    デフォルト=メッセージシステムに通知

    View Slide

  59. 59
    export default class WraxiosCore {
    ...
    private selectErrorHandler(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));
    };
    デフォルト=メッセージシステムに通知
    具体的なメッセージは?

    View Slide

  60. axios.request(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
    さきほどのコード

    View Slide

  61. axios.request(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
    レスポンスからエラー用メッセージの生成

    View Slide

  62. 62
    export default class WraxiosCore {
    ...
    private makeResponse(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;
    }
    }
    エラーメッセージ生成処理

    View Slide

  63. 63
    export default class WraxiosCore {
    ...
    private makeResponse(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;
    }
    }
    エラーコードからキーを生成

    View Slide

  64. 64
    export default class WraxiosCore {
    ...
    private makeResponse(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;
    }
    }
    メッセージのデータファイルから読み取り

    View Slide

  65. 65
    export default class WraxiosCore {
    ...
    private makeResponse(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" : "ユーザー名が長すぎます"
    }
    }
    }
    }
    }
    }

    View Slide

  66. 66
    export default class WraxiosCore {
    ...
    private makeResponse(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;
    }
    }
    エラーメッセージが見つからない時は
    デフォルトメッセージ

    View Slide

  67. これで基本的に
    何もしなくてOK
    67

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  73. 73
    export interface IOptionParameter {
    onError?: IErrorHandler;
    onErrored?: () => void;
    onUnexpectedError?: UnexpectedErrorHandler;
    config?: AxiosRequestConfig;
    }
    export default class WraxiosCore {
    ...
    public get(url: string, params?: IOptionParameter): Promise> {
    return this.request('get', url, null, params);
    }
    public post(url: string, data?: any, params?: IOptionParameter): Promise> {
    return this.request('post', url, data, params);
    }
    }
    エラーハンドラー登録用

    View Slide

  74. 74
    export default class UserAddConfirmPage extends Vue {
    ...
    public onSubmitButtonClick() {
    wraxios.post(
    '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: this.model};
    }
    }

    View Slide

  75. 75
    export default class UserAddConfirmPage extends Vue {
    ...
    public onSubmitButtonClick() {
    wraxios.post(
    '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: this.model};
    }
    }
    ページ遷移してエラー通知する

    View Slide

  76. 76

    View Slide

  77. 77

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  88. 基本は何もしなくてよくて
    必要なときに差し込める
    というのが大事
    88

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  95. 人力を排除せよ
    95

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  100. 100
    import LoginRootPage from "@/views/login/LoginRootPage.vue";
    export default {
    path: '/login',
    name: 'login',
    component: LoginRootPage,
    meta: { noAuth: true }
    };
    ページに認証不要を設定
    (未設定=認証必要)

    View Slide

  101. 101
    認証確認をしているのは

    View Slide

  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();
    }
    ルート変更時のグローバルフック

    View Slide

  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();
    }

    View Slide

  104. 104
    async function transferLoginWhenNoAuth(to: Route, from: Route, next: Function)
    : Promise {
    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;
    }

    View Slide

  105. 105
    async function transferLoginWhenNoAuth(to: Route, from: Route, next: Function)
    : Promise {
    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;
    }
    遷移先は認証が必要か

    View Slide

  106. 106
    async function transferLoginWhenNoAuth(to: Route, from: Route, next: Function)
    : Promise {
    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;
    }
    認証の確認

    View Slide

  107. 107
    async function transferLoginWhenNoAuth(to: Route, from: Route, next: Function)
    : Promise {
    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;
    }
    認証していなかったらセッション確認

    View Slide

  108. 108
    async function transferLoginWhenNoAuth(to: Route, from: Route, next: Function)
    : Promise {
    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;
    }
    未認証ならログイン画面へ

    View Slide

  109. 109

    View Slide

  110. 110
    未認証

    View Slide

  111. 111
    未認証
    認証

    View Slide

  112. 112
    async function transferLoginWhenNoAuth(to: Route, from: Route, next: Function)
    : Promise {
    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;
    }

    View Slide

  113. 113
    async function transferLoginWhenNoAuth(to: Route, from: Route, next: Function)
    : Promise {
    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;
    }
    遷移先を保存

    View Slide

  114. 114
    export default class LoginRootPage extends Vue {
    @Action("account/loadAccountInfo") public loadAccountInfo!: () => Promise;
    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'});
    }
    }
    }
    ログイン画面

    View Slide

  115. 115
    export default class LoginRootPage extends Vue {
    @Action("account/loadAccountInfo") public loadAccountInfo!: () => Promise;
    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'});
    }
    }
    }
    ログイン後、保存された遷移先へ

    View Slide

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

    View Slide

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

    View Slide

  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 関連

    View Slide

  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,
    },
    ...
    ]
    }
    最高権限が必要

    View Slide

  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 権限

    View Slide

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

    View Slide

  122. 122
    export default {
    path: '/',
    component: TopRootPage,
    meta: {role: 'any'},
    children: [
    {
    path: '',
    name: 'top',
    component: TopIndexPage
    }
    ]
    };
    権限不要

    View Slide

  123. 実際の処理
    123

    View Slide

  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();
    }
    ルート変更時のグローバルフック

    View Slide

  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();
    }
    認証後に認可の確認がある

    View Slide

  126. 126
    async function transferWhenNoRole(to: Route, from:Route, next: Function): Promise {
    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;
    }

    View Slide

  127. 127
    async function transferWhenNoRole(to: Route, from:Route, next: Function): Promise {
    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;
    }

    View Slide

  128. 128
    async function transferWhenNoRole(to: Route, from:Route, next: Function): Promise {
    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);
    }
    そもそも認証が要求されるか確認

    View Slide

  129. 129
    async function transferWhenNoRole(to: Route, from:Route, next: Function): Promise {
    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;
    }

    View Slide

  130. 130
    async function transferWhenNoRole(to: Route, from:Route, next: Function): Promise {
    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'));
    }
    認可が不要か確認

    View Slide

  131. 131
    async function transferWhenNoRole(to: Route, from:Route, next: Function): Promise {
    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;
    }

    View Slide

  132. 132
    async function transferWhenNoRole(to: Route, from:Route, next: Function): Promise {
    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 = metaRole;
    return accountStoreFacade.isAllowedAny(allowedRoles);
    } else {
    const allowedRole = !!metaRole
    ? metaRole
    : AccountRole.Administrator;
    return accountStoreFacade.isAllowed(allowedRole);
    }
    })
    }
    自身の権限と照らし合わせる

    View Slide

  133. 133
    async function transferWhenNoRole(to: Route, from:Route, next: Function): Promise {
    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へ遷移しエラー表示

    View Slide

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

    View Slide

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

    View Slide

  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);
    });

    View Slide

  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);
    });

    View Slide

  138. 403 のハンドリング
    138

    View Slide

  139. 139
    const notifyMessageWhenUnexpectedErrorHandler: IUnexpectedErrorHandler = (
    resolve: (value?: WraxiosResponse | PromiseLike>) => void,
    reject: (reason?: any) => void,
    error: AxiosError
    ) => {
    ...
    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;
    }
    };

    View Slide

  140. 140
    const notifyMessageWhenUnexpectedErrorHandler: IUnexpectedErrorHandler = (
    resolve: (value?: WraxiosResponse | PromiseLike>) => void,
    reject: (reason?: any) => void,
    error: AxiosError
    ) => {
    ...
    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へ遷移

    View Slide

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

    View Slide

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

    View Slide

  143. ObjectA ObjectB
    143

    View Slide

  144. ObjectA ObjectB
    144

    View Slide

  145. ObjectA ObjectB
    Method
    Call
    145

    View Slide

  146. ObjectA ObjectB
    Method
    Call
    Method
    Call
    146

    View Slide

  147. ObjectA ObjectB
    Method
    Call
    Method
    Call
    147

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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
    相互参照

    View Slide

  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 を渡してると
    怪しいかも

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  158. ObjectA ObjectB
    158

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  180. ObjectA ObjectB
    methodForA
    methodForB
    methodForB2
    methodForA2
    methodForB3
    methodForA3
    methodForA4
    180

    View Slide

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

    View Slide

  182. どうするか?
    182

    View Slide

  183. ObjectA ObjectB
    183

    View Slide

  184. ObjectA ObjectB
    Parent
    ObjectA ObjectB
    184

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  190. コンポーネントも
    同じ
    190

    View Slide

  191. TabButton TabButton TabButton TabButton
    191

    View Slide

  192. TabButton TabButton TabButton TabButton
    Click
    192

    View Slide

  193. TabButton TabButton TabButton TabButton
    Click
    Select
    193

    View Slide

  194. TabButton TabButton TabButton TabButton
    Click
    Select
    194

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  198. TabButton TabButton TabButton TabButton
    TabButtonGroup
    198

    View Slide

  199. TabButton TabButton TabButton TabButton
    TabButtonGroup
    Click
    199

    View Slide

  200. TabButton TabButton TabButton TabButton
    TabButtonGroup
    Click
    Clicked
    200

    View Slide

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

    View Slide

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

    View Slide

  203. TabButton TabButton TabButton TabButton
    TabButtonGroup
    Select
    203

    View Slide

  204. TabButton TabButton TabButton TabButton
    TabButtonGroup
    Select
    204

    View Slide

  205. TabButton TabButton TabButton TabButton
    TabContent
    Click
    205

    View Slide

  206. TabButton TabButton TabButton TabButton
    TabContent
    Clicked
    TabButtonGroup
    206

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  215. TabButton
    TabContent
    TabButtonGroup
    Tab
    215

    View Slide

  216. TabButton
    TabContent
    TabButtonGroup
    Tab
    216

    View Slide

  217. TabButton
    TabContent
    TabButtonGroup
    Tab
    メソッド
    217

    View Slide

  218. TabButton
    TabContent
    TabButtonGroup
    Tab
    メソッド
    218

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  222. TabButton TabButton TabButton TabButton
    TabContent
    TabButtonGroup
    Clicked
    Tab
    イベント
    Clicked
    (B)
    222

    View Slide

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

    View Slide

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

    View Slide

  225. TabButton TabButton TabButton TabButton
    TabContent
    TabButtonGroup
    Tab
    Select
    (B)
    Select
    メソッド
    Vue などでは
    これをバインディングで
    実現する
    225

    View Slide

  226. たとえば
    226

    View Slide

  227. 227







    <br/>export default class LoginRootPage extends Vue {<br/>...<br/>public formDisabled: boolean = false;<br/>public onSubmit(model: ILoginRequest) {<br/>ServiceLocator.instance.messageService.clearMessage();<br/>this.formDisabled = true;<br/>wraxios.post('/login', model, { onErrored: () => { this.formDisabled = false; }})<br/>.then(_ => {<br/>this.onLoggedIn();<br/>});<br/>}<br/>...<br/>

    View Slide

  228. 228







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

    View Slide

  229. 229







    <br/>export default class LoginRootPage extends Vue {<br/>...<br/>public formDisabled: boolean = false;<br/>public onSubmit(model: ILoginRequest) {<br/>ServiceLocator.instance.messageService.clearMessage();<br/>this.formDisabled = true;<br/>wraxios.post('/login', model, { onErrored: () => { this.formDisabled = false; }})<br/>.then(_ => {<br/>this.onLoggedIn();<br/>});<br/>}<br/>...<br/>

    View Slide

  230. 230







    <br/>export default class LoginRootPage extends Vue {<br/>...<br/>public formDisabled: boolean = false;<br/>public onSubmit(model: ILoginRequest) {<br/>ServiceLocator.instance.messageService.clearMessage();<br/>this.formDisabled = true;<br/>wraxios.post('/login', model, { onErrored: () => { this.formDisabled = false; }})<br/>.then(_ => {<br/>this.onLoggedIn();<br/>});<br/>}<br/>...<br/>子コンポーネントの操作は<br/>あくまでそれを保持するコンポーネント<br/>

    View Slide

  231. ページ遷移や通信を行う箇所
    231

    View Slide

  232. PageA PageB
    ComponentA
    Button
    232

    View Slide

  233. PageA PageB
    ComponentA
    Button
    Click
    233

    View Slide

  234. PageA PageB
    ComponentA
    Button
    Click
    234

    View Slide

  235. PageA PageB
    ComponentA
    Button
    235

    View Slide

  236. PageA PageB
    ComponentA
    Button
    236

    View Slide

  237. PageA PageB
    ComponentA
    Button
    Click
    237

    View Slide

  238. PageA PageB
    ComponentA
    Button
    Click
    238

    View Slide

  239. PageA PageB
    ComponentA
    Button
    Click
    239

    View Slide

  240. PageA PageB
    ComponentA
    Button
    240

    View Slide

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

    View Slide

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

    View Slide



  243. ...





    {{user.name}}





    ...

    <br/>...<br/>export default class UserIndexPage extends Vue {<br/>

    View Slide



  244. ...





    {{user.name}}





    ...

    <br/>...<br/>export default class UserIndexPage extends Vue {<br/>

    View Slide



  245. ...





    {{user.name}}





    ...

    <br/>...<br/>export default class UserIndexPage extends Vue {<br/>

    View Slide

  246. 通信も同じ
    246

    View Slide

  247. Page
    Component
    Data
    247

    View Slide

  248. Page
    Component
    Http
    Data
    248

    View Slide

  249. Page
    Component
    Http
    Http
    Data
    249

    View Slide

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

    View Slide

  251. 251






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

    View Slide

  252. 252






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

    View Slide

  253. 253






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

    View Slide

  254. まとめ
    254

    View Slide

  255. 重要なこと
    255

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide