概要
App.vueをベースに子コンポーネントをレンダリングする。
html、css、jsを同じ設定で使い回す際に便利な機能。
App.vueをベースにcomponentsディレクトリを作成(ディレクトリ名は自由)。
src
├─ assets
├─ components
├─ App.vue
├─ main.ts
...
子コンポーネントの利用方法
componentsディレクトリに子コンポーネントとなるvueファイルを作成してApp.vueでimportする。
<template>
<section class="box">
<p>子コンポーネント</p>
</section>
</template>
<script setup lang="ts">
import Child from "./components/Child.vue";
</script>
<template>
<section>
<div>App.vue</div>
<Child />
</section>
</template>
App.vue
子コンポーネント
コンポーネントの独立性
inputとv-modelで表示を確認。
同じ子コンポーネントでも独立して動作する。
<script setup lang="ts">
import {ref} from "vue";
const name = ref("名無し");
</script>
<template>
<section>
<p>{{name}}</p>
<input type="text" v-model="name">
</section>
</template>
<style scoped>
section {
color: blue;
}
</style>
<script setup lang="ts">
import Child from "./components/Child.vue";
</script>
<template>
<section>
<div>App.vue</div>
<Child />
<Child />
</section>
</template>
<style>
section {
color: red;
}
</style>
App.vue // 赤色
hoge // 青色
[hoge]
huga // 青色
[fuga]
styleのscopedについて
上記サンプルでは、子コンポーネントのstyleにscopedを付することで子コンポーネント独自のスタイルを設定している。
子コンポーネントのスタイルを削除すれば子コンポーネントの文字色は赤色になり、scopedをなくしてstyleを記述するとApp.vueの文字色が青色になる。
<stype scoped>
...
</style>
親から子へデータを渡す
子コンポーネントでは受け取るデータを定義してdefinePropsを宣言し、親コンポーネントでは子コンポーネントの引数としてデータを渡すことができる。
interface Props {
各Prop名: データ型;
...
}
defineProps<Props>();
<子コンポーネント名
各prop名="渡す値"
...
<script setup lang="ts">
interface Props {
title: string;
content: string;
}
defineProps<Props>();
</script>
<template>
<section class="box">
<p>{{title}}</p>
<p>{{content}}</p>
</section>
</template>
<script setup lang="ts">
import Child from "./components/Child.vue";
</script>
<template>
<section>
<div>App.vue</div>
<Child
title="Propsのtitle"
content="propのcontent"/>
</section>
</template>
App.vue
Propsのtitle
propのcontent
親の変数を子に渡す
子コンポーネントは前項と同じで、親コンポーネントの記述が変わる。
値を渡す場合はprop名を引数としていたが、変数を渡す場合はv-bind.prop名
となる。
※ 子コンポーネントは前項と同じため省略
<子コンポーネント名
v-bind:Prop名="変数名"
...
/>
<script setup lang="ts">
import {ref} from "vue";
import Child from "./components/Child.vue";
const propsTitle = ref("propのtitle");
const propsContent = ref("propのcontect");
</script>
<template>
<section>
<div>App.vue</div>
<Child
v-bind:title="propsTitle"
v-bind:content="propsContent"/>
</section>
</template>
App.vue
Propsのtitle
propのcontent
v-forとの組み合わせ
子コンポーネントは前項と同じで、親コンポーネントの記述が変わる。
※ 子コンポーネントは前項と同じため省略
<子コンポーネント名
v-for=:"要素の変数 in 配列の変数"
v-bind:Prop名="変数名"
...
/>
<script setup lang="ts">
import {ref} from "vue";
import Child from "./components/Child.vue";
interface Weather {
id: number;
title: string;
content: string;
}
const weatherListInit = [
{id: 1, title: "今日の天気", content: "晴でしょう" },
{id: 2, title: "明日の天気", content: "雨でしょう" },
];
const weatherList = ref(weatherListInit);
</script>
<template>
<section>
<h2>App.vue</h2>
<Child
v-for="weather in weatherList"
v-bind:key="weather.id"
v-bind:title="weather.title"
v-bind:content="weather.content"/>
</section>
</template>
今日の天気
晴でしょう
明日の天気
雨でしょう
親から渡された値を子で利用する
definePropsで値を受け取った子コンポーネントで値を利用することはできるが、値の直接編集はできないめリアクティブ変数に値をコピーして利用する。
const propsの変数名 = defineProps<型>();
const 子で利用するの変数名 = ref(propsの変数名);
<script setup lang="ts">
import { ref, computed } from "vue";
interface Props {
points: number;
note?: string;
}
const props = defineProps<Props>();
const localPoints = ref(props.points);
const localNote = computed(
(): string => {
let localNote = props.note ? props.note : "--";
return localNote;
}
);
const pointUp = (): void => {
localPoints.value++;
}
</script>
<template>
<section class="box">
<div>
<span>保有ポイント:{{ localPoints }}</span>
<span>、備考:{{ localNote }}</span>
</div>
<button v-on:click="pointUp">ポイント加算</button>
</section>
</template>
<script setup lang="ts">
import { ref, computed } from "vue";
import Child from "./components/Child.vue";
const lists = [
{points: 35, note: "note"},
{points: 56, },
]
const lists = ref(lists);
</script>
<template>
<section>
<Child
v-for="list in lists"
v-bind:points="list.points" v-bind:note="list.note" />
</section>
</template>
保有ポイント:39、備考:note
[ポイント加算]
保有ポイント:56、備考:--
[ポイント加算]
// ポイント加算を押下すると各ポイントが加算される
子から親への通信
子から親への通信はイベント処理を記述する。
流れとしては親コンポーネントに処理とv-onを記述して、子にemitを定義してemitの実行を記述する。
interface Emits {
(event: "イベント名"): void;
};
const emit = defineEmits<Emits>();
処理名 = (): void => {
emit(”イベント名”)
}
・scriptタグ
処理メソッド
・templateタグ
<子コンポーネント名
v-on:子コンポーネントのイベント名="処理メソッド"
/>
<script setup lang="ts">
interface Emits {
(event: "createNewVal"): void;
}
const emit = defineEmits<Emits>();
const onNewRandButtonClick = (): void => {
emit("createNewVal");
}
</script>
<template>
<section class="box">
<button v-on:click="onNewRandButtonClick">加算</button>
</section>
</template>
<script setup lang="ts">
import { ref } from "vue";
import Child from "./components/Child.vue";
const parent = ref(0);
const oncreateNewVal = (): void => {
parent.value++;
}
</script>
<template>
<section>
<p>親コンポーネントで値を表示: {{ parent }}</p>
<Child v-on:createNewVal="oncreateNewVal" />
</section>
</template>
親コンポーネントで値を表示: 0
[加算]
// 加算ボタンで値が1が加算される
emitの第二引数
前述したemitの第二引数に
interface Emits {
(event: "イベント名", 変数:型): void;
};
const emit = defineEmits<Emits>();
処理名 = (): void => {
emit(”イベント名”, 渡す値)
}
・scriptタグ
処理メソッド(子から受け取る値:型)
<script setup lang="ts">
interface Emits {
(event: "createNewVal", int: number): void;
}
const emit = defineEmits<Emits>();
const onNewRandButtonClick = (): void => {
emit("createNewVal", 2);
}
</script>
<template>
<section class="box">
<button v-on:click="onNewRandButtonClick">加算</button>
</section>
</template>
<script setup lang="ts">
import { ref } from "vue";
import Child from "./components/Child.vue";
const parent = ref(0);
const oncreateNewVal = (int: number): void => {
parent.value = parent.value + int;
}
</script>
<template>
<section>
<p>親コンポーネントで値を表示: {{ parent }}</p>
<Child v-on:createNewVal="oncreateNewVal" />
</section>
</template>
ProvideとInject
複数ページのアプリケーションでは、App.vueを親として子コンポーネントを作成していくとコードが複雑になり可読性が低くなるため、今までの子コンポーネントを中間コンポーネントとして孫コンポーネントを作成する。
この場合にデータの受け渡しを行うのが「アプリケーション全体で参照されるデータを提供するprovide
」と「階層に関わらずデータの注入ができるinject
」という方法になる。
また、inject()の戻り値はunknown型のため、型変換を行う必要がある。
※ サンプルでは親から孫まで同じ型宣言をするのでtype.tsを作成している
親コンポーネントで値を表示: 0
[加算]
// 加算ボタンで値が2が加算される
provide("Provide名", 値);
変数の宣言 = inject("provide名");
provide("Provide名", 値);
<script setup lang="ts">
import { provide } from "vue";
import BaseSection from "./components/BaseSection.vue";
import type { propsList } from "@/components/type";
const lists: propsList[] = [
{ id: 1, points: 35, note: "note" },
{ id: 2, points: 24 },
];
const childTitle: string = "孫コンポーネント";
provide("lists", lists);
provide("childTitle", childTitle);
</script>
<template>
<BaseSection />
</template>
<script setup lang="ts">
import { ref, inject } from "vue";
import ChildSection from "./Child.vue";
import type { propsList } from "@/components/type";
const lists = inject("lists") as propsList[];
// const lists = inject<propsList[]>("lists"); // 型指定でもOK
</script>
<template>
<section>
<p>base section</p>
<ChildSection v-for="list in lists"
v-bind:id="list.id"
v-bind:points="list.points"
v-bind:note="list.note"
/>
</section>
</template>
<script setup lang="ts">
import {computed, inject} from "vue";
import type { title, propsList } from "@/components/type";
const list = defineProps<propsList>();
const localNote = computed(
(): string => {
let localNote = list.note ? list.note : "--";
return localNote;
}
);
const childTitle = inject<title>("childTitle");
</script>
<template>
<section>{{ childTitle }}</section>
<section>
<span>id:{{ list.id }}、point:{{ list.points }}、備考: {{ localNote }}</span>
</section>
</template>
export type propsList = {
id: number;
points: number;
note?: string;
};
base section
孫コンポーネント
id:1、point:35、備考: note
孫コンポーネント
id:2、point:24、備考: --