ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
# 示例:Reddit API 这是一个[高级教程](#)的例子,包含使用 Reddit API 请求文章标题的全部源码。 ### 入口 #### `index.js` ~~~ import 'babel-core/polyfill'; import React from 'react'; import Root from './containers/Root'; React.render( <Root />, document.getElementById('root') ); ~~~ ### Action Creators and Constants #### `actions.js` ~~~ import fetch from 'isomorphic-fetch'; export const REQUEST_POSTS = 'REQUEST_POSTS'; export const RECEIVE_POSTS = 'RECEIVE_POSTS'; export const SELECT_REDDIT = 'SELECT_REDDIT'; export const INVALIDATE_REDDIT = 'INVALIDATE_REDDIT'; export function selectReddit(reddit) { return { type: SELECT_REDDIT, reddit }; } export function invalidateReddit(reddit) { return { type: INVALIDATE_REDDIT, reddit }; } function requestPosts(reddit) { return { type: REQUEST_POSTS, reddit }; } function receivePosts(reddit, json) { return { type: RECEIVE_POSTS, reddit: 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)); } }; } ~~~ ### Reducers #### `reducers.js` ~~~ import { combineReducers } from 'redux'; import { SELECT_REDDIT, INVALIDATE_REDDIT, REQUEST_POSTS, RECEIVE_POSTS } from '../actions'; function selectedReddit(state = 'reactjs', action) { switch (action.type) { case SELECT_REDDIT: return action.reddit; default: return state; } } function posts(state = { isFetching: false, didInvalidate: false, items: [] }, action) { switch (action.type) { case INVALIDATE_REDDIT: return Object.assign({}, state, { didInvalidate: true }); case REQUEST_POSTS: return Object.assign({}, state, { isFetching: true, didInvalidate: false }); case RECEIVE_POSTS: return Object.assign({}, state, { isFetching: false, didInvalidate: false, items: action.posts, lastUpdated: action.receivedAt }); default: return state; } } function postsByReddit(state = { }, action) { switch (action.type) { case INVALIDATE_REDDIT: case RECEIVE_POSTS: case REQUEST_POSTS: return Object.assign({}, state, { [action.reddit]: posts(state[action.reddit], action) }); default: return state; } } const rootReducer = combineReducers({ postsByReddit, selectedReddit }); export default rootReducer; ~~~ ### Store #### `configureStore.js` ~~~ import { createStore, applyMiddleware, combineReducers } from 'redux'; import thunkMiddleware from 'redux-thunk'; import loggerMiddleware from 'redux-logger'; import rootReducer from '../reducers'; const createStoreWithMiddleware = applyMiddleware( thunkMiddleware, loggerMiddleware )(createStore); export default function configureStore(initialState) { return createStoreWithMiddleware(rootReducer, initialState); } ~~~ ### 智能组件 #### `containers/Root.js` ~~~ import React, { Component } from 'react'; import { Provider } from 'react-redux'; import configureStore from '../configureStore'; import AsyncApp from './AsyncApp'; const store = configureStore(); export default class Root extends Component { render() { return ( <Provider store={store}> {() => <AsyncApp />} </Provider> ); } } ~~~ #### `containers/AsyncApp.js` ~~~ import React, { Component, PropTypes } from 'react'; import { connect } from 'react-redux'; import { selectReddit, fetchPostsIfNeeded, invalidateReddit } from '../actions'; import Picker from '../components/Picker'; import Posts from '../components/Posts'; class AsyncApp extends Component { constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); this.handleRefreshClick = this.handleRefreshClick.bind(this); } componentDidMount() { const { dispatch, selectedReddit } = this.props; dispatch(fetchPostsIfNeeded(selectedReddit)); } componentWillReceiveProps(nextProps) { if (nextProps.selectedReddit !== this.props.selectedReddit) { const { dispatch, selectedReddit } = nextProps; dispatch(fetchPostsIfNeeded(selectedReddit)); } } handleChange(nextReddit) { this.props.dispatch(selectReddit(nextReddit)); } handleRefreshClick(e) { e.preventDefault(); const { dispatch, selectedReddit } = this.props; dispatch(invalidateReddit(selectedReddit)); dispatch(fetchPostsIfNeeded(selectedReddit)); } render () { const { selectedReddit, posts, isFetching, lastUpdated } = this.props; return ( <div> <Picker value={selectedReddit} onChange={this.handleChange} options={['reactjs', 'frontend']} /> <p> {lastUpdated && <span> Last updated at {new Date(lastUpdated).toLocaleTimeString()}. {' '} </span> } {!isFetching && <a href='#' onClick={this.handleRefreshClick}> Refresh </a> } </p> {isFetching && posts.length === 0 && <h2>Loading...</h2> } {!isFetching && posts.length === 0 && <h2>Empty.</h2> } {posts.length > 0 && <div style={{ opacity: isFetching ? 0.5 : 1 }}> <Posts posts={posts} /> </div> } </div> ); } } AsyncApp.propTypes = { selectedReddit: PropTypes.string.isRequired, posts: PropTypes.array.isRequired, isFetching: PropTypes.bool.isRequired, lastUpdated: PropTypes.number, dispatch: PropTypes.func.isRequired }; 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); ~~~ ### 笨拙组件 #### `components/Picker.js` ~~~ import React, { Component, PropTypes } from 'react'; export default class Picker extends Component { render () { const { value, onChange, options } = this.props; return ( <span> <h1>{value}</h1> <select onChange={e => onChange(e.target.value)} value={value}> {options.map(option => <option value={option} key={option}> {option} </option>) } </select> </span> ); } } Picker.propTypes = { options: PropTypes.arrayOf( PropTypes.string.isRequired ).isRequired, value: PropTypes.string.isRequired, onChange: PropTypes.func.isRequired }; ~~~ #### `components/Posts.js` ~~~ import React, { PropTypes, Component } from 'react'; export default class Posts extends Component { render () { return ( <ul> {this.props.posts.map((post, i) => <li key={i}>{post.title}</li> )} </ul> ); } } Posts.propTypes = { posts: PropTypes.array.isRequired }; ~~~