LoginSignup
7
2

More than 3 years have passed since last update.

Vue3+Typescriptのネストコンポーネント間データの受け渡し

Posted at

この記事はWanoグループ Advent Calendar 2020の13日目の記事になります。

Vue 3+Typescript (classなし) コンポネント間のデータとイベントの受け渡し方法を整理してメモしておきたいです。

Vueネストコンポーネント

Vueのアプリケーションはネストされたコンポーネントのツリーの構成になっています。コンポーネントの階層の間データを受け渡す方法がいくつかあります。

Props

propsはプロパティの略、コンポーネントに登録できるカスタム属性です。Javascriptメソッドへ引数を渡すみたいにVueのコンポーネントへpropsを渡すことができます。

Propsの2つの特徴

  • propsは下の階層のコンポーネントへ渡せる
  • propsはread-only。更新したい場合は親のコンポーネントに新しい値を返して(emit event)親のコンポーネントで更新する必要がある

chrome_RhgFMkOvI6.png

Propsを更新しないコンポネント

Propsを渡すのみone-wayの親と子コンポーネントの例です。

ParentComponent.vue

<template>
    <child-component
      :childStringProp="childString"
      :childObjectProp="childObject"
      :childObjectArrayProp="childObjectArray"
    />
</template>

<script lang="ts">
import { defineComponent } from "vue";
import ChildComponent from "@/components/ChildComponent.vue";
import ChildObject from "@/entity/ChildObject.vue";

export default defineComponent({
  components: {
    ChildComponent,
  },
  setup() {
    const childString = "string";
    const childObject: ChildObject = {
      test: "test"
    };
    const childObjectArray: ChildObject[] = [
      {
        test: "test",
      },
    ];
    return {
      // data
      childString,
      childObject,
      childObjectArray,
    };
  }
});
</script>
ChildComponent.vue

<template>
    {{ childString }}
    {{ childStringModified }}
    {{ childObject }}
    {{ childObjectArray }}
</template>

<script lang="ts">
import { defineComponent, computed, PropType} from "vue";
import ChildObject from "@/entity/ChildObject.vue";

export default defineComponent({
  props: {
    childString: String,
    childObject: ChildObject,
    childObjectArray: Array as PropType<ChildObject[]>,
  },
  setup(props) {
    // computed仕様の例も書いておきます
    const childStringModified = computed(() => {
      return props.childString + 'modified';
    });
    return {
      // computed
      childStringModified,
    };
  }
});
</script>

上の例の注意が必要なところは一つだけです。オブジェクトの配列を型を指定できないので、VueのPropTypeを使って型を指定できるようになります。

Propsを更新するコンポネント

子のコンポーネントにデータの更新が必要なときにpropsの更新ではなく親のコンポネントへ更新イベントをemitする必要があります。

やり方が主に2つあります。
- 親のコンポーネントにVueのv-modelを仕様する
- 親にイベントを設定してfunctionを呼び出す

v-modelのtwo-way受け渡しの例

ParentComponent.vue

<template>
    <child-component
      v-model:childStringProp="childString"
      v-model:childString2Prop="childString2Computed"
    />
</template>

<script lang="ts">
import { defineComponent } from "vue";
import ChildComponent from "@/components/ChildComponent.vue";

export default defineComponent({
  components: {
    ChildComponent,
  },
  setup() {
    // refを使用したら変数がreactiveになり更新されたらUI上も表示が変わる
    const childString = ref<string>("string");
    const childString2 = ref<string>("string2");

    // getter&setterの使用の例、setterにemitして上のコンポーネントに返すこともできる
    const childString2Computed = computed({
      get: () => childObject.value,
      set: val => {
        childObject.value = val
      }
    });

    return {
      // data
      childString,
      childString2Computed,
    };
  }
});
</script>
ChildComponent.vue

<template>
    <input
    :value="childString"
    @input="$emit("update:childStringProp", e.target.value)"
  />
    <input
    :value="childString2"
    @input="childString2Update"
  />
</template>

<script lang="ts">
import { defineComponent} from "vue";

export default defineComponent({
  emits: ["update:childStringProp", "update:childString2Prop"],
  props: {
    childString: String,
    childString2: String,
  },
  setup({emit}) {
    function childString2Update(e: any) {
      this.$emit("update:childString2Prop", e.target.value);
    }
    return {};
  }
});
</script>

注意するところはVue 3のref<>の使用することのとcomputedにgetter&setterの設定のことです。

子コンポーネントのイベントを受け取る

も1つのtwo-wayデータ受け渡し方法はコンポーネント間のイベントです。

ParentComponent.vue

<template>
    <child-component
      childStringProp="childString"
      @updateChildStringProp="updateChildStringFunc"
    />
</template>

<script lang="ts">
import { defineComponent } from "vue";
import ChildComponent from "@/components/ChildComponent.vue";

export default defineComponent({
  components: {
    ChildComponent,
  },
  setup() {
    const childString = ref<string>("string");

    function updateChildStringFunc(val: string) {
      childString.value = val
    }
    return {
      // data
      childString,

      // func
      updateChildStringFunc,
    };
  }
});
</script>
ChildComponent.vue

<template>
    <input
    :value="childString"
    @input="childStringUpdate"
  />
</template>

<script lang="ts">
import { defineComponent, PropType} from "vue";
import ChildObject from "@/entity/ChildObject.vue";

export default defineComponent({
  emits: ["updateChildStringProp"],
  props: {
    childString: String,
  },
  setup() {
    function childStringUpdate(e: any) {
      this.$emit("updateChildStringProp", e.target.value);
    }
    return {};
  }
});
</script>

まとめ

Vueのネスト構成は2-3階層の場合propsとemitは適切ですが、コンポーネントの階層が増えるたびに深いコンポネントまでたどり着くと可読性と保守性が落ちてきます。
また、ループの中のコンポーネントとIDに依存しているコンポーネントにもpropsの受け渡しは難しくなります。
繁雑なアプリにpropsではなくVuexやVue3のreactive storeなどのコンポーネント間にデータをシェアと管理できるツールを実装すればいいと思います。

7
2
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
7
2