［Vue3］コンポーネントざっくりまとめ

# 概要
App.vueをベースに子コンポーネントをレンダリングする。
html、css、jsを同じ設定で使い回す際に便利な機能。

App.vueをベースにcomponentsディレクトリを作成（ディレクトリ名は自由）。

```:ディレクトリ構造
src
 ├─ assets
 ├─ components
 ├─ App.vue
 ├─ main.ts
 ...
```

# 子コンポーネントの利用方法
componentsディレクトリに子コンポーネントとなるvueファイルを作成してApp.vueでimportする。

```Child.vue
<template>
	<section class="box">
		<p>子コンポーネント</p>
	</section>
</template>
```

```App.vue
<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で表示を確認。
同じ子コンポーネントでも独立して動作する。

```Child.vue
<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>
```

```.vue
<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名="渡す値"
    ...
```

```Child.vue
<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>
```

```App.vue
<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名="変数名"
    ...    
/>
```

```App.vue
<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名="変数名"
    ...
/>
```

```App.vue
<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の変数名);
```

```Child.vue
<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>
```

```App.vue
<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:子コンポーネントのイベント名="処理メソッド"
/>
```

```Child.vue
<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>
```

```App.vue
<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タグ
処理メソッド(子から受け取る値:型)
```

```Child.vue
<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>


```

```App.vue
<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("Provide名", 値);
```

```:記法：inject
変数の宣言 = inject("provide名"); 

provide("Provide名", 値);
```


```App.vue
<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>

```

```BaseSection.vue
<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>

```

```ChildSection.vue
<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>

```

```type.ts
export type propsList = {
    id: number;
    points: number;
    note?: string;
};

```

```:表示結果
base section
孫コンポーネント
id：1、point：35、備考： note
孫コンポーネント
id：2、point：24、備考： --
```