NativeScript-Vueのコンポネート親子関係（props、$emit）【コピペできるコード付き】

# 注意

本記事の公開日は2021年となっておりますが、コードや記事のベースを書いたのは2020年です。
（コードはすぐ用意できたのに、記事の方は書いたりやめたり、なんやかんやで一年以上かかってしまった理由で）
もったいないので供養の意味を兼ねて投稿しました。

そのため、記述は Vue 2.x と NativeScript 6.x を前提にしています。
最新バージョンでは通用しない可能性があります。


# はじめに
NativeScript-VueはVue.jsとほぼ変わらないでコンポネートを扱うことができます。
しかしながら公式にその記述がない[^1]し、使おうとしてパニックになり使えないまま諦めてしまう[^2]こともあったので、
「NativeScript-Vueでも同じ使い方できるから安心して」と誰かに教えてもらいたかったため、執筆しました。

内容は大したことなくて、Vue.jsに慣れている方ならすでに分かっていると思います。
繰り返しますが、本記事はあくまで、
「NativeScript-VueもVue.jsと同じ使い方できる」のを伝いたいだけです。

対象読者は、以下のどちらに当てはまる方になると思います。

- コンポネートや親子関係の概念が分かっている
- Vue.jsでのprops、$emitは分かるが、NativeScript-Vueはどんな感じかを見たい
- コンポネートや親子関係はよく分かってないけど、概念を読むより、コピペして実際に動かしてみたい

