React 공부정리 #6 [END]
Router Example
- Udemy 강좌의 ‘Section 4: State with React’에 해당하는 내용
- Redux에 대한 자세한 내용은 Redux Framework를 참고
- 한글로 번역된 내용은 Dobbit.Co님의 github을 참고
Setup React Router
$ npm install --save react-router
- react-router를 설치
src\index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import { Router, browserHistory } from 'react-router';
import reducers from './reducers';
const createStoreWithMiddleware = applyMiddleware()(createStore);
ReactDOM.render(
<Provider store={createStoreWithMiddleware(reducers)}>
<Router history={browserHistory} />
</Provider>
, document.querySelector('.container'));
browserHistoy를 사용, 뒤로가기를 해도 필요한 부분만 다시 화면에 표시됨.
src\router.js
import React from 'react';
import { Route, IndexRoute } from 'react-router';
import App from './components/app';
export default (
<Route path="/" component={App}>
</Route>
);
- URL의
\위치를 지정
Nesting Of Routes
src\routes.js
import React from 'react';
import { Route, IndexRoute } from 'react-router';
import App from './components/app';
const Greeting = () => {
return <div>Hey there!</div>
};
export default (
<Route path="/" component={App}>
<Route path="greet" component={Greeting} />
<Route path="greet2" component={Greeting} />
<Route path="greet3" component={Greeting} />
</Route>
);
- 다른 URL을 생성하기 위한 짧은 예제
components\app.js
import React from 'react';
import { Component } from 'react';
export default class App extends Component {
render() {
return (
<div>
{this.props.children}
</div>
);
}
}
- props.children 은 소유자에 의해 자식이 전달되도록 지정
IndexRoutes with React Router
src\routes.js
import React from 'react';
import { Route, IndexRoute } from 'react-router';
import App from './components/app';
import PostsIndex from './components/posts_index';
export default (
<Route path="/" component={App}>
<IndexRoute component={PostsIndex} />
</Route>
);
IndexRoute를 설정
actions\index.js
import axios from 'axios';
export const FETCH_POSTS = 'FETCH_POSTS';
const ROOT_URL = 'http://reduxblog.herokuapp.com/api';
const API_KEY = '?key=lkajdsfapipwietpw';
export function fetchPosts() {
const request = axios.get(`${ROOT_URL}/posts${API_KEY}`);
return {
type: FETCH_POSTS,
payload: request
};
}
Promise기반으로 데이터를 송수신함
reducers\reducer_posts.js
import { FETCH_POSTS, FETCH_POST } from '../actions/index';
const INITIAL_STATE = { all: [], post: null };
export default function(state = INITIAL_STATE, action) {
switch(action.type) {
case FETCH_POSTS:
return { ...state, all: action.payload.data };
default:
return state;
}
}
- 데이터를 전달할 reducer를 생성
reducers\index.js
import { combineReducers } from 'redux';
import PostsReducer from './reducer_posts';
import { reducer as formReducer } from 'redux-form';
const rootReducer = combineReducers({
posts: PostsReducer,
form: formReducer
});
export default rootReducer;
- Reducer를 결합
components/posts_index.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchPosts } from '../actions/index';
class PostsIndex extends Component {
componentWillMount() {
this.props.fetchPosts();
}
render() {
return (
<div>List of blog posts</div>
);
}
}
export default connect(null, { fetchPosts })(PostsIndex);
- Index URL에서 호출할 Component를 생성
New Posts, From, Validation
components/posts_new.js
import React, { Component, PropTypes } from 'react';
import { reduxForm } from 'redux-form';
import { createPost } from '../actions/index';
import { Link } from 'react-router';
class PostsNew extends Component {
static contextTypes = {
router: PropTypes.object
};
onSubmit(props) {
this.props.createPost(props)
.then(() => {
this.context.router.push('/');
});
}
render() {
const { fields: { title, categories, content }, handleSubmit } = this.props;
return (
<form onSubmit={handleSubmit(this.onSubmit.bind(this))}>
<h3>Create A New Post</h3>
<div className={`form-group ${title.touched && title.invalid ? 'has-danger' : ''}`}>
<label>Title</label>
<input type="text" className="form-control" {...title} />
<div className="text-help">
{title.touched ? title.error : ''}
</div>
</div>
<div className={`form-group ${categories.touched && categories.invalid ? 'has-danger' : ''}`}>
<label>Categories</label>
<input type="text" className="form-control" {...categories} />
<div className="text-help">
{categories.touched ? categories.error : ''}
</div>
</div>
<div className={`form-group ${content.touched && content.invalid ? 'has-danger' : ''}`}>
<label>Content</label>
<textarea className="form-control" {...content} />
<div className="text-help">
{content.touched ? content.error : ''}
</div>
</div>
<button type="submit" className="btn btn-primary">Submit</button>
<Link to="/" className="btn btn-danger">Cancel</Link>
</form>
);
}
}
function validate(values) {
const errors = {};
if (!values.title) {
errors.title = 'Enter a username';
}
if (!values.categories) {
errors.categories = 'Enter categories';
}
if(!values.content) {
errors.content = 'Enter some content';
}
return errors;
}
mapDispatchToProps
export default reduxForm({
form: 'PostsNewForm',
fields: ['title', 'categories', 'content'],
validate
}, null, { createPost })(PostsNew);
react-form을 사용해서 데이터를 입력할 ‘폼’을 만들고, 해당 폼의 내용을 검증할 수 있는 validate를 생성
components/posts_index.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchPosts } from '../actions/index';
import { Link } from 'react-router';
class PostsIndex extends Component {
componentWillMount() {
this.props.fetchPosts();
}
renderPosts() {
return this.props.posts.map((post) => {
return (
<li className="list-group-item" key={post.id}>
<Link to={"posts/" + post.id}>
<span className="pull-xs-right">{post.categories}</span>
<strong>{post.title}</strong>
</Link>
</li>
);
});
}
render() {
return (
<div>
<div className="text-xs-right">
<Link to="/posts/new" className="btn btn-primary">
Add a Post
</Link>
</div>
<h3>Posts</h3>
<ul className="list-group">
{this.renderPosts()}
</ul>
</div>
);
}
}
function mapStateToProps(state) {
return { posts: state.posts.all };
}
export default connect(mapStateToProps, { fetchPosts })(PostsIndex);
- URL Index에 데이터를 연결
src\routes.js
import React from 'react';
import { Route, IndexRoute } from 'react-router';
import App from './components/app';
import PostsIndex from './components/posts_index';
import PostsNew from './components/posts_new';
import PostsShow from './components/posts_show';
export default (
<Route path="/" component={App}>
<IndexRoute component={PostsIndex} />
<Route path="posts/new" component={PostsNew} />
<Route path="posts/:id" component={PostsShow} />
</Route>
);
- 동적 파라메터가 자동하는 라우트
Loading Data on Render, Delete!
actions\index.js
import axios from 'axios';
export const FETCH_POSTS = 'FETCH_POSTS';
export const CREATE_POST = 'CREATE_POST';
export const FETCH_POST = 'FETCH_POST';
export const DELETE_POST = 'DELETE_POST';
const ROOT_URL = 'http://reduxblog.herokuapp.com/api';
const API_KEY = '?key=lkajdsfapipwietpw';
export function fetchPosts() {
const request = axios.get(`${ROOT_URL}/posts${API_KEY}`);
return {
type: FETCH_POSTS,
payload: request
};
}
export function createPost(props) {
const request = axios.post(`${ROOT_URL}/posts${API_KEY}`, props);
return {
type: CREATE_POST,
payload: request
};
}
export function fetchPost(id) {
const request = axios.get(`${ROOT_URL}/posts/${id}${API_KEY}`);
return {
type: FETCH_POST,
payload: request
};
}
export function deletePost(id) {
const request = axios.delete(`${ROOT_URL}/posts/${id}${API_KEY}`);
return {
type: DELETE_POST,
payload: request
};
}
- Action에 따른 함수 생성
components\posts_show.js
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { fetchPost, deletePost } from '../actions/index';
import { Link } from 'react-router';
class PostsShow extends Component {
static contextTypes = {
router: PropTypes.object
};
componentWillMount() {
this.props.fetchPost(this.props.params.id);
}
onDeleteClick() {
this.props.deletePost(this.props.params.id)
.then(() => { this.context.router.push('/'); });
}
render() {
const { post } = this.props;
if (!post) {
return <div>Loading...</div>;
}
return (
<div>
<Link to="/">Back To Index</Link>
<button
className="btn btn-danger pull-xs-right"
onClick={this.onDeleteClick.bind(this)}>
Delete Post
</button>
<h3>{post.title}</h3>
<h6>Categories: {post.categories}</h6>
<p>{post.content}</p>
</div>
);
}
}
function mapStateToProps(state) {
return { post: state.posts.post };
}
export default connect(mapStateToProps, { fetchPost, deletePost })(PostsShow);
- URL에 해당하는 데이터를 연결해서 화면에 출력
Array