概要
React Native の開発で、Reduxを導入します。
今回は、簡単なUIで Redux のデータフローの動作を確認しましょう。
前提条件
-
JavaScript が読み書きできる。
-
React Native の開発準備が整っていること。
-
TypeScript でプロジェクトが作成されている。
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
ルールは非常に多くのオプションがあります。
上のコマンドでは、おすすめのルールがすでに適用されたものが生成されます。
ここでは、これに加えて下記のルールを追加します。
{
"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つだけ扱うオブジェクトの型とします。
interface IAppState {
message: string;
}
export default IAppState;
Action の作成
アクションは、画面で入力した文字を受け取り、アクションオブジェクトを返します。
State にわたすには、dispatch を使いますがこれは、Component から送ります。
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 を更新する処理を書きます。
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) を含めます。
import IAppState from './states/IAppState';
export default interface IState {
app: IAppState;
}
Store の作成処理です。
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
を作成します。
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 モジュールが持っている コンポーネント の中で使わなくてはなりません。
ここでは下記のようにスタートページを作成します。
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 を呼び出すように修正します。
/** @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" を実行してみましょう。
次回
次回は、ページ遷移について書きたいと思います。