げぇむぷろぐらみんぐ

日々の生活で得た知識、経験を書きます

ReactNative+TypeScript+Formikでフォーム画面を作る

はじめに

ReactNative上でログイン機能を実装するために、フォームを実装する必要があったのですが、Formikが想像以上に良かったので書き残します。 説明雑なので、コードを参考にする程度で読んでいただければと思います。

環境は、

  • ReactNative 0.55.3
  • Formik 1.0.0-alpha.6
  • Yup 0.24.1
  • TypeScript 2.7.2

Formikとは

Formikとは、ReactやReactNativeで使用できるフォーム管理のためのライブラリで、以下のような特徴があります

  • バリデーションが簡単にできる
  • 送信ボタンの制御も簡単にできる
  • フォームのStateを簡単に受け取れる
  • State管理のライブラリに依存しない

使い方

普通にComponentとして書く場合と、HOCとして自分で定義したFormのViewを包んでやる方法があります。
今回は、HOCを利用する方法で書きました。こちらのほうがViewとフォームの制御に関しての記述が分かれてわかりやすいと思いました。
実装は、Using Formik with TypeScriptを参考にしました。
以下が実装したコード全体です。

import { FormikProps, withFormik } from "formik"
import * as React from "react"
import { StyleSheet, View, ViewStyle } from "react-native"
import {
  Button,
  FormInput,
  FormLabel,
  FormValidationMessage,
} from "react-native-elements"
import Yup from "yup"
import { login } from "../actions/auth"

interface FormValues {
  email: string
  password: string
  url: string
}

interface OtherProps {
  message: string
}

const InnerForm = (props: OtherProps & FormikProps<FormValues>) => (
  <View>
    <FormLabel>SiteURL</FormLabel>
    <FormInput
      onChangeText={text => props.setFieldValue("url", text)}
      value={props.values.url}
    />
    <SwitchFormValidationMessage
      text={props.errors.url}
      submitCount={props.submitCount}
    />

    <FormLabel>Email</FormLabel>
    <FormInput
      onChangeText={text => props.setFieldValue("email", text)}
      value={props.values.email}
    />
    <SwitchFormValidationMessage
      text={props.errors.email}
      submitCount={props.submitCount}
    />

    <FormLabel>Password</FormLabel>
    <FormInput
      onChangeText={text => props.setFieldValue("password", text)}
      value={props.values.password}
      secureTextEntry={true}
    />
    <SwitchFormValidationMessage
      text={props.errors.password}
      submitCount={props.submitCount}
    />
    <Button
      onPress={props.submitForm}
      title={props.message}
      buttonStyle={styles.loginButton}
    />
  </View>
)

interface SwitchFormProps {
  text: any
  submitCount: number
}

const SwitchFormValidationMessage = (props: SwitchFormProps) => {
  if (props.submitCount > 0) {
    return <FormValidationMessage>{props.text}</FormValidationMessage>
  }
  return <FormValidationMessage />
}

interface LoginFromProps {
  message: string
}

const LoginForm = withFormik<LoginFromProps, FormValues>({
  mapPropsToValues: () => {
    return {
      email: "",
      password: "",
      url: "",
    }
  },
  validationSchema: Yup.object().shape({
    email: Yup.string()
      .email("Please input Email Address")
      .required("Email Address is required!"),
    password: Yup.string()
      .max(36, "Please enter password in 36 characters or less")
      .required("Password is required!"),
    url: Yup.string()
      .url("Please input your site url")
      .required("URL is required!"),
  }),
  handleSubmit: values => {
    login(values.email, values.password, values.url)
      .then(token => console.log(token))
      .catch(error => console.log(error))
  },
})(InnerForm)

interface Style {
  loginButton: ViewStyle
}

const styles = StyleSheet.create<Style>({
  loginButton: {
    backgroundColor: "#0086F6",
    borderRadius: 2,
    marginTop: 20,
  },
})

export default LoginForm

コードの説明

まず、withFormikで包むためのフォームのViewを定義したコンポーネントを作ります。今回で言えば、以下の部分です。

ここでは、Form画面をどのように構成するかを記述します。 今回はReactNativeElementsというUIコンポーネントを利用して、画面を構成しています。

const InnerForm = (props: OtherProps & FormikProps<FormValues>) => (
  <View>
    <FormLabel>SiteURL</FormLabel>
    <FormInput
      onChangeText={text => props.setFieldValue("url", text)}
      value={props.values.url}
    />
    <SwitchFormValidationMessage
      text={props.errors.url}
      submitCount={props.submitCount}
    />

    <FormLabel>Email</FormLabel>
    <FormInput
      onChangeText={text => props.setFieldValue("email", text)}
      value={props.values.email}
    />
    <SwitchFormValidationMessage
      text={props.errors.email}
      submitCount={props.submitCount}
    />

    <FormLabel>Password</FormLabel>
    <FormInput
      onChangeText={text => props.setFieldValue("password", text)}
      value={props.values.password}
      secureTextEntry={true}
    />
    <SwitchFormValidationMessage
      text={props.errors.password}
      submitCount={props.submitCount}
    />
    <Button
      onPress={props.submitForm}
      title={props.message}
      buttonStyle={styles.loginButton}
    />
  </View>
)

また、TypeScriptを使用しているので予めFormValuesというinterfaceを定義して、その中にFormで入力する要素を定義しています。
その他、渡したいデータに関してはOtherPropsに定義します。

interface FormValues {
  email: string
  password: string
  url: string
}

interface OtherProps {
  message: string
}

ここで、一点注意が必要なのが、送信ボタンのonPressに渡しているメソッドについてです。
Formik公式のドキュメントを見ると、props.handleSubmitを渡していますが、ReactNativeの場合props.handleSubmitではonPressに渡すのに型が合わないので、今回はprops.submitFormを渡します。
こちらに関しては、公式でIssueが立てられており、そこの回答を見るとひとまずsubmitFormで対応してくれとのことでした。

次に、ロジックの部分に関して説明します。 こちらは、withFormikで自分で定義したFormコンポーネントを包み、そのFormコンポーネントに対して振る舞いを追加する感じで実装をします。 具体的なコードは以下の部分です。

const LoginForm = withFormik<LoginFromProps, FormValues>({
  mapPropsToValues: () => {
    return {
      email: "",
      password: "",
      url: "",
    }
  },
  validationSchema: Yup.object().shape({
    email: Yup.string()
      .email("Please input Email Address")
      .required("Email Address is required!"),
    password: Yup.string()
      .max(36, "Please enter password in 36 characters or less")
      .required("Password is required!"),
    url: Yup.string()
      .url("Please input your site url")
      .required("URL is required!"),
  }),
  handleSubmit: values => {
    login(values.email, values.password, values.url)
      .then(token => console.log(token))
      .catch(error => console.log(error))
  },
})(InnerForm)

mapPropsToValuesで、各フォームの値の初期値を設定しています。
validationSchemaでは、Yupというバリデーションライブラリを用いて、バリデーションを行っています。 FormikとYupの相性は非常によく、公式でもYupを使うことが推奨されており、コードを見ていただくとわかるように非常に直感的にバリデーションが書けます。
validationSchemeでバリデーションを定義しておくことで、バリデーションエラーの場合はsubmitが発火しないようになります。 最後に、handleSubmitに送信ボタンが押されたときの挙動を書きます。

まとめ

Formikは公式ドキュメントもしっかりしており、便利でしたが意外と記事が少なかったので書いてみました。 TypeScriptでの実装例として参考にしていただければと思います。

ReactNativeでTypeScript+Prettier+TSlintな開発環境構築手順

はじめに

定期的にReactNativeでネイティブアプリを作りたくなる時期がくるのですが、そのたびに環境構築に手間取るので、備忘録として残しておきます。

その設定おかしいよとかあれば教えてください。

導入手順

前提

  1. nodeやwatchmanやyarnは導入済み(ない人はbrewで入れてください)
  2. エディタはVisualStudioCode(WebStormとかでも動くとは思います)

ReactNativeの導入

yarn global add react-native-cli

上記のコマンドで、ReactNativeプロジェクトを作成するための準備が整います。

プロジェクト作成

react-native init testApp

TypeScript導入

yarn add --dev typescript

型の導入

yarn add --dev @types/react @types/react-native

型は自分の使いたいライブラリによって随時導入が必要です。基本は、上記のように@types/ライブラリ名でインストールできますが、マイナーなライブラリは型定義がない場合もあります。

tsconfig.jsonの編集

