Riot.js フォームの扱い


Riotでのフォームの扱い方を紹介します。

DOM要素に直接アクセス

おそらく一番簡単でわかりやすい方法です。フォームがちょっとでも複雑になるとコードがカオスになりがちなので実務ではあまり使いません。
サッと簡単なフォームを作る時にはこれで十分ですね。

<app-form>
  <form onsubmit="{ handleSubmit }">
    <input type="email" name="email" placeholder="メールアドレス" />
    <input type="password" name="password" placeholder="パスワード" />
    <input type="checkbox" id="password_visible" onchange="{ handleCheckChange }">
    <label for="password_visible">パスワード表示</l1abel>
    <button>送信</button>
  </form>
  <script>
    export default {
      handleCheckChange(e) {
        this.$('[name=password]').type = e.target.checked ? 'text' : 'password'
      },

      handleSubmit(e) {
        e.preventDefault()
        const email = this.$('[name=email]').value
        const password = this.$('[name=password]').value
        console.log({ email, password })
      }
    }
  </script>
</app-form>

form要素から値を取得

送信するだけのフォームならおすすめです。
DOMを検索しない分上の方法よりパフォーマンスはいいのですが、誤差ですね😅

<app-form>
  <form onsubmit="{ handleSubmit }">
    <input type="email" name="email" placeholder="メールアドレス" />
    <input type="password" name="password" placeholder="パスワード" />
    <button>送信</button>
  </form>
  <script>
    export default {
      handleSubmit(e) {
        e.preventDefault()
        const fields = e.target.elements
        const email = fields['email'].value
        const password = fields['password'].value
        console.log({ email, password })
      }
    }
  </script>
</app-form>

stateにフォームの値を持つ

冗長だけど凝ったことをするならこれ一択ですね。
自分はフォームを作るとき大体あとから複雑になってくるので、最初から全部この方法で統一しています。

<app-form>
  <form onsubmit="{ handleSubmit }">
    <input type="email" name="email" placeholder="メールアドレス" oninput="{ handleEmail }" value="{ state.email }" />
    <input type="{ state.passwordType }" name="password" placeholder="パスワード" oninput="{ handlePassword }" value="{ state.password }" />
    <input type="checkbox" id="password_visible" onchange="{ handleCheckChange }">
    <label for="password_visible">パスワード表示</l1abel>
      <button>送信</button>
  </form>
  <script>
    export default {
      state: {
        email: '',
        password: '',
        passwordType: 'password'
      },

      handleEmail(e) {
        this.state.email = e.target.value
        this.update()
      },

      handlePassword(e) {
        this.state.password = e.target.value
        this.update()
      },

      handleCheckChange(e) {
        this.state.passwordType = e.target.checked ? 'text' : 'password'
        this.update()
      },

      handleSubmit(e) {
        e.preventDefault()
        console.log(this.state)
      }
    }
  </script>
</app-form>

さすがにフィールドごとにイベントハンドラーhandle...を用意するのは冗長すぎるので、普段は一つのハンドラーで工夫しています。

-    <input type="email" name="email" placeholder="メールアドレス" oninput="{ handleEmail }" value="{ state.email }" />
-    <input type="{ state.passwordType }" name="password" placeholder="パスワード" oninput="{ handlePassword }" value="{ state.password }" />
+    <input type="email" name="email" placeholder="メールアドレス" oninput="{ handleChange }" value="{ state.email }" />
+    <input type="{ state.passwordType }" name="password" placeholder="パスワード" oninput="{ handleChange }" value="{ state.password }" />
...

-      handleEmail(e) {
-        this.state.email = e.target.value
-        this.update()
-      },
-      handlePassword(e) {
-        this.state.password = e.target.value
-        this.update()
-      },
+      handleChange(e) {
+        this.state[e.target.name] = e.target.value
+        this.update()
+      },

その他

name属性がないフィールドも扱う

<awesome-color-picker value="" on-change="" />という感じのname属性のないカスタムなコンポーネントがある時 + なるべくhandleChange一つにまとめたい

<app-form>
  <form onsubmit="{ handleSubmit }">
    <awesome-color-picker on-change="{ color => handleChange('color', color) }" value="{ state.color }" />
    <input type="text" oninput="{ e => handleChange('text', e.target.value) }" value="{ state.text }" />
  </form>
  <script>
    export default {
      state: {
        text: '',
        color: '#ffffff'
      },

      handleChange(name, value) {
        this.state[name] = value
        this.update()
      },

      handleSubmit(e) {
        e.preventDefault()
        console.log(this.state)
      }
    }
  </script>
</app-form>

{ e => handleChange('text', e.target.value) } ← 好みの問題ですがこれが「なんか気持ち悪い...」なら

-    <awesome-color-picker on-change="{ color => handleChange('color', color) }" value="{ state.color }" />
-    <input type="text" oninput="{ e => handleChange('text', e.target.value) }" value="{ state.text }" />
+    <awesome-color-picker on-change="{ handleChange('color') }" value="{ state.color }" />
+    <input type="text" oninput="{ handleChange('text') }" value="{ state.text }" />
...

-      handleChange(name, value) {
-        this.state[name] = value
-        this.update()
-      },
+      handleChange(name) {
+        return e => {
+          this.state[name] = e.target ? e.target.value : e;
+          this.update();
+        }
+      },

こっちの方がすっきりする気がします。

フォームが複雑になってくるとイベントハンドラ一つでカバーできないときもあるので、無理に一つにまとめずに複数のイベントハンドラを使いましょう。自分は単純なものは上にあったようなhandleChangeを使い、それ以外のものは個別にイベントハンドラを作ります。

最後に

質問などがあれば気軽にコメントしてください!(^^♪