Edited at

React Native で Redux を導入する(React Native開発~第x回)


概要

React Native の開発で、Reduxを導入します。

今回は、簡単なUIで Redux のデータフローの動作を確認しましょう。



前提条件



Redux とは

Redux は、ブラウザアプリでデータを state として管理するためのフレームワークです。

これは React Native のモバイルアプリ開発でも利用できます。

導入することで、複雑なアプリのデータの流れがわかりやすくなり、メンテナンス性が高まります。

React と合わせて使われることが多いですが、それぞれ独立したフレームワークなので、React 意外と組み合わせて使うことも可能です。

Redux について理解するには、下記のページが詳ので、一読するのをオススメします。



Redux ライブラリの導入

yarn でプロジェクトにインストールします。

> yarn add redux react-redux clone

react-redux は、React と Redux を接続するためのライブラリです。

clone は、指定したオブジェクトと同じ内容の別なオブジェクトを作成する(ディープクローンする)ライブラリです。

Redux の処理に利用します。

これらの TypeScript の型定義ファイルもインストールします。

> yarn add --dev @types/redux @types/react-redux @types/clone



TSLint の導入

TypeScript はコンパイル処理で、構文エラーをチェックしてくれますが、TSLint はそれ以上の指定したコーディングルール(例えば インデントのスペースの数や {} の位置、1行の最大行数など)をチェックするツールです。

チームで開発する際、Lint を導入しておけば、ルールに則った統一されたコードが生成されやすいです。

yarn で導入します。

> yarn add --dev tslint

ルールの定義ファイル tslint.json を作成します。

> yarn tslint --init

ルールは非常に多くのオプションがあります。

上のコマンドでは、おすすめのルールがすでに適用されたものが生成されます。

ここでは、これに加えて下記のルールを追加します。


tslint.json

{

"defaultSeverity": "error",
"extends": [
"tslint:recommended"
],
"jsRules": {},
"rules": {
"quotemark": [ // 文字列リテラルのクォーテーション
true,
"single", //シングルクォーテーションを使う
"jsx-double" // JSX のプロパティでは、ダブルクォーテーション
],
"indent": [ // インデント
true,
"spaces", // スペースによるインデント
4 // スペースの数
]
},
"rulesDirectory": []
}


TSLint ルール一覧: https://palantir.github.io/tslint/rules/


Visual Studio Code の TSLint 拡張を入れておくと、コーディング中にルール違反がわかります。


各モジュールのディレクトリを作成する

Redux には、View(React Component)、Action、Reducer、State を言う要素に分けて開発します。それ毎にディレクトリを作るとソースコードの整理がしやすいです。

[project root]

`- [src]
|-[actions]
|-[reducers]
|-[states]
|-[views]
|-[components]
`-[containers]

Power Shell の New-Item コマンドを利用すると、一気に作ることができます。

> New-Item -Path "./src/actions", "./src/reducers", "./src/states/", "./src/views/components", "./src/views/containers" -itemType Directory


Redux の動作を確認してみる

簡単な例で Redux の動作を確認します。

ここでは、画面で入力した文字を State で管理し、それを画面の別なコントロールに表示する、ということをやってみます。


State の作成

まず、扱うデータの型を定義します。ここでは単純に、文字列を1つだけ扱うオブジェクトの型とします。


src/states/IAppState.ts

interface IAppState {

message: string;
}
export default IAppState;


Action の作成

アクションは、画面で入力した文字を受け取り、アクションオブジェクトを返します。

State にわたすには、dispatch を使いますがこれは、Component から送ります。


src/actions/AppAction.ts

import { Action } from 'redux';

/**
* アクションを区別するための定数
*/

export const UPDATE_MESSAGE = 'UPDATE_MESSAGE';
/**
* 変更したメッセージを Reducer に送るためのアクション
*/

export interface IUpdateMessageAction extends Action {
message: string;
}
/**
* メッセージを変更するアクションを作成する
* @param message 変更するメッセージ
*/

export const createUpdateMessageAction = (message: string): IUpdateMessageAction => {
return {
message,
type: UPDATE_MESSAGE,
};
};



Reducer の作成

Reducer は、送られた Action を使って State を更新する処理を書きます。


src/reducers/AppReducer.ts

import clone from 'clone';

import { Action, Reducer } from 'redux';

import { IUpdateMessageAction, UPDATE_MESSAGE } from '../actions/AppActions';
import IAppState from '../states/IAppState';

/**
* State の初期値
*/

const initState: IAppState = {
message: '',
};

/**
* Reducer 関数
* @param state 現在のステート
* @param action 渡されたアクション
*/

