Skip to content

Instantly share code, notes, and snippets.

@fumiyasac
Last active January 22, 2019 05:03
Show Gist options
  • Save fumiyasac/54f5233904bde5e7cac4140d2a828cef to your computer and use it in GitHub Desktop.
Save fumiyasac/54f5233904bde5e7cac4140d2a828cef to your computer and use it in GitHub Desktop.
ReactNative+Redux+NativeBaseでつくるサンプル実装をのぞく ref: https://qiita.com/fumiyasac@github/items/e27a5901dde1dbcb2086
import React, { Component } from 'react';
// React-Reduxのインポート宣言 → ProviderタグでラップすることでReactコンポーネント内でStoreにアクセスできるようにする
import { Provider } from 'react-redux';
// createStore, applyMiddlewareのインポート宣言
import { createStore, applyMiddleware } from 'redux';
// redux-thunkのインポート宣言
import ReduxThunk from 'redux-thunk';
// redux-loggerのインポート宣言
import logger from 'redux-logger';
// react-native-router-fluxのインポート宣言(Router, Sceneを使用)
import { Router, Scene } from 'react-native-router-flux';
// reducerのインポート宣言
import reducers from './redux/reducers';
// configのインポート宣言 ※config/index.jsを作成し下記を定義(Firebaseから持ってくる値)
/**
* (補足)
* ※下記のようにご自身のFirebaseの設定を行なってください
* export const API_KEY = '●●●';
* export const AUTH_DOMAIN = '●●●';
* export const DATABASE_URL = '●●●;
* export const STORAGE_BUCKET = '●●●';
* export const MESSAGING_SENDER_ID = '●●●';
*/
import {
API_KEY,
AUTH_DOMAIN,
DATABASE_URL,
STORAGE_BUCKET,
MESSAGING_SENDER_ID
} from './config';
// 各画面表示用のそれぞれのコンポーネント
import TutorialContents from './components/contents/TutorialContents';
import AuthContents from './components/contents/AuthContents';
import GlobalTabContents from './components/contents/GlobalTabContents';
import RecordRequestContents from './components/contents/RecordRequestContents';
import SettingContents from './components/contents/SettingContents';
import WebViewContents from './components/contents/WebViewContents';
export default class App extends React.Component {
// - Component Life Cycles
// コンストラクタ
/**
* (補足)
* v0.54.2ではFirebaseの読み込みに関する処理は`componentWillMount()`で行なっていたが、v0.57.8ではコンストラクタで行う。 ※Firebaseのrequireに関する処理もこの中で実行する。
*/
constructor(props) {
super(props)
// firebaseのインポート宣言を行う
const firebase = require("firebase");
// firebaseのセッティング情報を記載する
// ※API情報に関してFirebaseコンソールを取得 → Authentication → 「ログイン方法」でメール/パスワードを有効にする
const config = {
apiKey: API_KEY,
authDomain: AUTH_DOMAIN,
databaseURL: DATABASE_URL,
storageBucket: STORAGE_BUCKET,
messagingSenderId: MESSAGING_SENDER_ID
};
// firebaseを有効にする
firebase.initializeApp(config);
}
// - Rendering Components
render() {
// Redux本来のdispatch処理が実行される前にMiddlewareの処理を実行する
const store = createStore(reducers, {}, applyMiddleware(ReduxThunk, logger));
return (
<Provider store={store}>
<Router>
{/*
                        (補足)
ライブラリ「React Native Router Flux」記載箇所の変更点
                         `root`となるタグ部分にも`hideNavBar={true}`の記載を追加する
※ このサンプルではヘッダーのナビゲーション部分は各画面ごとにヘッダー部品を適用
*/}
<Scene key="root" hideNavBar={true}>
<Scene key="auth">
<Scene key="TutorialContents" initial={true} component={TutorialContents} hideNavBar={true} />
<Scene key="AuthContents" component={AuthContents} hideNavBar={true} />
</Scene>
<Scene key="main">
<Scene key="GlobalTabContents" initial={true} component={GlobalTabContents} hideNavBar={true} />
<Scene key="RecordRequestContents" component={RecordRequestContents} hideNavBar={true} />
<Scene key="SettingContents" component={SettingContents} hideNavBar={true} />
<Scene key="WebViewContents" component={WebViewContents} hideNavBar={true} />
</Scene>
</Scene>
</Router>
</Provider>
);
}
}
import React, { Component } from 'react';
import {
Form
} from 'native-base';
・・・(省略)・・・
// 認証コンテンツ用の共通コンポーネント
import AuthFormInputMail from './AuthFormInputMail';
import AuthFormInputPassword from './AuthFormInputPassword';
import AuthFormErrorMessage from './AuthFormErrorMessage';
・・・(フォーム用の部品に関するComponentを予め切り出しておきインポートする)・・・
class AuthForm extends React.Component {
// - Functions
// メールアドレスの入力値変更時の処理
_onMailChange = (text) => {
// TODO: 入力したメールアドレスを一時的に保存するstateの変更を行う
};
・・・(フォーム用の部品の動きと連動して実行する処理があれば記載する)・・・
// - Rendering Components
render() {
return (
<Form>
<AuthFormInputMail value={this.props.mail} onChangeText={ (mail) => this._onMailChange(mail) } />
{/*
// TODO: このComponentのStateの変化に応じてボタンの状態やエラーメッセージの状態を反映する
*/}
</Form>
);
};
}
import React, { Component } from 'react';
import {
StyleSheet
} from 'react-native';
import {
Item,
Input,
Label
} from 'native-base';
export default class AuthFormInputMail extends React.Component {
// - Rendering Components
render() {
const { value, onChangeText } = this.props;
return (
<Item stackedLabel>
<Label style={styles.title}>メールアドレス:</Label>
<Input
style={styles.input}
value={value}
placeholder="例)[email protected]"
onChangeText={onChangeText}
/>
</Item>
);
};
}
// - Component Styles
const styles = StyleSheet.create({
title: {
fontSize: 13,
fontWeight: "bold",
color: "#333333"
},
input: {
fontSize: 13,
color: "#666666"
}
});
1. 下記のような画面のルーティングをApp.jsなどに定義する
<Router>
<Scene key="root">
<Scene key="main">
<Scene key="ComponetA" component={ComponetA} initial={true} />
<Scene key="ComponetB" component={ComponetB} />
</Scene>
</Scene>
</Router>
※ 一番最初に表示されるのはComponetAとなる。
2. 遷移元からComponentBへ進む
・遷移先のComponentへ送る値がある場合 → Actions.ComponetB({ id: item.id, title: item.title, content: item.content });
・遷移先のComponentへ送る値がない場合 → Actions.ComponetB();
※ 送った値は該当のComponentBのthis.propsに格納された状態になる
3. ComponentBから遷移元へ戻る
Actions.pop();
import React, { Component } from 'react';
import {
StyleSheet
} from 'react-native';
import {
Header,
Left,
Right,
Body,
Title,
Button,
Icon
} from 'native-base';
export default class GlobalHeader extends React.Component {
// - Rendering Components
render() {
const { title, onPressMenuButton, onPressSettingButton } = this.props;
return (
<Header iosBarStyle="light-content" style={styles.headerBackgroundColor} hasTabs>
<Left>
<Button transparent onPress={onPressMenuButton}>
<Icon style={styles.buttonColor} name="menu" />
</Button>
</Left>
<Body>
<Title style={styles.titleColor}>{title}</Title>
</Body>
<Right>
<Button transparent onPress={onPressSettingButton}>
<Icon style={styles.buttonColor} name="settings" />
</Button>
</Right>
</Header>
);
};
}
// - Component Styles
const styles = StyleSheet.create({
headerBackgroundColor: {
backgroundColor: '#4169e1'
},
titleColor: {
color: '#ffffff'
},
buttonColor: {
color: '#ffffff'
}
});
import React, { Component } from 'react';
import {
Platform,
StyleSheet
} from 'react-native';
import {
Button,
Icon,
Text
} from 'native-base';
export default class GlobalTab extends React.Component {
// - Rendering Components
render() {
const { selected, icon, title } = this.props;
let deviceStyle = (Platform.OS === "ios") ? iosStyles : androidStyles;
return (
<Button vertical onPress={this.props.onPress}>
<Icon style={selected ? deviceStyle.tabSelectedColor : deviceStyle.tabDefaultColor} name={icon} />
<Text style={selected ? deviceStyle.tabSelectedColor : deviceStyle.tabDefaultColor}>{title}</Text>
</Button>
);
};
}
// - Component Styles
const iosStyles = StyleSheet.create({
tabDefaultColor: {
color: '#999999',
},
tabSelectedColor: {
color: '#4169e1'
}
});
const androidStyles = StyleSheet.create({
tabDefaultColor: {
color: '#c6c6c6',
},
tabSelectedColor: {
color: '#ffffff'
}
});
・・・(省略)・・・
import GlobalFooter from '../common/GlobalFooter';
import GlobalTab from '../common/GlobalTab';
・・・(省略)・・・
// タブ表示用の要素
let screenItems = [
// タブと連動した部分
{ screen: "feed", title: "フィード", icon: "list" },
{ screen: "diary", title: "勉強日記", icon: "clipboard" },
・・・(他にもフッターに並べたいものがあれば同様に追加していく)・・・
];
export default class GlobalTabContents extends React.Component {
// - Component Life Cycles
// コンストラクタ
constructor(props) {
super(props);
// ステートの初期化を行う(ドロワーに関する設定と現在選択されている画面に関する設定)
this.state = { isDrawerOpen: false, isDrawerDisabled: false, selectedIndex: 0 };
};
// - Functions
・・・(省略)・・・
// タブのボタンを表示する
_showGlobalTabs = () => {
return screenItems.map( (tabBarItem, index) => {
return (
<GlobalTab
key={index}
selected={this.state.selectedIndex === index}
title={tabBarItem.title}
icon={tabBarItem.icon}
onPress={ () => this._onPressGlobalTab(index) } />
);
});
};
// タブのボタンを押下した際の処理
_onPressGlobalTab = (index) => {
this.setState({ selectedIndex: index });
};
・・・(省略)・・・
// - Rendering Components
render() {
const { selectedIndex, isDrawerDisabled } = this.state;
return (
{/* 必要なボタン要素を配置する関数を渡す */}
<GlobalFooter tabs={this._showGlobalTabs()} />
);
};
}
// combineReducersの処理を行うためのインポート宣言
import { combineReducers } from 'redux';
//各々の要素に関するReducerのインポート宣言
// → AuthReducer: 認証処理に関するState処理に関するReducer
// → NewsReducer: 最新のお知らせの取得処理に関するState処理に関するReducer
// → FeedReducer: フィード情報の取得処理に関するState処理に関するReducer
// → RecordReducer: 記録データ情報の取得処理に関するState処理に関するReducer
// → RecordFormReducer: 記録データのフォーム処理に関するState処理に関するReducer
import AuthReducer from './AuthReducer';
import NewsReducer from './NewsReducer';
import FeedReducer from './FeedReducer';
import RecordReducer from './RecordReducer';
import RecordFormReducer from './RecordFormReducer';
// それぞれのReducerを集約して一つのStateとして管理する
export default combineReducers({
auth: AuthReducer,
feed: FeedReducer,
news: NewsReducer,
records: RecordReducer,
recordForm: RecordFormReducer
});
// 最新のお知らせ情報のAction定義のインポート宣言
import {
NEWS_FETCH,
NEWS_FETCH_SUCCESS,
NEWS_FETCH_FAILURE
} from '../actions/types';
// 初期状態のステート定義(オブジェクトの形にする)
const initialState = { newsList: null, error: '', loading: false };
// MARK: - Functions
// 選択されたケースを元にstateの更新を行うメソッド
export default (state = initialState, action) => {
switch (action.type) {
case NEWS_FETCH:
return { ...state, ...initialState, loading: true };
case NEWS_FETCH_SUCCESS:
return { ...state, ...initialState, newsList: action.payload };
case NEWS_FETCH_FAILURE:
return { ...state, error: '新着お知らせの取得に失敗しました。', loading: false };
default:
return state;
}
};
import React, { Component } from 'react';
import {
StyleSheet
} from 'react-native';
import {
Content
} from 'native-base';
// connectのインポート宣言を行う
import { connect } from 'react-redux';
// ActionCreator(Actionの寄せ集め)のインポート宣言(this.props.この中に定義したメソッド名の形で実行)
import {
getAllNews
} from '../../../../../redux/actions';
// 新着のお知らせコンテンツ用の共通コンポーネント
import NewsListItem from './NewsListItem';
import NewsListLoading from './NewsListLoading';
import NewsListError from './NewsListError';
class NewsList extends React.Component {
// - Component Life Cycles
// コンポーネントの内容がMountされる前に行う処理
componentWillMount() {
this.props.getAllNews();
}
// - Functions
// 再度取得する処理
_onPressRetryButton = () => {
this.props.getAllNews();
};
// 取得した最新のお知らせを表示する処理
_displayNewsList = () => {
const { newsList, error, loading } = this.props;
// 最新のお知らせが取得中の場合における表示
if (loading) {
return (
<NewsListLoading />
);
}
// 最新のお知らせが取得失敗の場合における表示
if (error !== '') {
return (
<NewsListError
error={error}
onPressRetryButton={ () => this._onPressRetryButton() } />
);
}
// 最新のお知らせ情報を配列に格納し直す
var newsItems = [];
for (let index in newsList) {
newsItems.push(newsList[index]);
}
// 最新のお知らせのリストを生成して表示する
return newsItems.map( (item, index) => {
return (
<NewsListItem key={index} item={item} />
);
});
};
// MARK: - Rendering Components
render() {
return (
<Content style={styles.container}>
{this._displayNewsList()}
</Content>
);
};
}
// - Component Styles
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#ffffff'
}
});
// newのStateから値を取得してthis.propsにセットする ※取得したいStateについては上記のReducerを参照
const mapStateToProps = ({ news }) => {
// 引数で受け取った認証データを変数に分解する
const { newsList, error, loading } = news;
// 分解したそれぞれの値をオブジェクトにして返却する
return { newsList, error, loading };
};
// インポート可能にする宣言
export default connect(mapStateToProps, { getAllNews })(NewsList);
"jest": {
"preset": "react-native",
"transformIgnorePatterns": [
"/node_modules/(?!native-base|react-native-router-flux)/"
]
}
import React, { Component } from 'react';
import {
StyleSheet
} from 'react-native';
import {
Content
} from 'native-base';
// 設定コンテンツ用の共通コンポーネント
import SettingListItem from './SettingListItem';
export default class SettingList extends React.Component {
// MARK: - Functions
// 取得した設定コンテンツ用データを表示する処理
_displaySettingList = () => {
const { settingItems } = this.props;
// 設定コンテンツ用データのリストを生成して表示する
{/*
(補足) 設定コンテンツのリストの形式はこんな感じになっている
let settingItems = {
"items": [
{ title: "Twitter", onPressListItem: () => _goTwitterPage() },
{ title: "Facebook", onPressListItem: () => _goFacebookPage() },
{ title: "Github", onPressListItem: () => _goGithubPage() },
{ title: "Slideshare", onPressListItem: () => _goSlidesharePage() },
{ title: "Qiita", onPressListItem: () => _goQiitaPage()) },
]
};
*/}
return settingItems.items.map( (item, index) => {
return (
<SettingListItem
key={index}
title={item.title}
onPressListItem={item.onPressListItem} />
);
});
};
// - Rendering Components
render() {
const { settingItems } = this.props;
return (
<Content style={styles.container}>
              {this._displaySettingList()}
</Content>
);
};
}
// - Component Styles
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#ffffff'
}
});
import React, { Component } from 'react';
import {
StyleSheet
} from 'react-native';
import {
Text,
Icon,
ListItem
} from 'native-base';
export default class SettingListItem extends React.Component {
// - Functions
// それぞれのソーシャルリンクに合わせた色を設定する
_getSocialMediaColorCode = (title) => {
switch (title) {
case "Twitter":
return colorStyles.twitterColor;
case "Facebook":
return colorStyles.facebookColor;
case "Github":
return colorStyles.githubColor;
case "Slideshare":
return colorStyles.slideshareColor;
case "Qiita":
return colorStyles.qiitaColor;
default:
return colorStyles.defaultColor;
}
};
// - Rendering Components
render() {
const { title, onPressListItem } = this.props;
return (
<ListItem style={styles.listItem} onPress={onPressListItem}>
<Icon style={[styles.listItemIcon, this._getSocialMediaColorCode(title)]} name="paper-plane" />
<Text style={styles.listItemText}>{title}</Text>
</ListItem>
);
};
}
// MARK: - Component Styles
const styles = StyleSheet.create({
listItem: {
height: 50
},
listItemIcon: {
fontSize: 16,
},
listItemText: {
fontSize: 13,
paddingLeft: 8,
fontWeight: 'normal',
color: '#555555'
}
});
const colorStyles = StyleSheet.create({
twitterColor: {
color: '#00aced',
},
facebookColor: {
color: '#3b5998',
},
githubColor: {
color: '#333333',
},
slideshareColor: {
color: '#0077b5',
},
qiitaColor: {
color: '#55c500',
},
defaultColor: {
color: '#888888',
}
});
・・・(その他の発行したいアクションについても同様に定義をしています)・・・
// 新着お知らせの取得&表示処理に関するActionの定義
export const NEWS_FETCH = 'news_fetch';
export const NEWS_FETCH_SUCCESS = 'news_fetch_success';
export const NEWS_FETCH_FAILURE = 'news_fetch_failure';
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment