5
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

【React Native】React Redux って覚えるの面倒だけど使うしかないよね

Posted at

React Redux

前提ライブラリ

本記事は、「NativeBase」、「React Navigation」の導入を前提としています。一からプロジェクトを作成する場合には、以下の記事を参考に環境作成してください。
【React Native】NativeBase導入
【React Native】React Navigation を使ってみる(画面遷移編)

インストール

$ npm install redux --save 
$ npm install react-redux --save 

その意義 (私的な印象)

Redux は Facebookが提唱しているアーキテクチャである「Flux」の実装であり、Reactと相性が良いことから、Reactのアーキテクチャーとして語られることが多いです。
ただ、出来ることはネイティブアプリの感覚で言うとグローバル変数をちょっと綺麗に書けるライブラリ(Reactではグローバルに情報を持つのがとても難しいのです。)と言う印象で、出来ることが少ない割に言葉が難しくてハードルちょっと高めです。また、アーキテクチャーと言っても決められていることが少なく、縛りがほとんどないので、どう書いていいのかさっぱりわからなかったりします(ググってもなかなか何が正解なものやら)。そうは言ってもグローバルにステートを保持するのは、ある程度のプログラムではどうしても必要になることですので、他に選択肢も少ないこともあり、頑張って使っていきたいと思います。

出来ること

グローバルな(バケツリレーなしで)ステートを保持できる。

素のReactではコンポーネントに情報を渡す時に下記のような書式でpropsを引き渡して使うことがよくあります。階層が少なければこれでも不便でもないのですが、階層が多くなるとかちょっと面倒です(途中管理したい項目が増えたりするし)。

<Child {...props} />

それを統一的に同じ場所で管理したいと思った時に、Redux を使うと、

actions.js
export const setProfile = profile => ({  
    type: 'SET_PROFILE',
    profile: profile
});
reducer.js
import {combineReducers} from 'redux';

export const reducer = (state = {profile: ""}, action) => {  
    switch (action.type) {
      case 'SET_PROFILE':
        return {...state, profile: action.profile}
      default:
        return {...state}
    }
};

export const reducers = combineReducers({  
    user: reducer,
});

こんな感じで管理するステートを外だしに出来るので、随分とスッキリします。

上記はユーザのプロフィールをいろんな画面で表示したいというユースケースを例に、プロフィール情報をグローバルステートとして使用できるようにするものです。

冷静に「actions.js」「reducer.js」を見ると、難しいことが書いてあるような錯覚を覚えますが、実際にはactions.jsはオブジェクトを定義している純粋なJavascriptの文ですし、reducer.jsはそのオブジェクト定義を変換する純粋なアロー関数が書いてあり、それをreact-reduxに宣言(combineReducers)しているだけです。

あとは、各コンポーネントと、繋げる(connect)記述をしてあげます。(ReactのstateはJavascriptの変数ではないので、こんな感じで繋げてあげないと、コンポーネント内で使用できないのです)

const mapStateToProps = state => ({
  profile: state.user.profile,
});
  
const mapDispatchToProps = {
  setProfile
};

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

こうすることで、コンポーネント内で、

<Text>{this.props.profile}</Text>

こんな感じで表示できるようになったり、

<Button small block transparent primary onPress={() => this.props.setProfile("Taro")}>
  <Text>プロファイル設定(Taro)</Text>
</Button>

こんな感じでsetter関数を呼び出したりできます。

もちろんバケツリレーはしていません。この仕組みがよく見かける(けど全然頭に入ってこない)Reduxの動作概念の実態です。

では、そのほかのソースもちゃんと見てみましょう。

ここではReact Navigationw使用して画面遷移を行なって、グローバルに管理している「profile」を各画面で使用、操作できる様子をみてみましょう。

App.js
import React, {Component} from 'react';
import { Provider } from 'react-redux';
import { store } from './redux/store';
import HomeContainer from './Home';

export default class reduxApp extends Component {
  render() {
    return (
      <Provider store={store}>
        <HomeContainer />
      </Provider>
    );
  }
}
Home.js