const appReducer: Reducer<IAppState> =
(state: IAppState = initState, action: Action) => {
let newState = state;
switch (action.type) {
case UPDATE_MESSAGE:
{
// ステートを変更する場合は、別のオブジェクトを作成する
newState = clone(state);
const _action = action as IUpdateMessageAction;
newState.message = _action.message;
}
break;
default:
break;
}
// ここで返すオブジェクトが前回と異なるなら、関連する Component が再描画される。
return newState;
};

export default appReducer;



Store の登録

Store は、State の管理と、Reducer による変更を受け取り、Component に伝える役割があります。

Reducer は複数ある場合が多いので、それを combineReducers でまとめて、 createStore に渡して Store を作成します。

まずは、アプリ全体の State を Interface で定義します。この中に、上で作成した State (子State) を含めます。


src/IState.ts

import IAppState from './states/IAppState';

export default interface IState {
app: IAppState;
}


Store の作成処理です。


src/Store.ts

import { combineReducers, createStore } from 'redux';

import IState from './IState';
import appReducer from './reducers/AppReducer';

const reducers = combineReducers<IState>({
app: appReducer,
});

const store = createStore(reducers);

export default store;



Container Component の作成

ようやくになりますが、画面を設計します。

画面は、React Component クラスとして定義します。

ここで作成する Component は Redux の Store と接続する特別なものとなります。これを Container といいます。

React は外部から値をプロパティとして受け取り、その値を表示したり、値によって表示条件を変えたりするなどします。

また、画面の入力やボタンをタップなどのイベントのときに、画面の値を帰る場合は Acton を生成し、 dispatch 関数で Reducer に送ります。

ここでは、仮想DOM(JSX) を書くため、拡張子を '.tsx' とする必要があります。

さきに、ルートにある App.tsx を削除し、src/views/containers/App.tsx を作成します。


src/views/containers/App.tsx

import React, { Component } from 'react';

import { StyleProp, Text, TextInput, TextStyle, View, ViewStyle } from 'react-native';
import { connect } from 'react-redux';
import { Dispatch } from 'redux';
import { createUpdateMessageAction } from '../../actions/AppActions';
import IState from '../../IState';
import IAppState from '../../states/IAppState';

/**
* UIのイベントで、Stateを変更するイベントハンドラを定義する
*/

interface IEvents {
/**
* メッセージが変更されたときの処理
* @param message 変更後のメッセージ
*/

onChangeMessage: (message: string) => void;
}

/**
* この Container のプロパティを定義する
*/

interface IProps extends IEvents, IAppState {
}
/**
* スタイルの定義
*/

const pageStyle: ViewStyle = {
alignItems: 'center',
flex: 1,
justifyContent: 'center',
};
const inputStyle: StyleProp<TextStyle> = {
borderColor: '#999',
borderRadius: 5,
borderWidth: 1,
padding: 5,
width: 200,
};
/**
* サンプル画面 コンテナ
*/

class App extends Component<IProps> {
/**
* React の画面描画イベント
*/

public render() {
return (
<View style={pageStyle}>
<Text>はじめての React Native and Redux</Text>
<TextInput
style={inputStyle}
placeholder="ここに値を入力してください"
onChangeText={this.props.onChangeMessage}>
{this.props.message}</TextInput>
<Text>{this.props.message || ('ここに入力した文字が表示されます')}</Text>
</View>
);
}
}
/**
* ステートから、プロパティに値を適用する
* @param state Redux のステート
*/

const mapStateToProps = (state: IState): IAppState => {
return {
message: state.app.message,
};
};

/**
* コンポーネントの UI イベントの処理を定義する
* @param dispatch Reducer へ Action を送るための関数
*/

const mapDispatchToProps = (dispatch: Dispatch): IEvents => {
return {
onChangeMessage: (message: string) => {
dispatch(createUpdateMessageAction(message));
},
};
};

// Redux の State と Component を接続する
export default connect(mapStateToProps, mapDispatchToProps)(App);



スタートページを作って指定する

最後に、開始するときの Component を index.js で指定します。

Container は、必ず という React-Redux モジュールが持っている コンポーネント の中で使わなくてはなりません。

ここでは下記のようにスタートページを作成します。


src/View/StartPage.tsx

import React, { Component } from 'react';

import { Provider } from 'react-redux';
import store from '../Store';
import App from './containers/App';

export default class Start extends Component {
public render() {
return (
// store プロパティには、上で作成した store オブジェクトを割り当てる
<Provider store={store}>
<App />
</Provider>
);
}
}


index.js で Start.tsx を呼び出すように修正します。


index.js

/** @format */

import { AppRegistry } from 'react-native';
import StartPage from './src/views/StartPage';
import { name as appName } from './app.json';

AppRegistry.registerComponent(appName, () => StartPage);



起動して確認してみる

VSCode デバッグ機能で、"Debug Android" を実行してみましょう。


次回

次回は、ページ遷移について書きたいと思います。