10
7

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 NativeAdvent Calendar 2015

Day 10

React Native - Redux (part2 - 非同期アプリRedditAPIApp)

Last updated at Posted at 2015-12-09

本日も昨日に引き続き、React Native + Reduxの説明をします。

Reduxの公式ドキュメントのAdvancedのセクションにRedditAPIを用いた非同期のサンプルがあります。これをReact Nativeで実装していみたいと思います。

スクリーンショット 2015-12-09 20.59.14.png

ソースコードはgithubに載せてあります。

Directory structure

昨日のtodoAppのと違って、configureStore.jsを作る必要があります。

index.ios.js
app/
├── actions.js
├── components
│   └── Posts.js
├── configureStore.js
├── containers
│   ├── AsyncApp.js
│   └── Root.js
└── reducers.js

コード解説

index.ios.js

index.ios.jsは登録処理のみ。

import React from 'react-native';
import Root from './app/containers/Root';

let { AppRegistry } = React;

let ReactNativeReduxRedditAPIApp = React.createClass({
  render: function() {
    return(
      <Root />
    );
  }
});

AppRegistry.registerComponent('ReactNativeReduxRedditAPIApp', () => ReactNativeReduxRedditAPIApp);

app/containers/Root.js

トップレイヤーのRoot.jsではStoreは設定して、AsyncAppコンポーネントを呼び出します。

import React from 'react-native'
import { Provider } from 'react-redux/native'
import configureStore from '../configureStore'
import AsyncApp from './AsyncApp'

const store = configureStore()
var Root = React.createClass({
  render() {
    return (
      <Provider store={store}>
        {() => <AsyncApp />}
      </Provider>
    )
  }
});

export default Root;

app/configureStore.js

configureStoreではMiddlewareを設定します。作者のDan氏もあるpodcastで話してましたが、マーケティングのためにReduxは最小のコード(確か70行ぐらいだったような)のみで、その他の必要なものはmiddlewareとして提供されます。そのため非同期を実現するためにはredux-thunkというmiddlewareが必要になります。redux-loggerはstateの変化をSTDOUTに表示してくれます。これも便利なmiddlewareです。

import { createStore, applyMiddleware } from 'redux'
import thunkMiddleware from 'redux-thunk'
import createLogger from 'redux-logger'
import rootReducer from './reducers'

const loggerMiddleware = createLogger()

const createStoreWithMiddleware = applyMiddleware(
  thunkMiddleware,
  loggerMiddleware
)(createStore)

export default function configureStore(initialState) {
  return createStoreWithMiddleware(rootReducer, initialState)
}

app/containers/AsyncApp.js

AsyncAppはメインなコンポーネントでActionをdispatchで実行します。connectを用いてstoreをpropで読めるようにします。

import React, {ScrollView, Text, PropTypes, PickerIOS} from 'react-native';
import { connect } from 'react-redux/native'
import { selectReddit, fetchPostsIfNeeded, invalidateReddit } from '../actions'
import Posts from '../components/Posts'
var Button = require('react-native-button');

var {
  AppRegistry,
  StyleSheet,
  View,
} = React;

var AsyncApp = React.createClass({

  componentDidMount() {
    const { dispatch, selectedReddit } = this.props
    dispatch(fetchPostsIfNeeded(selectedReddit))
  },

  componentWillReceiveProps(nextProps) {
    if (nextProps.selectedReddit !== this.props.selectedReddit) {
      const { dispatch, selectedReddit } = nextProps
      dispatch(fetchPostsIfNeeded(selectedReddit))
    }
  },
...
function mapStateToProps(state) {
  const { selectedReddit, postsByReddit } = state
  const {
    isFetching,
    lastUpdated,
    items: posts
  } = postsByReddit[selectedReddit] || {
    isFetching: true,
    items: []
  }

  return {
    selectedReddit,
    posts,
    isFetching,
    lastUpdated
  }
}

export default connect(mapStateToProps)(AsyncApp)

app/actions.js

actionでは非同期を行うためにdispatchを返す形で書きます(redux-thunkを利用)。fetchPostsが非同期の箇所になります。つまり、actionが非同期を書く場所になります。ReducersはtodoAppと変わらなくシンプルなコードになります。

function receivePosts(reddit, json) {
  return {
    type: RECEIVE_POSTS,
    reddit,
    posts: json.data.children.map(child => child.data),
    receivedAt: Date.now()
  }
}

function fetchPosts(reddit) {
  return dispatch => {
    dispatch(requestPosts(reddit))
    return fetch(`http://www.reddit.com/r/${reddit}.json`)
      .then(req => req.json())
      .then(json => dispatch(receivePosts(reddit, json)))
  }
}

function shouldFetchPosts(state, reddit) {
  const posts = state.postsByReddit[reddit]
  if (!posts) {
    return true
  } else if (posts.isFetching) {
    return false
  } else {
    return posts.didInvalidate
  }
}

export function fetchPostsIfNeeded(reddit) {
  return (dispatch, getState) => {
    if (shouldFetchPosts(getState(), reddit)) {
      return dispatch(fetchPosts(reddit))
    }
  }
}

Summary

以上でReduxにあるExampleを実装してみました。React Nativeでも非同期が問題なく書けることがわかります。気をつけるところは、asyncなどのBabelでtransformする箇所がactionで動かない場合があるように思いました。(深くは調査していませんが。

10
7
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
10
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?