［Vue3］slotのざっくりまとめ

# 概要
親コンポーネントからhtml要素をカスタマイズして子コンポーネントに渡してレンダリングさせるための記法。

# slotの基本
子コンポーネントで`slot`タグを記述する。
親コンポーネントでは子コンポーネントを呼び出す処理を記述するが`<子コンポーネント>...</子コンポーネント>`として閉じタグが必要となる。
また、親から子への値渡しは`v-b　ind:***`で行える。


```:記法：子コンポーネント
<template>
	<slot />
</template>
```

```:記法：親コンポーネント
<template>
	<子コンポーネント名>
		<div>slotに渡す内容</div>  // タグに限定はない
	</子コンポーネント名>
</template>
```


```child.vue
<script setup lang="ts">
defineProps<{
	name: string;
}>();
</script>

<template>
	<section>
		<div>・{{ name }}</div>
		<slot />
	</section>
</template>
```

```App.vue
<script setup lang="ts">
import {ref} from "vue";
import Child from "./components/Child.vue";

const name = ref("slotのサンプル");
</script>

<template>
	<section>
		<div>slotの利用</div>
		<Child v-bind:name="name">
			<p>App.vue</p>
		</Child>
	</section>
</template>
```

```:表示結果
slotの利用
・slotのサンプル
App.vue
```


# slotのデフォルト値と渡す値
Slotに対して親コンポーネントから必ずHTML要素が渡される保証はないため、HTML要素が渡されなかった場合を想定してデフォルトのHTML要素を設定することができる。
これをフォールバックコンテンツ（FallbackContent）という。
また、slotへ値を渡す場合は該当ファイルの変数のみで、コンポーネントをまたいだ変数の参照はできない。

```child.vue
<script setup lang="ts">
defineProps<{
	name: string;
}>();
</script>

<template>
	<section>
		<div>・{{ name }}</div>
		<slot>
			<p>デフォルト</p>
		</slot>
	</section>
</template>
```

```App.vue
<script setup lang="ts">
import {ref} from "vue";
import Child from "./components/Child.vue";

const hoge = ref("hoge");
const fuga = ref("fuga");
</script>

<template>
	<section>
		<div>Slotの利用</div>
		<Child v-bind:name="hoge">
			<p>{{ hoge }} App.vue</p>
			<!-- <p>{{ name }} App.vue</p> -->  // エラー
		</Child>
		<Child v-bind:name="fuga"/>
	</section>
</template>

```

```:表示結果
Slotの利用
・hoge
hoge
・fuga
デフォルト
```

# 異なるhtml要素をレンダリングする
特別な内容ではないが今までのサンプルが同じhtml要素だったため備忘として作成。

```child.vue
<script setup lang="ts">
interface Props {
	name: string;
}
defineProps<Props>();
</script>

<template>
	<section>
		<div>・{{ name }}さん</div>
		<slot>
			<p>child text</p>
		</slot>
	</section>
</template>
```

```App.vue
<script setup lang="ts">
import { ref } from "vue";
import Child from "./components/Child.vue";

const listsInit: string[] = ["aaa", "bbb"];
const lists = ref(listsInit);
const hoge = ref("hoge");
const fuga = ref("fuga");
</script>

<template>
	<section>
		<Child v-bind:name="hoge">
			<ul>
				<li v-for="litst in lists" v-bind:key="litst">
					{{ litst }}
				</li>
			</ul>
		</Child>
		<Child v-bind:name="fuga" />
	</section>
</template>
```

```:表示結果
・hogeさん
aaa
bbb
・fugaさん
child text
```


# 名前付きslot
ひとつのコンポーネントの複数の箇所に異なるHTML要素を挿入したい場合、それぞれのslotタグに名前を付けることがで、れを名前付きSlot（NamedSlots）という。
子コンポーネントでslotに`name="slot名"`として親コンポーネントでは`v-slot:slot名`と記述する。
また、slot名がないslotは親コンポーネントで`v-slot:default`と記述することで指定できる。
この際、slot名がないslotは親コンポーネントで`v-slot:default`と記述がないと#document-fragmentとなる。

```:記法：子コンポーネント
<slot name="slot名">...</slot>
```

```:記法：親コンポーネント
<template v-slot:slot名>...</template>
```

```child.vue
<script setup lang="ts">
interface Props {
	name: string;
}
defineProps<Props>();
</script>

<template>
	<section>
		<p>---</p>
		<div>・default：{{ name }}</div>
		<slot>
			<p>default</p>
		</slot>

		<div>・detail</div>
		<slot name="detail">
			<p>child detail</p>
		</slot>
	</section>
</template>
```

```App.vue
<script setup lang="ts">
import { ref } from "vue";
import Child from "./components/Child.vue";

const text = ref<string>("text");

const hoge = ref("hoge");
const fuga = ref("fuga");
const foo = ref("foo");
</script>

<template>
	<section>
		<Child v-bind:name="hoge" />  // 親からの値なし
		<Child v-bind:name="fuga">  // v-slot:defaultなし
			<template>
				<p>App.vue</p>
			</template>
			<template v-slot:detail>
				<p>{{ text }}</p>
			</template>
		</Child>
		<Child v-bind:name="foo">  // v-slot:defaulあり
			<template v-slot:default>
				<p>App.vue</p>
			</template>
			<template v-slot:detail>
				<p>{{ text }}</p>
			</template>
		</Child>
	</section>
</template>
```