import React, {Component} from 'react';
import { createStackNavigator } from 'react-navigation'
import { connect } from 'react-redux';
import { setProfile } from './redux/actions';

import { Container, View, Header, Left, Body, Right, Button, Title, Text } from 'native-base';

import Main from './screens/Main'
import Push from './screens/Push'

const MainNavigation = createStackNavigator(
  {
    Main: { screen: Main },
    Push: { screen: Push },
  },
  {initialRouteName: 'Main', mode: 'card', headerMode: 'none'}
)
const NestNavigation = createStackNavigator(
  {
    MainNavigation: { screen: MainNavigation },
  },
  {initialRouteName: 'MainNavigation', mode: 'modal', headerMode: 'none'},
)

export default class HomeContainer extends Component {
    constructor(props) {
        super(props);
        console.log(props);
    }

    setProfile = profile => {
        this.props.setProfile(profile)
        const { navigation } = this.props
        navigation.navigate('Push')
    }
    
    render() {
    const props = this.props;
        return (
          <NestNavigation />
        );
    }
}
Main.js
import React, {Component} from 'react';
import { Container, View, Header, Left, Body, Right, Button, Content, Title, Text } from 'native-base';
import { connect } from 'react-redux';
import { setProfile } from '../redux/actions';

export class Main extends Component {
  push = () => {
    const { navigation } = this.props
    navigation.navigate('Push')
  }

  render() {
    return (
      <Container>
        <Header>
          <Left />
          <Body>
            <Title>メイン</Title>
          </Body>
          <Right />
        </Header>
        <Content>
          <View style={{display: 'flex', alignItems: 'center'}}>
            <View style={{height: 50, justifyContent: 'center'}}><Text>{this.props.profile}</Text></View>
            <Button small block transparent primary onPress={() => this.props.setProfile("Taro")}>
              <Text>プロファイル設定(Taro)</Text>
            </Button>
            <Button small block transparent primary onPress={() => this.props.setProfile("Hanako")}>
              <Text>プロファイル設定(Hanako)</Text>
            </Button>
            <Button small block transparent primary onPress={this.push}>
              <Text>プッシュ表示</Text>
            </Button>
          </View>
        </Content>
      </Container>
    );
  }
}

const mapStateToProps = state => ({
  profile: state.user.profile,
});
  
const mapDispatchToProps = {
  setProfile
};

export default connect(mapStateToProps, mapDispatchToProps)(Main);
Push.js
import React, {Component} from 'react';
import { Container, Header, Left, Body, Right, View, Button, Icon, Content, Title, Text } from 'native-base';
import { connect } from 'react-redux';
import { setProfile } from '../redux/actions';

export class Push extends Component {
  render() {
    return (
      <Container>
        <Header>
          <Left>
            <Button transparent onPress={() => this.props.navigation.goBack()}>
              <Icon name='arrow-back' />
            </Button>
          </Left>
          <Body>
            <Title>プッシュ</Title>
          </Body>
          <Right />
        </Header>
        <Content>
          <View style={{display: 'flex', alignItems: 'center'}}>
            <View style={{height: 50, justifyContent: 'center'}}><Text>{this.props.profile}</Text></View>
            <Button small block transparent primary onPress={() => this.props.setProfile("Taro")}>
              <Text>プロファイル設定(Taro)</Text>
            </Button>
            <Button small block transparent primary onPress={() => this.props.setProfile("Hanako")}>
              <Text>プロファイル設定(Hanako)</Text>
            </Button>
          </View>
        </Content>
      </Container>
    );
  }
}

const mapStateToProps = state => ({
  profile: state.user.profile,
});
  
const mapDispatchToProps = {
  setProfile
};

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

これらを使うことで、

動作の様子

ios.gif

このような画面動作がスッキリと実現できます。

リポジトリ

本記事で作成したものは以下で公開していますので、参考にしてください。
https://github.com/k-neo/ReactNativeCourseRedux

5
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?