【ビギナー脱却】Vue.js これであなたもPropsマスター
複数のコンポーネントを利用してVue.jsのアプリケーションを構築していく場合は親コンポーネントから子コンポーネントに対してデータを渡すために利用されるPropsの知識が不可欠になります。本文書ではPropsのみに注目しシンプルなコードを利用しながらPropsの使い方について詳細に説明を行っています。本文書を読み終えるとVue.jsのビギナーの人でもPropsの使い方をマスターすることができます。
本文書ではVue3を利用して動作確認を行っています。
公開当初はVue3のOptions APIのみの説明でしたがComposition API(script setup)を利用した場合の説明を追記しています。2つの記述方法を比較しながら読み進めることができます。
目次
Propsとは
Propsという単語はPropertiesを表しています。Propsを利用することで親コンポーネントから子コンポーネントに文字列、数値、配列やオブジェクトなどの値を渡すことができます。JavaScriptで値を渡すという処理で最初に思い浮かぶのは関数かと思います。関数に値を渡す時に引数を利用するのと同様にPropsでも引数のようにコンポーネントに対して値を渡すことができます。また関数に値を与えることで決められた処理を行うようにコンポーネントにPropsを渡すことでコンポーネント内でも決められた処理を行うことができます。
コンポーネントが正しく処理を行うためには正しいデータをコンポーネントに渡す必要があります。Vue.jsでは渡す値のデータタイプやデフォルト値を設定することでバグにつながらないように正しいデータをPropsを利用してコンポーネントに渡す仕組みを備えています。
Propsを利用したシンプルな例
propsをどのように利用するかをシンプルな例を使って確認していきましょう。
まずコンポーネントProduct.vueを作成します。Product.vueコンポーネントはpropsでnameを受け取り表示させるだけのシンプルなものです。propsは配列を利用して設定を行うことができます。
【Composition APIの場合】
definePropsを利用してpropsを設定します。
<script setup>
defineProps(['name']);
</script>
<template>
<h3>{{ name }}</h3>
</template>
<style></style>
【Options APIの場合】
<template>
<h3>{{ name }}</h3>
</template>
<script>
export default {
name: "Product",
props: ["name"],
};
</script>
<style>
</style>
作成したProductコンポーネントを利用するためには親となる別のコンポーネントでProductコンポーネントをimportします。コンポーネントをimportすることでtemplateタグ内でProductタグとして利用できるようになります。PropsはProductタグで設定することがきるため”name”という名前で”トートバッグ”という値を渡しています。ここでは親コンポーネントにAppコンポーネント(App.vue)を利用しています。
【Composition APIの場合】
<script setup>
import Product from './components/Product.vue';
</script>
<template>
<Product name="トートバッグ" />
</template>
【Options APIの場合】
<template>
<div id="app">
<Product name="トートバッグ" />
</div>
</template>
<script>
import Product from "./components/Product.vue";
export default {
name: "App",
components: {
Product,
},
};
</script>
ブラウザ上には親コンポーネントから子コンポーネント(Product.vue)に渡したnameの値であるトートバッグが表示されます。
コンポーネントの再利用
コンポーネントは再利用することができるので、1つのコンポーネントの中でも複数のProductコンポーネントを利用することができます。nameに異なる値を渡すことで同じコンポーネントでも異なる内容を表示させることができます。
<Product name="トートバッグ" />
<Product name="バックパック" />
2つのコンポーネントを追加したのでnameに設定した異なる名前の情報が表示されます。
複数のコンポーネントを利用する場合にProduct.vueコンポーネントでの変更や設定は何も必要ありません。
v-bindを利用した場合
【Composition APIの場合】
Productタグのnameに直接値を入れていましたがscriptタグの中でデータを定義して値を渡することができます。その際はバインドをする必要があるのでnameの前にv-bindディレクティブを設定します。追加した変数productNameにトートバッグを設定しています。もしバインドを設定しない場合はnameに設定したproductNameの文字列をそのまま子コンポーネントに渡すことになります。
<script setup>
import Product from './components/Product.vue';
const productName = 'トートバッグ';
</script>
<template>
<Product v-bind:name="productName" />
<Product name="バックパック" />
</template>
Propsで渡すデータがユーザとのインタラクションによって変更する場合はref関数を利用してリアクティブな変数を定義して、その変数をv-bindで設定することができます。
<script setup>
import { ref } from 'vue';
import Product from './components/Product.vue';
const productName = ref('トートバッグ');
</script>
<template>
<Product v-bind:name="productName" />
<Product name="バックパック" />
</template>
【Options APIの場合】
Productタグのnameに直接値を入れていましたがVueのデータプロパティを利用して値を渡することができます。その際はバインドをする必要があるのでnameの前にv-bindディレクティブを設定します。追加したデータプロパティproductNameにトートバッグを設定しています。もしバインドを利用しない場合はnameに設定したproductNameの文字列をそのまま子コンポーネントに渡すことになります。
<template>
<div id="app">
<Product v-bind:name="productName" />
<Product name="バックパック" />
</div>
</template>
<script>
import Product from "./components/Product.vue";
export default {
name: "App",
components: {
Product,
},
data() {
return {
productName: "トートバッグ",
};
},
};
</script>
productNameにトートバッグを設定しているのでブラウザに表示される内容に変化はありませんがトートバッグは変数/データプロパティを経由して設定されていることになります。
PropsのTypeを設定
使用した例があまりにも簡単であるため渡す値に文字列以外のものを渡すことは考えられないかもしれません。しかしコードが複雑になれば、誤って文字列で渡すべきところを数値で渡したり、オブジェクトを渡したりすることもあります。ここまでの設定ではPropsで渡したデータがそのまま表示されてしまいます。
vue.jsでは上記のような問題を解消するためpropsにはどのような値が入るかtypeによってチェックを行うことができます。Productコンポーネントのpropsのnameには文字列が入るのでまずtypeにStringを設定します。Stringの先頭文字は大文字です小文字にするとエラーになります。これでnameにはString(文字列)が入ってくることを宣言しています。先ほどはpropsで配列を設定しpropsの名前を設定するだけでした。propsでのタイプチェックなど細かな設定を行いたい時はpropsは配列ではなく下記ようにオブジェクト形式で設定を行っていきます。
【Composition APIの場合】
<script setup>
defineProps({
name: {
type: String,
},
});
</script>
<template>
<h3>{{ name }}</h3>
</template>
下記のようにtypeを設定することもできます。
defineProps({
name: String,
});
【Options APIの場合】
export default {
name: "Product",
props: {
name: {
type: String,
},
},
};
再度ブラウザで確認すると先ほどと変化はありません。
次に渡す値を文字列ではなく数字で渡すことでどのような変化があるのか確認しましょう。
【Composition APIの場合】
ref関数を利用してreactiveな変数としてproductNameを定義しています。
const productName = ref(2);
//または
const productName = 2;
【Options APIの場合】
v-bindで設定していたデータプロパティproductNameの値を文字列から数字に変更します。
data() {
return {
productName: 2,
};
},
ブラウザをだけを数字が表示されますしかしコンソールログを見ると警告が表示されています。
typeチェックが行われString(文字列)ではなく数字が渡されていると警告で知らせてくれています。
このようにtypeを利用することで値の型のチェックを行うことができます。警告により文字列以外の値が渡されたことを即座に理解することができます。
下記のように直接nameに入れる値を数字にしても2を文字列として渡しているので何も警告が出ません。数字を渡したのに警告が出ないと慌てないでください。
<Product name="2" />
もし上記のように2を文字列ではなく数値で渡したい場合はnameの前にコロンをつけて:nameで行ってください。
<Product :name="2" />
Propsのデフォルト値の設定
子コンポーネントに設定したpropsが設定されなかった場合にデフォルト値を設定しておくことで設定したデフォルト値を利用することができます。下記ではdefault値に”バッグ”を設定しています。
【Composition APIの場合】
defineProps({
name: {
type: String,
default: 'バッグ',
},
});
【Options APIの場合】
props: {
name: {
type: String,
default: "バッグ",
},
},
default値を設定するとコンポーネントにnameを設定しない場合にデフォルト値が利用されます。2つ目のProductタグにはnameの設定を行っていません。
<Product v-bind:name="productName" />
<Product />
2つ目に表示されているバッグはdefault値を利用して表示されています。
もしデフォルト値を設定しなかった場合にどのようになるかの確認も行っておきましょう。
【Composition APIの場合】
defineProps({
name: {
type: String,
},
});
【Options APIの場合】
props: {
name: {
type: String,
},
},
defaultを設定しない場合は警告などのメッセージも表示されません。ブラウザ上にはpropsで何も値を渡していないので何も表示されません。トートバッグはもう一つのProductコンポーネントに設定を行った値です。
Requiredを設定し必須かどうかを設定
requiredプロパティを設定することでそのpropsが必須かどうかの設定を行うことができます。先ほどの続きでdefaultを設定なしで、コンポーネントタグの中にpropを設定していない状態でrequiredを設定してみましょう。requiredはtrueまたはfalseの値を設定することができます。
<Product v-bind:name="productName" />
<Product />
propsのnameに必須を設定するためにrequiredをtrueに設定します。
【Composition APIの場合】
defineProps({
name: {
type: String,
required: true,
},
});
【Options APIの場合】
props: {
name: {
type: String,
required: true,
},
},
ブラウザのコンソールを確認すると今回は必須なのにnameが与えられていないということで警告が表示されます。警告ではpropの”name”がないという表示されています。
requiredをtrueに設定することで警告が表示されたのでこのことからrequiredを設定していないデフォルトの場合はrequiredがfalseに設定されていることが予想できます。
defaultとrequired
defaultを設定している場合はpropsが与えられないとdefaultに設定した値が利用されるのでreuqiredを設定する必要はありません。つまり下記のようにdefaultとrequired2つの記述を行う必要がありません。
【Composition APIの場合】
defineProps({
name: {
type: String,
default: 'バッグ',
required: true,
},
});
【Options APIの場合】
props: {
name: {
type: String,
default: "バッグ",
required: true,
},
},
ここではStringの初期値を設定していますが、配列やオブジェクトを設定する場合は下記のメッセージが表示される場合があります。
[Vue warn]: Invalid default value for prop "[props name]": Props with type Object/Array must use a factory function to return the default value.
この場合は関数を利用する必要があります。
props: {
breadcrumbs: {
type: Array,
default: () => [],
default:[] //この設定ではエラーが発生
},
},
Validationを設定する(カスタムバリデーター)
type, default, requiredまでの設定については入門者の人でも理解することは簡単です。しかしValidationなどで関数の話が出てくると敬遠する人も多いと思いますが頑張って理解を深めていきましょう。
例えば今回の例ではバッグに関する商品だけこのProductコンポーネントを利用できるとします。決められた名前以外の商品が入った場合は警告が出るようにValidationを利用します。
下記ではpropsで受け取ったnameの値がバッグ、トートバッグ、バックパックのいずれかであるかチェックを行っています。indexOfメソッドで配列の中にvalueの値が含まれているかチェックをしています。含まれていない場合は-1が戻り値として戻されます。バッグ、トートバッグ、バックパックの3つ以外の文字列が入ってくるとvalidationに失敗したことになります。
【Composition APIの場合】
defineProps({
name: {
type: String,
default: 'バッグ',
validator: (value) => {
return ['バッグ', 'トートバッグ', 'バックパック'].indexOf(value) !== -1;
},
},
});
【Options APIの場合】
props: {
name: {
type: String,
default: "バッグ",
validator: (value) => {
return ["バッグ", "トートバッグ", "バックパック"].indexOf(value) !== -1;
},
},
},
コンポーネントの値を与えるproductNamをトートバッグからショルダーバッグに変更を行います。
【Composition APIの場合】
const productName = ref('ショルダーバッグ');
【Options APIの場合】
data() {
return {
productName: "ショルダーバッグ",
};
},
ショルダーバッグはvalidatorで指定した配列の要素に含まれていないのでコンソールログのメッセージにはカスタムバリデーターが失敗している警告が表示されます。
validatorがまだ難しいと感じているかもしれないのでもう少し詳細に説明するとvalidatorではreturnでtrueかfalseを返すことで成功か失敗かが決められるので、return trueと記述すればいつも成功ということになります。return falseに変更すると必ずコンソールログに警告が表示されます。
【Composition APIの場合】
defineProps({
name: {
type: String,
default: 'バッグ',
validator: () => {
return true;
},
},
});
【Options APIの場合】
props: {
name: {
type: String,
default: "バッグ",
validator: () => {
return true;
},
},
},
Propsで受け取った値の文字列の長さでValidationを行いたい場合は下記のように記述することでvalidationを設定することができます。このValidataionでは6文字よりも多い文字数のものであればtrueになるため、3文字のバッグを入力した場合はfalseになり警告が表示されます。
【Composition APIの場合】
defineProps({
name: {
type: String,
default: 'ショッピングバッグ',
validator: (value) => {
return value.length > 6;
},
},
});
【Options APIの場合】
props: {
name: {
type: String,
default: "ショッピングバッグ",
validator: (value) => {
return value.length > 6;
},
},
},
アロー関数を使わない場合は下記のように記述することができます。
props: {
name: {
type: String,
default: "ショッピングバッグ",
validator: function (value) {
return value.length > 6;
},
},
Propsの値の更新(注意)
Propsは親コンポーネントから子コンポーネントへ一方的にpropsを渡すことができます。そのため子コンポーネントでは受け取ったpropsを更新してはいけません。子コンポーネントから親コンポーネントへデータを送りたい場合はemitを利用したイベントを利用してpropsを使って渡された値はすべて親のコンポーネントで更新処理を行います。
Propsの次はSlotについて
親コンポーネントから子コンポーネントに値を渡すということではSlotも存在します。Propsが文字列、数値、配列やオブジェクトを渡すのに対してSlotではHTMLのタグを渡すことができます。今回Propsの理解を深めることができたのでPropsの次はSlotの理解を深めて行ってください。