TypeScriptの設定ファイルであるtsconfig.jsonを編集します。 各オプションは、各々でカスタマイズしてください。 例として、僕のtsconfig.jsonを載せます。

{
  "compilerOptions": {
    "target": "es2017",
    "module": "es2015",
    "jsx": "react-native",
    "outDir": "out",
    "rootDir": "src",
    "allowJs": false,
    "allowSyntheticDefaultImports": true,
    "noImplicitAny": false,
    "noImplicitReturns": true,
    "noUnusedParameters": true,
    "noUnusedLocals": true,
    "preserveConstEnums": true,
    "sourceMap": true,
    "lib": ["dom", "es2015", "es2016"],
    "experimentalDecorators": true
  },
  "exclude": ["android", "ios", "out", "node_modules"],
  "compileOnSave": true
}

Prettierの導入

Prettierはコードフォーマッターで、フロントエンドで使われる言語は全般対応しており、便利なやつです。
雑にコードを書いても保存時にフォーマットの設定をしておけばフォーマットしてくれるのですごくいいです。 チーム開発でも、フォーマットが揃ってすごくいいと思います。

yarn add --dev prettier 

.prettierrcの作成

prettierの設定ファイルです。プロジェクトのルートディレクトリに置けば、ここで設定したとおりに整形してくれます。 なくても動くと思います。

{
  "singleQuote": false,
  "trailingComma": "es5",
  "semi": false
}

TSlintの導入

TSlintは、TypeScript用のlinterです。linterは、コードを監視してだめな書き方してる場合とかに教えてくれるやつです。
なくてもいいですがあると捗ります。特に初心者の場合はこれを入れておくことでだめな書き方に気づけるので絶対いれたほうがいいです。

yarn add --dev tslint tslint-react

TSlintとPrettierを併用するためのプラグイン導入

今回は、PrettierとTSlintが競合した場合に、Prettierを優先するようにします。

yarn add prettier --dev tslint-config-prettier  tslint-plugin-prettier

tslint.jsonの作成

さっきインストールしたプラグインたちを有効にするために、tslint.jsonを作成して以下のように記述します。 rulesは細かく設定をできるので、興味のある人は調べてみてください。

{
  "extends": ["tslint-react", "tslint-config-prettier"],
  "rulesDirectory": ["tslint-plugin-prettier"],
  "rules": {
    "prettier": true,
  }
}

VSCode上の設定

まず、拡張機能のTSLintとPrettierをインストールしてください。 また、VSCodeの設定からsettings.jsonを編集します。

{
    "editor.formatOnType": true,
    "editor.formatOnSave": true
}

これらの設定をすることで、VSCode上でコーディングをしながらtslintやprettierが自動で走るようになります。

作成したプロジェクトを編集してみる

srcディレクトリを作成し、App.jsをその下に移動、さらに拡張子を.tsxに変更します。 そして、以下のようにファイルを編集しました。
styleとかに型がついて、flexとかfontSizeの補完が効くようになります。便利。

import React, { Component } from "react"
import {
  Platform,
  StyleSheet,
  Text,
  TextStyle,
  View,
  ViewStyle,
} from "react-native"

const instructions: string = Platform.select({
  ios: "Press Cmd+R to reload,\n" + "Cmd+D or shake for dev menu",
  android:
    "Double tap R on your keyboard to reload,\n" +
    "Shake or press menu button for dev menu",
})

interface Props {}
export default class App extends Component<Props> {
  render() {
    return (
      <View style={styles.container}>
        <Text style={styles.welcome}>Welcome to React Native!</Text>
        <Text style={styles.instructions}>To get started, edit App.js</Text>
        <Text style={styles.instructions}>{instructions}</Text>
      </View>
    )
  }
}

interface Style {
  container: ViewStyle
  welcome: TextStyle
  instructions: TextStyle
}

const styles = StyleSheet.create<Style>({
  container: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
    backgroundColor: "#F5FCFF",
  },
  welcome: {
    fontSize: 20,
    textAlign: "center",
    margin: 10,
  },
  instructions: {
    textAlign: "center",
    color: "#333333",
    marginBottom: 5,
  },
})

コンパイルした結果は、outディレクトリに出力されるので、index.jsはoutディレクトリ以下を見に行くように変更します。

import { AppRegistry } from "react-native"
import App from "./out/App"

AppRegistry.registerComponent("sampleApp", () => App)

TypeScriptファイルをコンパイル

tsc

基本は上記のコマンドでいいですが、一々コンパイルするのも面倒なので、package.jsonに以下のように定義しておくと便利です。

{
    "start": "tsc --watch & node node_modules/react-native/local-cli/cli.js start"
}

こうすると、

yarn start

上記のコマンドで監視を開始すれば、エディタで保存するたびにTypeScriptからコンパイルが走ります。

まとめ

ここまで環境を整えれば、あとは非常に快適に開発ができると思います。
ReactNative + TypeScriptの知見が日本語であんまりないように感じるので、もっと増えるとうれしいです。

参考

ReactNativeでミュージックストリーミングサーバーkoelのネイティブアプリ作ってみた

ネタがなかったので、ゲーム関係ないですが、NITMic Advent Calendar 2017 16日目の記事です。
一応うちの部活はゲーム開発を行うだけの部活ではない!(らしい)ので、セーフです。

今までゲームばっかり作ってきていたのですが、最近はあるきっかけから割りとゲーム開発以外もやってみたいなあという思いが一段と強くなり、その流れからReactNativeでネイティブアプリを作ってみました。

今回は、開発環境と、使ったコンポーネントについてだけ書きます。

アプリ開発は初なので温かい目で見て下さい。
完成形はこんな感じです。 個人用なのでUIとか知りません。
iphoneのミュージックアプリを参考に作りました。
とりあえず、目標としていたバックグラウンド再生、シャッフル再生、プレイリストごとの再生はできたので満足です!

f:id:siguma_sig:20171215213921g:plain

ミュージックストリーミングサーバー koel

以前、こちらでも紹介したのですが、OSSな個人用ミュージックストリーミングサーバーを簡単に立てられるのがkoelです。

フロントがVue.js、サーバーサイドがLaravelで書かれており、公式サイトにも書いてありますが割りとモダンな感じです。
OSSなVue.jsのアプリではかなり有名な方らしく、Vue.jsの実装の参考にもされるらしいです。

僕は作業中はずっと音楽を流しているのですが、色んな端末に音源を入れるのが面倒なので使っています。
ですが、公式でiOS/Androidのネイティブアプリが存在しなかったため、今回作ってみようと思いました。

技術選定

サーバーサイドに関しては、API一覧などは存在しないので、頑張ってLaravelの実装を読んでWebアプリで使われているAPIを叩くだけです。

クライアントサイドに関して、初めにSwiftをこの機会に学んでみようかとも思いましたが、直近でReactを使ってSPAな自分用ツールを作っていたので、ReactNativeで行こうと思いました。
また、今回は自分用なのでiOSのみで良いのですが、クロスプラットフォームなところにも惹かれました。

開発環境

初めは、create-react-native-appを用いて開発をはじめました。
Expoというものを利用していて、QRコードで実機での動作確認がすぐできて便利でした。
がしかし、こちらはネイティブプラグインが基本的には利用できず、ネイティブAPIはExpo側で用意されたものだけしか使えません。
音楽再生のネイティブAPIも提供されていたのですが、こちらはバックグラウンド再生ができなかったので泣く泣く諦めました。

結局、react-native-cliからプロジェクトを作り直して開発を行いました。

エディタは、WebStormを使いました。学生のうちに使ってみたかっただけで、基本的にはVSCodeで良さそうでした。
今回は、TypeScriptやFlow、ESlintなどは利用せず開発を行いました。理由は導入が大変そうだったからです。
ただ、このアプリの開発が終わったあとにESlintを入れてみたら大変便利で、非常に後悔しているので、ESlintは少なくとも入れたほうが良さそうです。
Reactのお作法の勉強にもなります。

便利だったコンポーネント紹介

react-native-video
これがなければ作れなかった。
videoとついてるが、audioのみも再生できる。
他にも、サウンド再生系のコンポーネントはいくつかあるが、他はほぼメンテされていなかったりしたのでこれにした。
ボリュームをいじったり、再生時間をいじったりもできる。

redux
言わずもがな
Fluxの概念を拡張して、stateを管理するためのフレームワーク
僕のような素人でも書き方が大体決まっているので、コードがぐっちゃぐちゃにはなりづらかった。

redux-actions
reduxのactionCreatorを決まった形式で簡単に作れる。
実装方法を縛ることで複数人で書くときも書き方が統一されて良さそう。

