【vue3】component基礎



* 参考
[vue公式リファレンス：https://v3.ja.vuejs.org/guide/component-basics.html#%E5%9F%BA%E6%9C%AC%E4%BE%8B](https://v3.ja.vuejs.org/guide/component-basics.html#%E5%AD%90%E3%82%B3%E3%83%B3%E3%83%9B%E3%82%9A%E3%83%BC%E3%83%8D%E3%83%B3%E3%83%88%E3%81%AE%E3%82%A4%E3%83%98%E3%82%99%E3%83%B3%E3%83%88%E3%82%92%E8%B3%BC%E8%AA%AD%E3%81%99%E3%82%8B)

* 全てを網羅していませんが、今今活用できる内容をまとめました。

# コンポーネントの基本（グローバルコンポーネント）

* 基本的なcomponentの定義
    * グローバルコンポーネントとローカルコンポーネントの2種類存在する。
        * 以下に示しているのはグローバルコンポーネント
        * グローバルコンポーネントはアプリケーション内のどのコンポーネントのテンプレートでも使う事ができる。

``` js
// Vue アプリケーションを作成します
const app = Vue.createApp({})

//app変数に格納されたvueインスタンスに直接コンポーネントを紐づけている。
// グローバルな button-counter というコンポーネントを定義します
app.component('button-counter', {
  data() {
    return {
      count: 0
    }
  },
  template: `
    <button @click="count++">
      You clicked me {{ count }} times.
    </button>`
})

//vueの仮想DOMにマウントする。
app.mount('#components-demo')
```

* html側ではcomponentの名前定義でマウントする。

```html

<div id="components-demo">
  <button-counter></button-counter> 
  <button-counter></button-counter> //コンポーネントは再利用が可能。
  <button-counter></button-counter> //それぞれ独立したコンポーネントインスタンスになる。
</div>

```

<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="rNVqYvM" data-user="Vue" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/team/Vue/pen/rNVqYvM">
  Component basics: reusing components</a> by Vue (<a href="https://codepen.io/team/Vue">@Vue</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

## コンポーネントのローカル登録

グローバル登録のデメリット
* 全てのコンポーネントをグローバル登録すると、使用していないコンポーネントも最終ビルドに含まれ、余分なjavascriptを増やしまう。

### ローカル登録手順

1. プレーンjavascriptとしてコンポーネントを定義

``` js
const ComponentA = {
  /* ... */
}
const ComponentB = {
  /* ... */
}
const ComponentC = {
  /* ... */
}
```

2. components オプション内に使用したいコンポーネントをkey value形式で定義します。

```js
const app = Vue.createApp({
  components: {
    'component-a': ComponentA,
    'component-b': ComponentB
  }
})
```
**ローカル登録されたコンポーネントはサブコンポーネントでは利用できない。**

ComponentA を ComponentB 内で使用可能にしたいときは、以下のように使用する必要があります.

```js
const ComponentA = {
  /* ... */
}

const ComponentB = {
  components: {
    'component-a': ComponentA
  }
  // ...
}
```
Babel と Webpack のようなものを用いて ES2015 モジュールを利用している場合は、このようになります.

```js
import ComponentA from './ComponentA.vue'

export default {
  components: {
    ComponentA
  }
  // ...
}
```
## [モジュールシステム内のローカル登録](https://v3.ja.vuejs.org/guide/component-registration.html#%E3%83%A2%E3%82%B7%E3%82%99%E3%83%A5%E3%83%BC%E3%83%AB%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E5%86%85%E3%81%AE%E3%83%AD%E3%83%BC%E3%82%AB%E3%83%AB%E7%99%BB%E9%8C%B2)

単一ファイルコンポーネントを使用している場合、それぞれインポートをして使用する必要がある。

``` js
import ComponentA from './ComponentA'
import ComponentC from './ComponentC'

export default {
  components: {
    ComponentA,
    ComponentC
  }
  // ...
}

//ComponentA と ComponentC が ComponentB のテンプレート内で利用できるようになりました。
```


# [コンポーネントの構成](https://v3.ja.vuejs.org/guide/component-basics.html#%E3%82%B3%E3%83%B3%E3%83%9B%E3%82%9A%E3%83%BC%E3%83%8D%E3%83%B3%E3%83%88%E3%81%AE%E6%A7%8B%E6%88%90)

* コンポーネントはツリー構造に構成する事ができる。
* すなわち親子関係が出来上がる。

![components.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378066/784d4673-e2e7-5d44-28a7-8d0a5edeb494.png)

# [プロパティを用いた子コンポーネントへのデータの受け渡し](https://v3.ja.vuejs.org/guide/component-basics.html#%E3%83%95%E3%82%9A%E3%83%AD%E3%83%8F%E3%82%9A%E3%83%86%E3%82%A3%E3%82%92%E7%94%A8%E3%81%84%E3%81%9F%E5%AD%90%E3%82%B3%E3%83%B3%E3%83%9B%E3%82%9A%E3%83%BC%E3%83%8D%E3%83%B3%E3%83%88%E3%81%B8%E3%81%AE%E3%83%86%E3%82%99%E3%83%BC%E3%82%BF%E3%81%AE%E5%8F%97%E3%81%91%E6%B8%A1%E3%81%97)

## case

親から子へプロパティー（props）を渡すことでデータのやり取りが可能になる。

```js

//親となるコンポーネント
const App = {
  data() {
    return {
      posts: [
        { id: 1, title: 'My journey with Vue' },
        { id: 2, title: 'Blogging with Vue' },
        { id: 3, title: 'Why Vue is so fun' }
      ]
    }
  }
}

// Appコンポーネントを引数にとりvueインスタンスを作成
const app = Vue.createApp(App)

app.component('blog-post', {
  props: ['title'],  //propsオプションに子で使用するプロパティ名を定義している。
  template: `<h4>{{ title }}</h4>`
})

app.mount('#blog-posts-demo')
```

* コンポーネントをそれぞれレンダリングします
* v-bind を用いて動的にプロパティを渡すことができると分かります。これはレンダリングする内容が事前に分からない場合に特に便利です。

```html
<div id="blog-posts-demo">
  <blog-post
    v-for="post in posts"
    :key="post.id"
    :title="post.title"
  ></blog-post>
</div>
```

## プロパティの型

プロパティを文字列の配列として列挙してきました。

`props: ['title', 'likes', 'isPublished', 'commentIds', 'author']`

プロパティをオブジェクトとして列挙し、プロパティのキーと値にそれぞれプロパティの名前と型を設定します:

```js

props: {
  title: String,
  likes: Number,
  isPublished: Boolean,
  commentIds: Array,
  author: Object,
  callback: Function,
  contactsPromise: Promise // またはその他のコンストラクタ
}
```

## [単一方向のデータフロー](https://jp.vuejs.org/v2/guide/components-props.html#%E5%8D%98%E6%96%B9%E5%90%91%E3%81%AE%E3%83%87%E3%83%BC%E3%82%BF%E3%83%95%E3%83%AD%E3%83%BC)

::: note info
親のプロパティが更新されると子へと流れ落ちていきますが、逆向きにデータが流れることはありません。
これによって、子コンポーネントが誤って親の状態を変更すること(アプリのデータフローを理解しづらくすることがあります)を防ぎます。
:::

* 親から子へ渡ったデータを加工したい場合は子のコンポーネント独自のローカルデータとして処理をする必要がある。

```js
props: ['initialCounter'],
data: function () {
  return {
    counter: this.initialCounter
  }
}
```

* プロパティを未加工の値として渡す場合。 この場合、プロパティの値を使用した算出プロパティを別途定義することを推奨します

```js
props: ['size'],
computed: {
  normalizedSize: function () {
    return this.size.trim().toLowerCase()
  }
}
```

::: note alert
JavaScript のオブジェクトと配列は、参照渡しされることに注意してください。参照として渡されるため、子コンポーネント内で配列やオブジェクトを変更すると、 親の状態へと影響します。
:::

# 子コンポーネントのイベントを親がキャッチする。

## case

ブログ記事のタイトルの大きさを子コンポーネントから変更し、親へイベントを$emit(放つ)ケース。

* someイベント=$emit（`イベント名`）とすることで,子で発火したイベントを親へ放ち、子から親へ値の変化を通知する。

```js

//親コンポーネント
const App = {
  data() {
    return {
      posts: [
        /* ... */
      ],
      postFontSize: 1 //フォントサイズを定義している。
    }
  }
}

//Appコンポーネントを引数にとり、Vueインスタンスを作成
const app = Vue.component(App)


//子コンポーネント
app.component('blog-post', {
  props: ['title'],
  emits: ['enlargeText'],//親へ通知するためのイベント名を定義
  template: `
    <div class="blog-post">
      <h4>{{ title }}</h4>
      <button @click="$emit('enlargeText')">
  Enlarge text
</button>
    </div>
  `
})

```

* html
    * $emitで定義したイベント名=`親プロパティ名` somethingとし、親へ通知するエンドポイントとなる。

```html
<blog-post ... @enlarge-text="postFontSize += 0.1"></blog-post>
```

### [イベントと値を発行する](https://v3.ja.vuejs.org/guide/component-basics.html#%E3%82%A4%E3%83%98%E3%82%99%E3%83%B3%E3%83%88%E3%81%A8%E5%80%A4%E3%82%92%E7%99%BA%E8%A1%8C%E3%81%99%E3%82%8B)

* $emit( `イベント名` , `値`)とすることでイベントと値を親へ渡すことができる。
* 親は$eventで第二引数の値を受け取る事ができる。

```js
<blog-post ... @enlarge-text="postFontSize += $event"></blog-post>
```

#### イベントハンドラがメソッドの場合

```js
<blog-post ... @enlarge-text="onEnlargeText"></blog-post>


methods: {
  onEnlargeText(enlargeAmount) {
    this.postFontSize += enlargeAmount
  }
}
```

<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="KKpGyrp" data-user="Vue" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/team/Vue/pen/KKpGyrp">
  Component basics: emitting events</a> by Vue (<a href="https://codepen.io/team/Vue">@Vue</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

## [コンポーネントでv-modelを使う](https://v3.ja.vuejs.org/guide/component-basics.html#%E3%82%B3%E3%83%B3%E3%83%9B%E3%82%9A%E3%83%BC%E3%83%8D%E3%83%B3%E3%83%88%E3%81%A6%E3%82%99-v-model-%E3%82%92%E4%BD%BF%E3%81%86)


そもそものv-modelの仕組み。

```js
<input :value="searchText" @input="searchText = $event.target.value" />
```
1. @inputでinputを監視し、$event.target.valueでsearchTextプロパティに代入。
1. v-bindでvalueを束縛し双方向バインディングを実現している。

これを熟語化したのが、v-model

```js
<input v-model="searchText" />
```

コンポーネントで使用する場合は、内部的には以下のようになる。

```js
<custom-input
  :model-value="searchText"
  @update:model-value="searchText = $event"
></custom-input>
```

これをinput要素で機能させると、以下のようになる。

1. value 属性を modelValue プロパティにバインドする
2. input では、 update:modelValue イベントを新しい値と共に発行する

<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="vYeyOjJ" data-user="caleiikv" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/caleiikv/pen/vYeyOjJ">
  Untitled</a> by  (<a href="https://codepen.io/caleiikv">@caleiikv</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>