redux/examples/real-world を見て学ぶ
GitHub - reactjs/redux: Predictable state container for JavaScript apps
Githubのアカウント名を入力すると、 starしているリポジトリと作者の一覧が表示される。
一覧表示のリポジトリ名やアカウント名をクリックすると、それぞれ対応したページに切り替わる
ページ遷移
redux-router を活用してアカウント、リポジトリをURLで表現することで、ページの遷移を実現している。
以下のコードで、アカウント名を入力してgoボタンを押すと、"/アカウント名" に遷移する処理を実現。
// container/App.jsから対象部分を抜粋 import { browserHistory } from 'react-router' import Explore from '../components/Explore' ... class App extends Component { ... handleChange(nextValue) { browserHistory.push(`/${nextValue}`) }, ... render() { const { children, inputValue } = this.props return ( <div> <Explore value={inputValue} onChange={this.handleChange} /> <hr /> {this.renderErrorMessage()} {children} </div> ) } ... }
// container/Explore.js内 export default class Explore extends Component { ... handleGoClick() { this.props.onChange(this.getInputValue()) }, ... }
ページ遷移後のデータ取得
処理の流れは下記のようなイメージ。
- URLからアカウント名 or リポジトリ名を取得して、github apiをcallするアクションを実行
- api callを処理するmiddlewareでアクションを type:REQUEST, type:SUCCESS or FAILURE に分割したアクションを生成
- SUCCESSを処理するreducerからstateを生成
URLからアカウント名 or リポジトリ名を取得して、github apiをcallするアクションを実行
redux-routerの機能で、URLに応じて、読み込むコンポーネントを切り分けて、さらにURLからコンポーネントへ渡すPropsを定義している。
// ./routes.js export default ( <Route path="/" component={App}> <Route path="/:login/:name" component={RepoPage} /> <Route path="/:login" component={UserPage} /> </Route> )
この記述により、例えばUserPageの場合、mapStateToProps内で、 アカウント名を取得することが可能
// containers/UserPage.js ... function mapStateToProps(state, ownProps) { ... const login = ownProps.params.login.toLowerCase() // アカウント名を取得 ... }
UserPage#componentWillMount でアカウント名、Github APIからデータを取得するアクションをdispatchする
// containers/UserPage.js ... import { loadUser, loadStarred } from '../actions' ... function loadData(props) { const { login } = props props.loadUser(login, [ 'name' ]) props.loadStarred(login) } ... class UserPage extends Component { ... componentWillMount() { loadData(this.props) } ... } ... export default connect(mapStateToProps, { loadUser, loadStarred })(UserPage)
CALL API アクションのdispatch
上記のUserPageコンポーネントでimportされる loadUser の定義.
// actions/index.js ... function fetchUser(login) { return { [CALL_API]: { types: [ USER_REQUEST, USER_SUCCESS, USER_FAILURE ], endpoint: `users/${login}`, schema: Schemas.USER } } } // Fetches a single user from Github API unless it is cached. // Relies on Redux Thunk middleware. export function loadUser(login, requiredFields = []) { return (dispatch, getState) => { const user = getState().entities.users[login] if (user && requiredFields.every(key => user.hasOwnProperty(key))) { return null } return dispatch(fetchUser(login)) } } ...
fetchUserで定義されているフォーマットは、
GitHub - agraboso/redux-api-middleware: Redux middleware for calling an API.
で定義されているAPI CALLを行うアクションのフォーマット。
アクションを処理するmiddleware
storeにmiddlewareを設定することで、特定のフォーマットのアクションへの処理を追加することができる。
例えば
GitHub - agraboso/redux-api-middleware: Redux middleware for calling an API.
を導入すると、上記のAPI CALL フォーマットのアクションを適切に処理してくれるようになる。
今回のexampleでは.middleware/api.js に自前で実装されている。
やっていることは
- actionが対象のフォーマットかどうかを確認. 対象でなければ何もしないで終了
- REQUEST typeのアクションを実行
- api call後にSUCCESS or FAILURE typeのアクションをレスポンスをくっつけて実行
感想
stateの持ち方はかなりシンプルにして、middlewareやcomponentのcomponentWillMount, componentWillReceivePropsを活用することで、複雑な実装をクリアにしている感じが参考になった。