今回は実装重視なので、概念のことは一切触れません。
コードは全公開します。
⇨ [コード全文](#完成形)

# とりあえず画面

#### 起動画面
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/243366/6e0329e5-5b3b-32bd-8d45-a2c37e7cbad8.png" width=50%>
非常にシンプルです。
ヘッダーに「Home」、画面上部にタイトルの「Title」と、下部に
一列のボタン「First」「Second」「Third」があります

#### ボタンタップ
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/243366/162ea853-ff48-e9b4-813d-c3c76a49c810.png" width=33%><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/243366/e714bd6b-7b64-5187-f488-4cef340e32fc.png" width=33%><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/243366/f78663f8-06ec-d0e1-edbf-70fc0402a1be.png" width=33%>
タイトルの文字がタップされたボタンのテキストに変わります。

# 今回のファイル構造
```
app
├── app.css
├── app.js
└── components
    ├── ParentPage.vue
    └── elements
        └── ButtonOfChangingTitle.vue 
```

`ParentPage`が唯一のページで、
`elements`のディレクトリの下には`ButtonOfChangingTitle`という子コンポネートがあります。

流れ的には、ページ（親）からボタン（子）へ文字列を渡します。
ボタンは親からの文字列を表示します。
タップされた時に、受け取った文字列を親に返して、親がそれを自身（ページ）のタイトルにします。

ちょっとバカバカしい設計になっています。
同じデータが一周回ってますからね。
そこは大目に見ていただければ助かります。

# 事前準備

以上のファイル構造を用意できたら、二つのVueファイルに以下のように書き換えてください。
（実際に動かさない方はここをスキップしてください）

```ParentPage.vue
<template>
    <Page>
        <ActionBar title="Home" />
        <Label text="Title" row="0" class="title" />
        <GridLayout rows="*, auto">
            <!-- この後書き換えるよ -->
        </GridLayout>
    </Page>
</template>

<script>
    export default {
        // 後に追加
    };
</script>

<style scoped>
    .title {
        font-size: 40;
        horizontal-align: center;
    }
</style>
```

```ButtonOfChangingTitle.vue
<template>
    <!-- この後書き換えるよ -->
</template>

<script>
    export default {
        // 後に追加
    };
</script>

<style scoped>
    .custom-button {
        background-color: #1a1f54;
        color: #FFF;
        margin: 20;
    }
</style>
```

Vueファイルのフォーマット、cssクラスと最小限のHTMLを定義しました。
この後のコードを適切な場所を入れると同じ動きになります。
最後には全体コードを置いてあるので、不安のある方を照らし合わせながらお進みください。


# 親から子のデータ渡し（props）

[props](https://jp.vuejs.org/v2/guide/components-props.html)は親が[子コンポーネントへのデータの受け渡し](https://jp.vuejs.org/v2/guide/components.html#%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%97%E3%81%9F%E5%AD%90%E3%82%B3%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%8D%E3%83%B3%E3%83%88%E3%81%B8%E3%81%AE%E3%83%87%E3%83%BC%E3%82%BF%E3%81%AE%E5%8F%97%E3%81%91%E6%B8%A1%E3%81%97)に使われます。
propsを通じて、
親からボタンに表示する文字列（=後に返してくる文字列）を渡します。

### 子コンポネート
まず、子コンポネート`ButtonOfChangingTitle`でpropsの設定をします。

#### JavaScript
```ButtonOfChangingTitle.vue
<script>
    export default {
        props: {
            text: String
        },
    };
</script>
```

props:{}の中で、受け取りたいデータのプロパティ名を指定します。
propsのプロパティの定義については[Vue.js公式](https://jp.vuejs.org/v2/guide/components-props.html#%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3%E3%81%AE%E3%83%90%E3%83%AA%E3%83%87%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3)で詳しく紹介されています。
今回は`text`というString型を受け取ります。

#### HTML
```ButtonOfChangingTitle.vue
<template>
    <Button :text="text" class="custom-button" />
</template>
```

ボタンの文字列に、propsで受け取る`text`にバインディングします。
その結果、ボタンには親が指定した文字列が表示されます。
見やすくためにcss classを入れました。挙動には影響がありません。

### 親コンポネート
子コンポネートが用意できたので、今度を親コンポネートの方を実装します。

##### JavaScript
```ParentPage.vue
<script>
    // 使いたいコンポネートをまずimport
    import ButtonCT from "./elements/ButtonOfChangingTitle";
    export default {
        // そしてコンポネートとして登録する
        components: {
            ButtonCT
        },

        data() {
            return {
                title: "Title"
            };
        },
    };
</script>
```

さて、ここは一旦データ渡しから離れて、コンポネートの登録をします。
まず、スクリプトをimportします。絶対パスも相対パスも使えます。
そして、componentsの中に入れて、登録します。
そうすると、HTML部分で通常のコンポネートと同じ使い方ができます。

下の`data()`には、タイトルにバインディングするための`title`を定義します。
デフォルトの値は`"Title"`です。

##### HTML
```ParentPage.vue
    <!-- タイトル表示 -->
    <Label :text="title" row="0" class="title" />
```
まず、`<Label />`の`text="Title"`を`:text="title"`に書き換えてください。
`:`を付けたことによって、文字列の"Title"ではなく、動的に変数の`title`を表示することに変わります。
この変数`title`は、ついさっき`data()`に設定した`title`のことです。


```ParentPage.vue
    <GridLayout row="1" columns="*, *, *">

        <!-- 子コンポーネントを読み込む -->
        <ButtonCT text="First"  col="0" />
        <ButtonCT text="Second" col="1" />
        <ButtonCT text="Third"  col="2" />

    </GridLayout>
```
次に、`<!-- この後書き換えるよ -->`の部分をこのように書き換えてください。

JavaScript部分で`ButtonCT`を定義したので、HTMLで`<ButtonCT />`が使えるようになりました。
プロパティ`text`で表示する文字列（=後に返してくる文字列）を指定します。
この`text`が子コンポネートのpropsで定義した`text`と紐付けます。

（`GridLayout `と`col`はUI表示用。挙動に影響ありません）

# 子から親のデータ渡し（$emit）

今度はボタンがタップされた時に、
親に自身の文字列（=propsで受け取った文字列）を渡して、親が表示しているタイトルを変更させます。
[$emit](https://jp.vuejs.org/v2/api/index.html#vm-emit)を使うことによって、子が親のメソッドを呼ぶことが可能になり、その際引数にデータを渡すこともできます。

### 親コンポネート
今度は親コンポネートの方から実装していきます。

##### JavaScript
```ParentPage.vue
<script>
    export default {
        // componentsを省略

        // data()を省略

        methods: {
            changeText(result) {
                this.title = result;
            }
        }
    };
</script>
```

methods:{}を追加し、中に`changeText`メソッドを作成します。
引数の`result`を変数`title`に代入するメソッドです。
（`title`は先ほど作成したdata()の中で定義されています）

##### HTML
```ParentPage.vue
    <GridLayout row="1" columns="*, *, *">
        <ButtonCT text="First"  @changeText="changeText" col="0" />
        <ButtonCT text="Second" @changeText="changeText" col="1" />
        <ButtonCT text="Third"  @changeText="changeText" col="2" />
    </GridLayout>
```
先ほど作成した`<ButtonCT />`に`@changeText="changeText"`を追加します。
`changeText`に統一しましたが、別に同じ名前じゃなくても問題ありません。
前半の`@changeText`は子コンポーネント内に使う名前で、後半の`"changeText"`は親コンポーネント内に使う名前です。
子コンポーネント`<ButtonCT />`に`changeText`メソッドを渡したことによって、
子コンポーネントから呼べるようになります。

### 子コンポネート
親コンポーネントの準備が終わったので、いよいよ子コンポネートで呼んでみます。

#### JavaScript

```ButtonOfChangingTitle.vue
<script>
    export default {
        // propsを省略

        methods: {
            changeText() {
                this.$emit("changeText", this.text);
            }
        }
    };
</script>
```
methods:{}を追加し、親コンポーネントと同様、中に`changeText`メソッドを作成します。
これは親コンポーネントの`changeText`メソッドを実行するためのメソッドです。
`this.$emit()`の第一引数に呼ぶ関数、第二引数に親に渡す文字列（今回の場合はpropsで定義した`text`）を設定します。


# 完成形
全部合わせるとこんな感じになります。

```ParentPage.vue
<template>
    <Page>
        <ActionBar title="Home" />
        <GridLayout rows="*, auto">
            <Label :text="title" row="0" class="title" />
            <GridLayout row="1" columns="*, *, *">
                <ButtonCT text="First"  @changeText="changeText" col="0" />
                <ButtonCT text="Second" @changeText="changeText" col="1" />
                <ButtonCT text="Third"  @changeText="changeText" col="2" />
            </GridLayout>
        </GridLayout>
    </Page>
</template>

<script>
    import ButtonCT from "./elements/ButtonOfChangingTitle";
    export default {
        components: {
            ButtonCT
        },

        data() {
            return {
                title: "Title"
            };
        },

        methods: {
            changeText(result) {
                this.title = result;
            }
        }
    };
</script>

<style scoped>
    .title {
        font-size: 40;
        horizontal-align: center;
    }
</style>
```

```ButtonOfChangingTitle.vue
<template>
    <Button :text="text" @tap="changeText" class="custom-button" />
</template>

<script>
    export default {
        props: {
            text: String
        },

        methods: {
            changeText() {
                this.$emit("changeText", this.text);
            }
        }
    };
</script>

<style scoped>
    .custom-button {
        background-color: #1a1f54;
        color: #FFF;
        margin: 20;
    }
</style>
```

# さいごに

$refsについて言及していませんでしたが、NativeScriptでの使い方もまた一緒です。

# 参考資料

emit
https://forum.vuejs.org/t/passing-data-back-to-parent/1201
prop(Typescript)
https://www.nativescript.org/blog/nativescript-vue-with-class-components


[^1]: $navigateToで[画面遷移時のprops渡し](https://nativescript-vue.org/en/docs/routing/manual-routing/#passing-props-to-the-target-component)ならありますが。
[^2]: 今考えてみれば、パニックによる凡ミスだと思います。