0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

JSXを使ってラジオボタンの設定をシンプルにする

Last updated at Posted at 2020-12-27

始めに

Vue.jsでラジオボタンの設定をする時は以下のように書くと思いますが、毎回v-modelの設定をするのが面倒だと感じたことはないでしょうか?

ラジオボタンの設定
<template lang="pug">
div
  label
    input(v-model="$data.value", type="radio", value="A")
    | A
  label
    input(v-model="$data.value", type="radio", value="B")
    | B
</template>

これを以下のようにselectと同じように親だけv-modelを設定するだけで済むようなコンポーネントを作ることができますので、そのやり方を紹介したいと思います。

selectと同じような設定方法
<template lang="pug">
div
  //- 親コンポーネントだけv-modelを設定する
  RadioButtonGroup(v-model="$data.value")
    RadioButton(value="A") A
    RadioButton(value="B") B
</template>

実装方法

タイトルにも書いてありますが、JSXを使用します。JSXを使用することで子コンポーネントに対してプロパティを注入することができるので、RadioButtonコンポーネントを探してv-modelのプロパティを注入する形になります(正確にはv-modelの対象となるprops名を入れます)。
vnodeの操作についてはvee-validateのコードを参考にしました。

またvnodeのプロパティの注入はこちらを参考にしました。

JSXで必要なプロパティ・イベントを注入する
const RADIO_BUTTON_NAME = "RadioButton";

/**
 * リスナーオブジェクトにリスナーを追加する
 * @param {Object} listeners - リスナーオブジェクト
 * @param {string} eventName - イベント名
 * @param {function} listener - コールバック関数
 */
function _addListener(listeners, eventName, listener) {
  // リスナーが未登録の場合は登録して終了
  if (!listeners[eventName]) {
    listeners[eventName] = [listener];
    return;
  }

  // 既に単体でリスナーが登録されている時は配列形式にする
  if (typeof listeners[eventName] === "function") {
    listeners[eventName] = [listeners[eventName]];
  }

  // 配列形式のリスナーは単純に追加する
  listeners[eventName].push(listener);
}

/**
 * vnodeにリスナーを追加する
 * @param {Object} vnode - vnode
 * @param {string} eventName - イベント名
 * @param {function} listener - コールバック関数
 */
function addListener(vnode, eventName, listener) {
  // リスナーのプロパティ自体がない時は初期化する
  if (!vnode.componentOptions.listeners) {
    vnode.componentOptions.listeners = {};
  }

  _addListener(vnode.componentOptions.listeners, eventName, listener);
}

/**
 * componentNameにマッチするvnodeにapplyFuncを適応させる
 * @param {Array} vnodes - vnodeリスト
 * @param {string} componentName - コンポーネント名
 * @param {function} applyFunc - マッチしたvnodeに対しての処理
 */
function applyMatchedNodes(vnodes, componentName, applyFunc) {
  const regex = new RegExp(`${componentName}$`);
  for (let i = 0; i < vnodes.length; i++) {
    const vnode = vnodes[i];
    if (regex.test(vnode.tag)) {
      applyFunc(vnode);
    }
    // 子供がある場合は再帰する
    if (vnode.children) {
      applyMatchedNodes(vnode.children, componentName, applyFunc);
    }
  }
}

export default {
  name: 'RadioButtonGroup',
  model: {
    prop: "selectedValue",
    event: "select",
  },
  props: {
    selectedValue: { type: String },
  },
  render() {
    const vnodes = this.$slots.default;
    // ラジオボタンのコンポーネント全てにプロパティを注入する
    applyMatchedNodes(vnodes, RADIO_BUTTON_NAME, (vnode) => {
      if (!vnode.componentOptions || !vnode.componentOptions.propsData) {
        return;
      }
      // 注入するパラメータを入れる(v-modelの対象となるprops名をselectedValueにしているため、その値を入れる)
      vnode.componentOptions.propsData.selectedValue = this.$props.selectedValue;
      // リスナーを設定する
      addListener(vnode, "select", (value) => {
        this.$emit("select", value);
      });
    });
    return <div>{vnodes}</div>;
  },
};

v-modelの値を注入する処理は子孫まで見てくれるため、以下のようにスタイル調整のためにdivが入ってもきちんと値の連動はしてくれます。

子孫でもv-modelの連携はできる
<template lang="pug">
div
  RadioButtonGroup(v-model="$data.value")
    .list
      .list__item
        //- 途中divタグが入っているが、きちんとv-modelが注入されている
        RadioButton(value="A") A
      .list__item
        RadioButton(value="B") B
      .list__item
        RadioButton(value="C") C
</template>

<style lang="scss" scoped>
.list {
  display: flex;

  &__item {
    width: 50px;
  }
}
</style>

終わりに

以上がJSXを使ってラジオボタンの設定をシンプルにする方法でした。JSXはVue.jsでは普段使わないものですが、一部では使うと書きやすくなることがあるため、これを機会に使ってみるのはいかがでしょうか。
サンプルはCodeSandboxに書きましたので、興味がある方は是非見てください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?