```:表示結果
--- // 親からの値なし
・default：hoge
default
・detail
child detail
---  // v-slot:defaultなし
・default：fuga
・detail
text
---  // v-slot:defaultあり
・default：foo
App.vue
・detail
text
```

# 動的にコンポーネントを選択する
動的コンポーネントではコンポーネント名の代わりにcomponentタグを使って、`v-bind:is`を記述する。
この場合、子コンポーネントでの特段の記述は必要がなく、temlateタグ内にhtmlを記述するだけで作成できる。
サンプルではInputとRadioのコンポーネントを作成する。

```:記法：親コンポーネント
import コンポーネント名 from "./components/コンポーネント名.vue";
...
<component v-bind:is="コンポーネントを格納した変数" />
```

```Input.vue
<script setup lang="ts">
import { ref } from "vue";

const inputNameModel = ref("hoge");
</script>

<template>
	<input type="text" v-model="inputNameModel">
	<p>{{ inputNameModel }}</p>
</template>

```

```Radio.vue
<script setup lang="ts">
import { ref } from "vue";

const memberType = ref(1);
</script>

<template>
	<label>
		<input type="radio" name="memberType" value="1" v-model="memberType">
		hoge
	</label>
	<label>
		<input type="radio" name="memberType" value="2" v-model="memberType">
		fuga
	</label>
	<br>
	<p>選択されたラジオボタン: {{ memberType }}</p>
</template>

```


```App.vue
<script setup lang="ts">
import { ref } from "vue";
import Input from "./components/Input.vue";
import Radio from "./components/Radio.vue";

// 現在表示させるコンポーネントを表すテンプレート変数
const currentComp = ref(Input);
// コンポーネントの配列
const compList = [Input, Radio];
// コンポーネント名の配列
const compNameList: string[] = ["Input", "Radio"];
// 現在表示させているコンポーネントに対応した配列のインデックス番号
let currentCompIndex = 0;
// コンポーネントを切り替えるメソッド
const switchComp = (): void => {
	// インデックス番号をインクリメント
	currentCompIndex++;
	// インデックス番号の判定
	currentCompIndex = currentCompIndex > 1 ? 0 : currentCompIndex;
	// インデックス番号に該当するコンポーネントをcurrentCompに代入
	currentComp.value = compList[currentCompIndex];
}
</script>

<template>
	<p>コンポーネント名: {{ compNameList[currentCompIndex] }}（index.{{ currentCompIndex }}）</p>
	<KeepAlive>
		<component v-bind:is="currentComp" />
	</KeepAlive>
	<button v-on:click="switchComp">切り替え</button>
</template>
```

```:表示結果
・Input
コンポーネント名: Input（index.0）
［hoge］
hoge
【切り替え】

・Radio
コンポーネント名: Radio（index.1）
◎hoge ◯fuga
選択されたラジオボタン: 1
【切り替え】
```

## KeepAliveタグ
KeepAliveタグがないコンポーネントは、非表示になった際にunmounted状態になり、全てのデータなどが破棄される。
KeepAliveが記述されたコンポーネントは、表示されなくなった際にunmounted状態ではなく待機状態になため内部ではレンダリングされないままDOMが維持されており、入力内容なども含め現在の状態が保持される。

# 子から親へデータを渡す
`slot props`の記法により子から親へslot経由でデータを渡すことができる。
個人的にはemitでいいとは感じるところ、、
利便性のいい場面がわかる方、教えて頂けますと幸いです。

```:記法：子コンポーネント
<slot v-bind:SlotProp名="該当データのテンプレート変数名">
```

```:記法：親コンポーネント
<template v-slot="slotProps">
<div>{{ SlotProp.該当データのテンプレート変数名 }}</div>
```

```child.vue
<script setup lang="ts">
import {reactive} from "vue";

const memberInfo = reactive({
	name: "田中太郎",
	state: "問題ありません。"
});
</script>

<template>
	<section>
		<slot v-bind:memberInfo="memberInfo">
			<h1>指名：{{memberInfo.name}}</h1>
			<h2>状態：{{memberInfo.state}}</h2>
		</slot>
	</section>
</template>

```

```App.vue
<script setup lang="ts">
<script setup lang="ts">
import Child from "./components/Child.vue";
</script>

<template>
	<section>
		<Child>
			<template v-slot="slotProps">
				<dl>
					<dt>名前</dt>
					<dd>{{ slotProps.memberInfo.name }}</dd>
					<dt>状況</dt>
					<dd>{{ slotProps.memberInfo.state }}</dd>
				</dl>
			</template>
		</Child>
	</section>
</template>
```

```:表示結果
名前
    田中太郎
状況
    問題ありません。
```






```:記法：子コンポーネント
```

```:記法：親コンポーネント
```

```child.vue
```

```App.vue
```

```:表示結果
```


# v-slotの省略形
v-slotには省略形があり`#`を使う。

```:記法
< template #detail>
```