Vue NativeでNativeBaseのStyleProviderを利用する

# はじめに
[React Native](https://facebook.github.io/react-native/ "React Native · A framework for building native apps using React")のUIコンポーネントライブラリとしてよく知られている[NativeBase](https://nativebase.io/ "NativeBase | Essential cross-platform UI components for React Native")ですが、[Vue Native](https://vue-native.io/ "Vue Native")でも簡単に組み込んで利用することができます。

個人的に開発しているVue NativeアプリケーションでもNativeBaseを利用しているのですが、`StyleProvider`を用いたテーマの適用で少しハマったので、その時の解消法を紹介します！

# 環境構築
初めにNativeBaseを利用したVue Nativeアプリケーションの環境構築を行います。

### Vue Nativeプロジェクトの作成
[公式ドキュメント](https://vue-native.io/docs/installation.html "Installation — Vue Native")を参考に以下のコマンドで作成します。

```bash

$ npm install -g vue-native-cli
$ vue-native init <projectName> // Initializes crna project
$ vue-native init <projectName> --no-crna // Initializes react-native project
$ cd <project-name>
```

また、Vue Nativeプロジェクトの作成が完了したら以下のコマンドでアプリケーションを起動することができます。

```bash

$ npm start
```

### NativeBaseインストール
こちらも[公式ドキュメント](https://docs.nativebase.io/docs/GetStarted.html "Getting Started · NativeBase")を参考に以下のコマンドよりインストールを行います。

```bash

$ npm install native-base --save
```

インストール完了後、以下のようにGlobalにNativeBaseのコンポーネントを登録し、Vue Nativeアプリケーション内から利用することができます。

```javascript

import Vue from "vue-native-core";
import { VueNativeBase } from "native-base";

// registering all native-base components to the global scope of the Vue
Vue.use(VueNativeBase);
```

# 簡単なVue Nativeアプリケーションの作成
環境構築が完了したら、NativeBaseを利用した簡単なVue Nativeアプリケーションを作成します。

作成にあたっては、以下のサンプルアプリケーションを参考に行います。
https://github.com/GeekyAnts/KitchenSink-Vue-Native

```vuejs:src/boot/setup.vue

<template>
  <app-loading v-if="!isAppReady" />
  <app v-else />
</template>

<script>
import Vue from 'vue-native-core';
import { VueNativeBase } from 'native-base';
import { AppLoading } from 'expo';
import App from '../App.vue';

// ここでNativeBaseのコンポーネントをGlobalに登録
Vue.use(VueNativeBase);

export default {
  components: { App, AppLoading },
  data: function() {
    return {
      isAppReady: false,
    };
  },
  created: function() {
    this.loadFonts();
  },
  methods: {
    loadFonts: async function() {
      try {
        this.isAppReady = false;
        await Expo.Font.loadAsync({
          Roboto: require('native-base/Fonts/Roboto.ttf'),
          Roboto_medium: require('native-base/Fonts/Roboto_medium.ttf'),
          Ionicons: require('@expo/vector-icons/fonts//Ionicons.ttf'),
        });
        this.isAppReady = true;
      } catch (error) {
        console.log('some error occured', error);
        this.isAppReady = true;
      }
    },
  },
};
</script>
```

また、今回スクリーンはHomeのみ実装し、NativeBaseの`nb-text`コンポーネントを表示します。

```vuejs:src/screens/home/index.vue

<template>
  <view class="container">
    <nb-button block primary>
      <nb-text>Primary</nb-text>
    </nb-button>
  </view>
</template>

<script>
export default {
  props: {
    navigation: {
      type: Object,
    },
  },
};
</script>

<style>
.container {
  background-color: white;
  align-items: center;
  justify-content: center;
  flex: 1;
}
.text-color-primary {
  color: blue;
}
</style>
```

Simulatorでアプリケーションを起動すると、ボタンが中央に表示されたHomeスクリーンが表示されます。

<img width="50%" alt="スクリーンショット 2019-03-17 18.20.29.png" src="https://qiita-image-store.s3.amazonaws.com/0/165249/3b181775-c972-0d0d-efff-2ce1d2855d33.png">

現状ではデフォルトのデザインが適用されていますが、これからアプリケーションのテーマをカスタマイズしていきます！

# StyleProviderを利用してMaterial Designのテーマを適用
NativeBaseでは`StyleProvider`を利用してアプリケーションのテーマをカスタマイズすることが出来ます。
https://docs.nativebase.io/Customize.html#Customize

そこで今回はMaterial Designのテーマ利用してカスタマイズします。

## native-base-theme取得
カスタマイズにあたって、初めに以下のコマンドを実行し、`native-base-theme`を取得します。

```bash

# 以下のコマンドでnative-base-themeを生成
$ node node_modules/native-base/ejectTheme.js
```

するとRootディレクトリ配下に、`native-base-theme`ディレクトリが生成されます。
`native-base-theme`には`component`と`variables`ディレクトリが含まれており、今回はディレクトリを`src/theme`配下に移動させました。

```bash

# 生成したファイル群をsrc/theme配下に移動
$ mv native-base-theme src/theme
```
## StyleProviderでカスタマイズ
`native-base-theme`を取得したら、`StyleProvider`コンポーネントを利用してアプリケーションのテーマをカスタマイズします。

カスタマイズの方法は、テーマを反映させたいコンポーネントを`StyleProvider`コンポーネントでラップしてあげることで実現できます。
その際、適用したいテーマを`variables`からimportし、`theme`メソッドを介して`StyleProvider`の`style`プロパティにて指定します。

`StyleProvider`コンポーネントも`Vue.use(VueNativeBase);`により、`nb-style-provider`としてGlobalなVue.jsコンポーネントで定義されているため、以下のように実装しました。

```vuejs:src/boot/setup.vue

<template>
  <app-loading v-if="!isAppReady" />
  <!-- nb-style-providerコンポーネントを追記し、Material Designのstyleを指定 -->
  <nb-style-provider v-else :style="getTheme(material)">
    <app />
  </nb-style-provider>
</template>

<script>
// ・・・省略
// getTheme, materialをimport
import getTheme from '../theme/components';
import material from '../theme/variables/material';

// ・・・省略

export default {
  components: { App, AppLoading },
  data: function() {
    return {
      isAppReady: false,
      // getTheme, materialをそれぞれdataに追加
      getTheme: getTheme,
      material: material,
    };
  },
  // ・・・省略
};
</script>
```

## Warningが発生
上記実装を行ったところ、以下のようなWarningが発生しました。

<img width="50%" alt="スクリーンショット 2019-03-17 18.42.55.png" src="https://qiita-image-store.s3.amazonaws.com/0/165249/7cdeedee-7759-6df4-b55c-d07908f0d938.png">


> Warning: Failed prop type: Invalid prop `style` of type `array` supplied to `StyleProvider`, expected `object`.

内容としては、`StyleProvider`は`style`プロパティにObject型を期待しているようですが、`nb-style-provider`コンポーネントから渡していた`getTheme(material)`がArray型で渡されてしまっていたようです。

ただ`getTheme(material)`の返却値を確認してみてもObject型で返却されているため、内部的にVue.jsコンポーネントからReactコンポーネントへ変換される時に`style`プロパティがArray型として変換されているのかも。。。
（ここはまだちゃんと調べられていないため、間違っていたらすみません）

修正方法を探していたところ、GithubのIssueにも同様のコメントをしている方を発見。
https://github.com/GeekyAnts/KitchenSink-Vue-Native/issues/6

上記Issueのコメントを確認してみると以下のような回答がされておりました。
> You'll have to create a separate react component to use theme for now and then import that theme as root component within your vue file.

現状、`StyleProvider`を利用してテーマを適用するためにはReactコンポーネントを実装して、Vue.jsのファイルからimportして利用する必要があるようでした。

## NativeBase用のプラグインを実装
上記を踏まえて、`nb-style-provider`コンポーネントは利用せず、新たにMaterial Designのテーマを適用したReactコンポーネント（`StyleProviderMaterial`）を実装します。

ただ、他のNativeBaseコンポーネントがVue.jsのGlobalなコンポーネントとして定義されているのに対し、`StyleProviderMaterial`だけ毎回importして利用するのは面倒臭いし、違和感がある。。。

そこで、NativeBase用のプラグインを自作し、```Vue.use()```の際、Reactコンポーネント`StyleProviderMaterial`をVue.jsコンポーネント`nb-style-provider-material`としてGlobalに登録するようにしました。
Vue.jsのプラグインについては[こちら](https://jp.vuejs.org/v2/guide/plugins.html "プラグイン — Vue.js")。

```react:src/plugins/native-base.js

import React, { Component, Children } from 'react';
import { VueNativeBase, StyleProvider } from 'native-base';
import getTheme from '../theme/components';
import material from '../theme/variables/material';

// ReactコンポーネントとしてMaterial esignのテーマを指定したStyleProviderを生成
class StyleProviderMaterial extends Component {
  render() {
    const { children } = this.props;

    return (
      <StyleProvider style={getTheme(material)}>
        {Children.only(children)}
      </StyleProvider>
    );
  }
}

// Vue.jsプラグインの作成
const NativeBasePlugin = {
  install(Vue, options) {
    // ついでにVueNativeBase.install(Vue)の呼び出しもこちらで行う
    Vue.use(VueNativeBase);
    // 上記で作成したReactコンポーネントをVue.jsのglobalなコンポーネントとして設定
    Vue.component('nb-style-provider-material', StyleProviderMaterial);
  },
};

export default NativeBasePlugin;
```

## nb-style-provider-materialの利用
作成したプラグインを呼び出し、Warningが発生していた`nb-style-provider`を`nb-style-provider-material`に置き換えます。

```vuejs:src/boot/setup.vue
<template>
  <app-loading v-if="!isAppReady" />
  <!-- plugins/native-base.jsでglobalに定義したnb-style-provider-materialコンポーネント -->
  <nb-style-provider-material v-else>
    <app />
  </nb-style-provider-material>
</template>

<script>
import Vue from 'vue-native-core';
import { AppLoading } from 'expo';
import App from '../App.vue';
// getTheme, materialのimportを削除し、NativeBaseのプラグインをimport
import NativeBase from '../plugins/native-base.js';

// プラグインのNativeBase.install(Vue)を呼び出し
Vue.use(NativeBase);

export default {
  components: { App, AppLoading },
  data: function() {
    return {
      isAppReady: false,
      // getTheme, materialの定義を削除
    };
  },
  // ・・・省略
};
</script>
```

## Material Designテーマの適用
改めてSimulatorを確認したところ、Warningが解消され、Material Designのテーマが適用されていることが確認できました！

<img width="50%" alt="スクリーンショット 2019-03-17 19.04.01.png" src="https://qiita-image-store.s3.amazonaws.com/0/165249/9bdcca33-01db-825e-0368-aef1236bd0dc.png">