[Svelte] 孫コンポーネントの入力値をbindするには？

# はじめに

`Svelte`初心者向けの記事です。
コードと動作確認は[「こちらの`REPL`」](https://svelte.dev/repl/008203d9f8774d24a97401fa69919c2e?version=3.31.0)でできます。

# 基本

[TutorialのBinding / Text inputs](https://svelte.dev/tutorial/text-inputs) にも書いてありますが、`Svelte`では要素に対して`bind`を付けることで`<script>`タグに書いた変数と**双方向バインド**してくれます。

```html:App.svelte
<script>
	let name = 'world';
</script>

<input bind:value={name}>

<h1>Hello {name}!</h1>
```

動作確認
![ezgif.com-gif-maker (2).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134221/6139d1e9-8577-7eff-ab44-03dc8cfd97cf.gif)


# 子コンポーネントの入力値をbindするには？

ここで、子コンポーネントとして`AppInput.svelte`を作成します。

```html:AppInput.svelte（子）
<script>
	// exportで宣言するとコンポーネントのpropsになる
	export let value;
</script>

<input bind:value>
```
**`<input bind:value>`は`<input bind:value={value}>`と同じです。**

すると、`App.svelte`（親）からの呼び出し方はこうなります。

```diff:App.svelte（親）
<script>
+	import AppInput from './AppInput.svelte';
	let name = 'world';
</script>


- <input bind:value={name}>
+ <AppInput bind:value={name} />

<h1>Hello {name}!</h1>
```

ポイントは、親も子コンポーネントのどちらにも`bind:`を付けてあげる必要があります。

# 孫コンポーネントの入力値をbindするには？

先程の`AppInput.svelte`を基底コンポーネントとしたとき、1つのコンポーネントの中に、複数の`AppInput.svelte`が存在することもあるでしょう。
例えば、以下のようなログイン画面が挙げられます。
![ezgif.com-gif-maker (3).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134221/6b997849-b47f-d4d2-df72-72d605a400ef.gif)

このログイン画面のコンポーネント構成は以下の通りです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134221/7e872a81-5080-85d7-7ec3-fabafc0dd625.png)

## 孫コンポーネント

`AppInput.svelte（孫）`の実装をもう少し改良してみます。

```diff:AppInput.svelte（孫）
<script>
-	export let value;
+	export let value = '';
+	export let type = 'text';
+	export let name = '';
+	export let placeholder = '';
+	const props = {
+		type,
+		name,
+		placeholder,
+	};
</script>


- <input bind:value={value}>
+ <input
+	bind:value
+	{...props}
+ />
```

###注意点（`input`要素に`type`を設定するには？）
`input`要素に直接`type`を設定すると以下のエラーが発生するので、`props`変数経由でセットしてください。
> 'type' attribute cannot be dynamic if input uses two-way binding

## 子コンポーネント

`FormInput.svelte（子）`の実装は、ラベル＋入力項目としています。ただ、親から渡された属性値を孫に渡しているだけです。**ここでも`bind:value`で`value`値を連動させる必要があります。

```html:FormInput.svelte（子）
<script>
	import AppLabel from './AppLabel.svelte';
	import AppInput from './AppInput.svelte';

	export let type;
	export let name;
	export let title;
	export let value;
	export let placeholder;
</script>

<AppLabel name='{name}' title='{title}' />
<AppInput
	type='{type}'
	name='{name}'
	placeholder='{placeholder}'
	bind:value
/>
```

## 親コンポーネント

ログイン用のコンポーネントとして、`email`と`password`の入力項目が必要なため、子コンポーネントである`FormInput`を2つ参照しています。

```html:Login.svelte
<script>
	import FormInput from './FormInput.svelte';
	import AppButton from './AppButton.svelte';

	let email;
	let password;

	const login = () => {
		console.log(`${email} | ${password}`);
	};
</script>

<div>
	<FormInput
		type='email'
		name='email'
		title='email'
		bind:value='{email}'
		placeholder='メールアドレスを入力してください'
	/>
	<FormInput
		type='password'
		name='password'
		title='パスワード'
		bind:value='{password}'
		placeholder='パスワードを入力してください'
	/>
</div>
<AppButton on:click='{login}'>ログイン</AppButton>
```

###注意点（`bind:value`って何？）
今まで`bind:value`として定義してきましたが、これは`bind:value='{value}'`の省略形です。そのため、それぞれ`bind:value='{email}'`と`bind:value='{password}'`のように`bind`する対象を指定する必要があります。

もちろん`bind:value`の`value`も`export`した変数名なので、`FormInput.svelte`で

```html:FormInput.svelte
	export let inputValue;
```
と宣言したなら、`bind:inputValue='{email}'`となります。