[TypeScript]LoginFormのパスワードコンポーネントを作成する。

前編に引き続き、今日は前編に定義したValidateルールでパスワードコンポーネントを作ってみようと思います。
constant.tsではvalidationルールを定義しています。 前編に出た内容と同じ
```typescript:constant.ts
import { ValidateRule } from "./types";

export const RequireRule: ValidateRule = {
  rule: /.+/,
  match: true,
  message: '必須入力項目です.',
};

export const CantContainWhitespace: ValidateRule = {
  rule: /\s/,
  match: false,
  message: '空白は含められません.',
};

export const CantStartNumber: ValidateRule = {
  rule: /^\d/,
  match: false,
  message: '数字で始まるIDは使用できません.',
}

export const MinimumLengthLimit = (limit: number): ValidateRule => ({
  rule: new RegExp(`(.){${limit}}`),
  match: true,
  message: `少なくとも${limit}文字以上でなければなりません.`,
});
```

app.tsでは、それぞれの入力コンポーネントを定義し、それぞれのフィールドコンポーネントに必要なvalidationを追加します。
```typescript:app.ts
  // 省略
  private initialize = () => {
    const nameField = new TextField('#required-fields', { 
      id: 'name', label: '名前', type: 'text', placeholder: '名前を入力してください', require: true,
    });

    const idField = new TextField('#required-fields', { 
      id: 'id', label: 'ID', type: 'text', placeholder: 'IDを入力してください', require: true,
    });

    const emailField = new TextField('#required-fields', { 
      id: 'email', label: 'e-mail', type: 'email', placeholder: 'e-mailを入力してください', require: true,
    });
    
    const passwordField = new PasswordField('#required-fields', { 
      id: 'password', label: 'パスワード', placeholder: 'パスワードを入力してください', 
    });

    const addressField = new AddressField('#optional-fields', {
      id: 'address', label: 'お届け先',
    });

    idField.addValidateRule(CantContainWhitespace);
    idField.addValidateRule(CantStartNumber);
    idField.addValidateRule(MinimumLengthLimit(3));

    emailField.addValidateRule(CantContainWhitespace);

    this.fields.push(nameField);
    this.fields.push(idField);
    this.fields.push(emailField);
    this.fields.push(passwordField);
    this.fields.push(addressField);
  }
// 省略
```

パスワードコンポーネントです。
```typescript:password-field.ts
// 省略


  //UIに入力されたパスワードが暗号の水準がどれほど強いか表示するメッセージ
  const StrongMessage: [string, string, string, string] = [
   '禁止された水準',
   '深刻な水準',
   '普通のレベル',
   '強力な暗号'
  ];

  //実際の画面に出力するデータを作成する関数
  private buildData = () => { 
    let strongLevel = -1;
    const isInvalid: ValidateRule | null = this.validate();

    //入力された暗号の長さが0より大きくなったら
    if (this.data.text!.length > 0) { strongLevel++; }
    // 入力されたパスワードの長さが12より大きい場合は
    if (this.data.text!.length > 12) { strongLevel++; }
    //特殊記号を一つでも使う場合はstrongLevel++
    if (/[!@#$%^&*()]/.test(this.data.text!)) { strongLevel++; }
    //入力された暗号に数字が含まれていると
    if (/\d/.test(this.data.text!)) { strongLevel++; }

    //objectをリターンしている。
    //このobjectはtemplateで使用されるが、{{key値}}の形で用いられている。
    return {
      ...this.data, 
      updated: this.updated,
      valid: this.updated ? !isInvalid : true,
      strongMessage: strongLevel < 0 ? '' : StrongMessage[strongLevel],
      strongLevel0: strongLevel >= 1,
      strongLevel1: strongLevel >= 2,
      strongLevel2: strongLevel >= 3,
      strongLevel3: strongLevel >= 4,
    };
  }

  private validate = (): ValidateRule | null => {
    const target = this.data.text ? this.data.text.trim() : '';
    // filter とする理由は、validation に違反する事項が n つである可能性があるから
    const invalidateRules = this.validateRules
      .filter(validateRule => validateRule.rule.test(target) !== validateRule.match);

    return (invalidateRules.length > 0) ? invalidateRules[0] : null;
  }

  //containerイベントにリスナーを登録する
  private attachEventHandler = () => {
    document.querySelector(this.container)?.addEventListener('change', this.onChange);
  }

  //update()の説明は下の説明を確認!
  private update = () => {
    const container = document.querySelector(`#field-${this.data.id}`) as HTMLElement;
    const docFrag = document.createElement('div');

    docFrag.innerHTML = this.template(this.buildData());

    container.innerHTML = docFrag.children[0].innerHTML;
  }

// 省略
```

update()はtemplateをアップデートする関数です。
docFrag.innerHtmlからbuildDataした値をすぐcontainer.innerHtmlに入れるのではなく 
childrenを抽出して入れています。
このようにアップデートする理由は、最初にこのPasswordFieldのUIが作られる時と違って(renderが呼び出される時)
アップデートは既存の値、UIを維持しながら、つまり少なくとも外側にあるcontainer UIは維持しながら 
内側だけ変えてあげる必要があります。このような理由は、イベント ハンドラーのためです。
コンポーネントの上位であるcontainerにイベント ハンドラーを登録しています。もし、パスワードコンポーネントにイベントを登録すると、
イベントがinnerHtmlで新しいUIがアップデートされる時、ハンドラ自体もすべて消えてしまうことになります。 
すると、その度にイベントハンドラーを新たに登録し続けなければならない問題が生じます。 
そうすると、様々なコード構成や、後でメモリ問題なども生じることがあります。
というときの最も効果的な方法は、親にイベント ハンドラーを登録し、子要素をアップデートすることです。
そうなると、子供がいなくなってまたできるとしても、イベントハンドラーには変化がありません。
このようなテクニックを使える根拠は、イベントシステムが基本的にbubbling、capturingメカニズムを持っているからです！

（bubbling、capturingとは？https://javascript.info/bubbling-and-capturing）


'1'を入力した時。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3097775/bd2443fe-14df-d236-3c7c-bbb6623d0426.png)

'1fdf###FDFDF1wf'を入力した時
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3097775/9709a5c0-79e7-3fb1-a16b-f4a578065d50.png)