React NavigatorでTabにStack入れ子にしてReduxでstate管理


あまりサンプルが無いので自分用にメモ。
根本を理解していないのでズレてるかも（でも期待の動作はします）。


## 動作イメージ

下記のような感じ。Settingsで値いじったら、その他の画面にも反映・利用できるようにしたい。
ユーザープロファイルを引き回すイメージなので、name変数とそれを変更するupdateName()を引き回す。

![スクリーンショット 2018-10-25 15.32.50.png](https://qiita-image-store.s3.amazonaws.com/0/55188/938bc517-0f71-6e6d-02be-80d0dd729b0b.png)

## ディレクトリ構造

とりあえずRedux-way風。いまいち落ち着かない。

![Web Image 2018-10-25 15-35-43.png](https://qiita-image-store.s3.amazonaws.com/0/55188/49f0c6e2-1674-2c0a-9f2b-4aee0f84a0df.png)

以下、実装を見ていきます。
## 環境

expを利用しますが、create-react-native-appでも基本同じかと。

```bash
exp init demo
cd demo
npm install --save redux react-redux react-navigation
npm install
```

## 実装

### App.js

まずはApp.js。基本的な流れをわかりやすく？するために、tab, stack, storeの紐づけを書いている。

```js
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { Provider } from 'react-redux';
import { createStackNavigator, createBottomTabNavigator } from 'react-navigation';
import createStore from './createStore';

//screens
import HomeScreen from './screens/HomeScreen';
import AboutScreen from './screens/AboutScreen';
import SettingsScreen from './screens/SettingsScreen';

//store
const store = createStore();

//stack
const Stack = createStackNavigator(
  {
      Home: HomeScreen,
      About: AboutScreen,
  },
  {
      initialRouteName: 'Home',
  }
);

//tabs
const Tabs = createBottomTabNavigator(
  {
    Top: Stack,
    Settings: SettingsScreen
  }
);

//main
export default class App extends React.Component {
  render() {
    return (
      <Provider store={store}>
        <Tabs/>
      </Provider>
    );
  }
}
```

### createStore.js

今回はuser情報のみを扱うので本当はuserReducerだけあればいい。stackやnavの初期値を引き回すサンプルもあるけど、今ひとつ意味がわからないので放って置く。設定せずとも独自の値は引き回せる。

```js
import { createStore as reduxCreateStore, combineReducers, compose, applyMiddleware } from "redux";
import { stackReducer } from "./reducers/stackReducer";
import { tabReducer } from "./reducers/tabReducer";
import userReducer from "./reducers/userReducer";

export default function createStore(){
    const store = reduxCreateStore(
        combineReducers({
            stack: stackReducer,
            tab: tabReducer,
            user: userReducer
        }),
    );
    return store;
}
```

### HomeScreen.js

ただ表示しているだけ。

```js
import React from 'react';
import { Button, View, Text } from 'react-native';
import { connect } from 'react-redux';
import { updateName } from '../actions/userAction';

class HomeScreen extends React.Component {
    render() {
        // console.log(this.props);
        return (
            <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
                <Text>Home</Text>
                <Button
                    title='Link to About'
                    onPress={() => this.props.navigation.navigate('About')}
                />
                <Text>{this.props.user.name}</Text>
            </View>
        );
    }
}

const mapStateToProps = state => ({
    user: state.user
});

const mapDispatchToProps = dispatch => {
    return {
        updateName: (name) => dispatch(updateName(name))
    }
}

export default connect(mapStateToProps,mapDispatchToProps)(HomeScreen);

// export default HomeScreen;
```

### AboutScreen.js

ただ表示してるだけ。いちおうボタンつけて値（名前）をAboutでも更新できるようにしてみた。

```js
import React from 'react';
import { Button, View, Text } from 'react-native';
import { connect } from 'react-redux';
import { updateName } from '../actions/userAction';

class AboutScreen extends React.Component {
    render() {
        // console.log(this.props);
        return (
            <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
                <Text>About</Text>
                <Text>{this.props.user.name}</Text>
                <Button
                    title='名前の変更'
                    onPress={()=>this.props.updateName('yamada@about')}
                />
            </View>
        );
    }
}

const mapStateToProps = state => ({
    user: state.user
});

const mapDispatchToProps = dispatch => {
    return {
        updateName: (name) => dispatch(updateName(name))
    }
}

export default connect(mapStateToProps,mapDispatchToProps)(AboutScreen);

// export default AboutScreen;
```

### SettingsScreen.js

値の表示と変更用のボタン。

```js
import React from 'react';
import { Button, View, Text } from 'react-native';
import { connect } from 'react-redux';
import { updateName } from '../actions/userAction';

class SettingsScreen extends React.Component {
    render() {
        // console.log(this.props);
        return (
            <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
                <Text>Settings</Text>
                <Text>{this.props.user.name}</Text>
                <Button
                    title='名前の変更'
                    onPress={()=>this.props.updateName('suzuki@settings')}
                />
            </View>
        );
    }
}

const mapStateToProps = state => ({
    user: state.user
});

const mapDispatchToProps = dispatch => {
    return {
        updateName: (name) => dispatch(updateName(name))
    }
}

export default connect(mapStateToProps,mapDispatchToProps)(SettingsScreen);

// export default SettingsScreen;
```

### userReducer.js

初期値の設定と、変更するための機能を実装。

```js
// export const userReducer = (state = {}) => state;

const initialState = {
    name: 'tanaka',
    age: 44
}

const userReducer = (state = initialState, action) => {
    // console.log(action);
    switch(action.type){
        case 'UPDATE_NAME':
            const updateState = Object.assign({}, state);
            updateState.name = action.name
            return updateState;
        default:
            return state;
    }
}

export default userReducer;
```

### stackReducer.js

まんま返してるだけ。必要に応じて実装。

```js
export const stackReducer = (state = {}) => state;
```

### tabReducer.js

まんま返してるだけ。必要に応じて実装。

```js
export const stackReducer = (state = {}) => state;
```

### userAction.js

名前更新用のActionを実装。

```js
export const updateName = name => {
    return {
        type: 'UPDATE_NAME',
        name: name
    }
}
```

まあ、とりあえず動いてます。