export const increment = createAction(COUNTER_INCREMENT);

こんな感じで、incrementのactionが作れる。 引数はpayloadに乗っかる感じ。

redux-saga
非同期処理で大変お世話になった。
初めは、redux-thunkを使っていたけど、APIを叩いたりの非同期処理がactionCreator側に有るのがどうにも気持ち悪かったのを同期に相談したら教えてもらった。
redux-sagaで非同期処理と戦うがとっても参考になる。

下のようにAPIを叩いてPromiseを返すメソッドを用意して、

saga側で呼び出して、返ってきた値を用いてactionをdispatchということができる。

導入の際に覚えることがいくつかあるけど、それに見合ったメリットはあると思った。

react-native-router-flux
Webフロントのreact-routerライクに画面遷移が書ける。
初めは、react-navigationを使ってみたが、よくわからなかったので、使いやすそうなこちらにした。
ただ、中ではreact-navigationが動いてるらしい。
react-native-navigationというのもあるらしいが、使わなかった。遷移のアニメーションが豊富らしい?

他には、lodashとかreact-native-vector-iconsとかも使った。

まとめ

初コミットから見ると、約2週間くらいで完成しました。
ネイティブアプリ開発の経験もなく、Reactのアプリ開発経験もほぼほぼないと言っていいくらいだったけどこんな感じなので、割りと取っ付きやすいのかなあと思いました。
とはいえ、パフォーマンスを気にしたりし始めるとまだまだ学ぶことは多そうです。
アプリをリリースするときは、そういうところもきちんとしないといけないと思うし、UI/UXの勉強も必要だなあと思いました。

とはいえ、普段のゲーム開発とは全く違うことをするのも楽しかったです。
こんな感じで就職するまでは、フラフラ色んな技術に浮気しつつ、ゲームも遊びつつ、ゲーム開発の知識も増やしていきたいです。

研究頑張ります…。

コンピュータ倶楽部NITMicの活動

NITMic Advent Calendar 2017 3日目担当のsigumaです。

今年からAdvent CalendarをやってみようとOBで勝手に盛り上がり、勝手に作りました。
1日目のマホウさんも言っていますが、NITMicのエンジニアだけでなく他の役職であるコンポーザー、デザイナーの方々も参加できるように、QiitaではなくAdventerでやっています。

今日はそんなコンピュータ倶楽部NITMicの活動について書こうと思います。

続きを読む

OSSなミュージックストリーミングサーバー、koelの導入と躓いたところ

最近、VPSをKagoyaからVultrにお引越ししました。
そのついでに、ミュージックストリーミングサーバーも変更しようと思いました。
色々調べた結果koelというOSSのミュージックストリーミングサーバーが非常に魅力的だったので、こちらに乗り換えた話と、その際に躓いた点を書きます。

続きを読む

VPSを借りてみてやったこと

先月半ば頃にVPSなるものを借りてみた。

僕はサーバーサイドもインフラの知識もほぼ無いのだが、ずっと前から興味はあった。

だけど、去年の同じくらいの時期に何故かポートフォリオサイトを作り、その際にレンタルサーバーを1年契約で借りていたのと、VPSレンタルサーバーより高いんだぞ〜という情報から月数千円くらいするんだろうなあという勝手な想像で、VPSは借りていなかった。

しかし、先月にレンタルサーバーの契約がそろそろ切れそうだったので、VPSについて調べてみると、思っていたより数倍安いではないか!と気づいた。なんなら、僕が借りていたレンタルサーバーより安いではないか!と。

それで、さらにVPSについて調べていると、レンタルサーバーより色々できて楽しそうだったので、そのまま流れで借りることにした。

で、借りてから半月くらいで色々したので、つらつらと書き残そうと思った。

続きを読む

逆求人のすゝめ

早いもので、もう19卒のインターン申込みが続々と始まりつつあります。

最近、うちの部活の後輩たちもインターンに向けて動き始めていて、逆求人について聞かれることも多いので、まとめておこうかと思います。

僕が去年参加した逆求人イベントは全てサポーターズさんによるものだったので、ここではサポーターズさんに絞って説明しますが、他のイベントでもほぼ同様な結果が得られるとは思います。

それ書いてもらっちゃまずい!みたいなのあったらサポーターズさん、教えてください。

続きを読む