ややめも

アプリ作りたい女子大学院生のめも💁‍♀️

技術メモ
日記
つくったもの
就活の話

ReactNative+Typescript+Reduxの構成でアプリ作成

ReactNative + Typescript + Reduxでアプリ開発をしていて、
あんまりまとまってる記事がなく、自分自身がひっかかったので、まとめました。
今回はシンプルにTextInputで文字を入力してFlatListで表示するところまでを実装しています。(最後にGithubも)
間違いやアドバイスがあれば遠慮なくコメントください!

ちなみに私の事前知識は
ReactNative/React/Reduxをインターン先などで少しだけかじったことがある程度です。


先日、ReactNativeを用いて作ったアプリをリリースしました。
順番をきめてくれたり、ランダムで1つを選んでくれるアプリです。

play.google.com

順番決め

順番決め

  • Satoshi Kobayashi
  • ユーティリティ
  • 無料

環境設定

qiita.com

こちらの方の記事の、環境構築の部分までを参考にさせていただきました。

パッケージのインストール

$ yarn add react-redux redux remote-redux-devtools

$ yarn add --save-dev @types/react @types/react-native @types/react-redux @types/remote-redux-devtools tslint tslint-config-prettier tslint-plugin-prettier tslint-react typescript

私はprettierとtslintを使用しているのでこれらに関するパッケージもインストールしていて、

作成したアプリの配下に

  • .prettierrc
  • tsconfig.json
  • tslint.json

を置いています。

フォルダ構成

 ├ android/
 ├ ios/
 ├ src/
 │ └ actions/
 │   └ types.ts
 │   └ index.ts
 │ └ components/
 │   └ Items.tsx
 │   └ TextForm.tsx
 │ └ containers/
 │   └ ItemsContainer.ts
 │   └ TextFormContainer.ts
 │ └ reducers/
 │   └ index.ts
 │   └ items.ts
 │ └ screens/
 │   └ App.tsx
 │ └ store/
 │   └ index.ts
 │
 ├ index.js
 └ (略)

もともと作成したフォルダ配下にあったApp.jsをApp.tsxに変更し、screensに置いています。

それに伴って、index.jsを以下のように変更しました。

// index.js

/** @format */

import { AppRegistry } from 'react-native'
import App from './src/screens/App'
import { name as appName } from './app.json'

AppRegistry.registerComponent(appName, () => App)


私はcomponentを作ってからactionやreducerを作成し、
最後にcontainerにてconnectを使ってcomponentにstateやactionを渡しています。

actions

//actions/index.ts

import { ADD_ITEM, EDITING_ITEM } from './types'

let nextItemId: number = 0

export const addItem = () => {
  return {
    type: ADD_ITEM,
    id: nextItemId++
  }
}

export const editingItem = (text: string) => {
  return {
    type: EDITING_ITEM,
    editing_title: text
  }
}
//actions/types.ts

export const ADD_ITEM = 'ADD_ITEM'
export const EDITING_ITEM = 'EDITING_ITEM'

reducers

//reducers/index.ts

import { combineReducers } from 'redux'
import items from './items'

const sampleApp = combineReducers({
  items
})

export default sampleApp
//reducers/items.ts

import { ADD_ITEM, EDITING_ITEM } from '../actions/types'

const initialState: {
  editing_title: string
  items: { id: number; title: string }[]
} = { editing_title: '', items: [] }

export const items = (state = initialState, action: any): any => {
  switch (action.type) {
    case ADD_ITEM:
      return {
        ...state,
        items: [
          ...state.items,
          {
            id: action.id,
            title: state.editing_title
          }
        ]
      }
    case EDITING_ITEM:
      return {
        ...state,
        editing_title: action.editing_title
      }
    default:
      return state
  }
}

export default items

components

//components/Items.tsx

import React, { Component } from 'react'
import { Text, FlatList } from 'react-native'

interface Props {
  items: { id: number; title: string }[]
}

export default class Items extends Component<Props> {
  render() {
    let items = this.props.items

    return (
      <FlatList
        data={items}
        renderItem={({ item }) => <Text>{item.title}</Text>}
        keyExtractor={item => `${item.id}`}
      />
    )
  }
}

keyExtractorはstring型じゃないとエラーがでたので,idを文字列に変換しています。

//components/TextForm.tsx

import React, { Component } from 'react'
import {
  TextInput,
  View,
  Text,
  StyleSheet,
  TouchableOpacity,
  ViewStyle,
  TextStyle
} from 'react-native'

interface Props {
  addItem: () => void
  editingItem: (text: string) => void
  items: { id: number; title: string }[]
}

export default class TextForm extends Component<Props> {
  render() {
    return (
      <View style={styles.container}>
        <TextInput
          style={styles.textInput}
          onChangeText={text => this.props.editingItem(text)}
        />
        <TouchableOpacity
          style={styles.button}
          onPress={() => this.props.addItem()}
        >
          <Text style={styles.buttonText}>追加</Text>
        </TouchableOpacity>
      </View>
    )
  }
}

interface Style {
  container: ViewStyle
  textInput: ViewStyle
  button: ViewStyle
  buttonText: TextStyle
}

const styles = StyleSheet.create<Style>({
  container: {
    flexDirection: 'row',
    padding: 20
  },
  textInput: {
    flex: 3,
    backgroundColor: '#FFF',
    marginRight: 5
  },
  button: {
    flex: 1,
    backgroundColor: '#F58E7E',
    marginLeft: 5,
    alignItems: 'center',
    justifyContent: 'center',
    paddingTop: 10,
    paddingBottom: 10
  },
  buttonText: {
    color: '#FFF',
    fontWeight: 'bold'
  }
})

ここは入力されたテキストが変わるたびにstateに保存して、
ボタンが押されたらその中身をitemsに追加するようにしてあります。

containers

//containers/ItemsContainer.ts

 import Items from '../components/Items'
import { connect } from 'react-redux'

const mapStateToProps = (state: any) => {
  return { items: state.items.items }
}

export default connect(mapStateToProps)(Items)
//containers/TextFormContainer.ts

import TextForm from '../components/TextForm'
import { addItem, editingItem } from '../actions'
import { connect } from 'react-redux'

const mapStateToProps = (state: any) => {
  return { items: state.items }
}

const mapDispatchToProps = (dispatch: any) => {
  return {
    addItem: () => {
      dispatch(addItem())
    },
    editingItem: (text: string) => {
      dispatch(editingItem(text))
    }
  }
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(TextForm)

screens

//screens/App.tsx

import React from 'react'
import { Provider } from 'react-redux'
import store from '../store'
import { StyleSheet, ViewStyle, SafeAreaView, View } from 'react-native'
import TextFormContainer from '../containers/TextFormContainer'
import ItemsContainer from '../containers/ItemsContainer'

interface Props {}
interface State {}

export default class Main extends React.Component<Props, State> {
  render() {
    return (
      <Provider store={store}>
        <SafeAreaView style={{ flex: 1 }}>
          <View style={styles.container}>
            <TextFormContainer />
            <ItemsContainer />
          </View>
        </SafeAreaView>
      </Provider>
    )
  }
}

interface Style {
  container: ViewStyle
}

const styles = StyleSheet.create<Style>({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#CCCCCC'
  }
})

store

//store/index.ts

import reducers from '../reducers'
import { createStore } from 'redux'
import devToolsEnhancer from 'remote-redux-devtools'

const store = createStore(reducers, devToolsEnhancer())

export default store

ここでremote-redux-devtoolsを使えるように設定。

ここまでやると、見た目はこんな感じ。
f:id:yaya-w-1026:20181030012815p:plain

ちゃんと動くかをReduxDevToolsを使って確認する。

Redux DevToolsの導入

https://github.com/zalmoxisus/remote-redux-devtools


この拡張をいれる。

chrome.google.com

Chromeの拡張はこれです。

シュミレーター上で⌘+DでDevug JS Remotelyで開いたブラウザ上にて右クリック。
Redux DevTools > Open Remote Dev Tools

で、stateやdiffを確認することができる。

f:id:yaya-w-1026:20181030012812p:plain


ちゃんと入力できてる〜!

ちなみに私はReact.jsを使うときにredux-devtools-extensionというパッケージをインストールしていたので、
ReactNativeでも同じだろうと思っていて使おうとしたところ、うまく使えませんでした…。
ReactNativeのときはremote-redux-devtoolsを使うっぽい。

サンプルコード

github.com

参考になったサイト

www.udemy.com