React 공부정리 #5
App Overview
- Udemy 강좌의 ‘Section 5: Redux with React’에 해당하는 내용
- Redux에 대한 자세한 내용은 Redux Framework를 참고
- 한글로 번역된 내용은 Dobbit.Co님의 github을 참고
완성한 모습
Component Setup
container\search_bar.js
import React, { Component } from 'react';
export default class SearchBar extends Component {
render() {
return (
<form className="input-group">
<input />
<span className="input-group-btn">
<button type="submit" className="btn btn-secondary">Submit</button>
</span>
</form>
);
}
}
container는redux와 연결하기 위한 상위 컴포넌트로 입력값을 전달
components\app.js
import React from 'react';
import { Component } from 'react';
import SearchBar from '../containers/search_bar';
export default class App extends Component {
render() {
return (
<div>
<SearchBar />
</div>
);
}
}
component는 하위 컴포넌트로container를 통해 전달된 값을 화면에 표현하는 것이 목적
Controlled Components and Binding Context
containers\search_bar.js
import React, { Component } from 'react';
export default class SearchBar extends Component {
constructor(props) {
super(props);
this.state = { term: '' };
this.onInputChange = this.onInputChange.bind(this);
}
onInputChange(event) {
this.setState({ term: event.target.value });
}
onFormSubmit(event) {
event.preventDefault();
}
render() {
return (
<form onSubmit={this.onFormSubmit} className="input-group">
<input
placeholder="Get a five-day forecast in your favorite cities"
className="form-control"
value={this.state.term}
onChange={this.onInputChange} />
<span className="input-group-btn">
<button type="submit" className="btn btn-secondary">Submit</button>
</span>
</form>
);
}
}
actions\index.js
const API_KEY = '1234567890abcdef1234567890abcdef';
- OpenWeatherMap에 가입하고 발급받은 API 키를 입력한다.
Middleware
npm install redux-promise --save
npm install axios --save
Promise를 사용하기 위해서 Redux 관련 라이브러리와 Promise 기반 HTTP 클라이언트 라이브러리를 설치
src\index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import ReduxPromise from 'redux-promise';
import App from './components/app';
import reducers from './reducers';
const createStoreWithMiddleware = applyMiddleware(ReduxPromise)(createStore);
ReactDOM.render(
<Provider store={createStoreWithMiddleware(reducers)}>
<App />
</Provider>
, document.querySelector('.container'));
- Redux의 Promise를 미들웨어(Middleware)로 생성하고
store에Promise를 할당
actions\index.js
import axios from 'axios';
const API_KEY = '8532ab9dab1470c8bff4c8f71d0f089b';
const ROOT_URL = `http://api.openweathermap.org/data/2.5/forecast?appid=${API_KEY}`;
export const FETCH_WEATHER = 'FETCH_WEATHER';
export function fetchWeather(city) {
const url = `${ROOT_URL}&q=${city},us`
const request = axios.get(url);
return {
type: FETCH_WEATHER,
payload: request
};
}
- Promise 기반 HTTP 클라이언트를 사용해서 Query에 해당하는 결과를 가져옴
containers\search_bar.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { fetchWeather } from '../actions/index';
export default class SearchBar extends Component {
constructor(props) {
super(props);
this.state = { term: '' };
this.onInputChange = this.onInputChange.bind(this);
this.onFormSubmit = this.onFormSubmit.bind(this);
}
onInputChange(event) {
this.setState({ term: event.target.value });
}
onFormSubmit(event) {
event.preventDefault();
this.props.fetchWeather(this.state.term);
this.setState({term: ''});
}
render() {
return (
<form onSubmit={this.onFormSubmit} className="input-group">
<input
placeholder="Get a five-day forecast in your favorite cities"
className="form-control"
value={this.state.term}
onChange={this.onInputChange} />
<span className="input-group-btn">
<button type="submit" className="btn btn-secondary">Submit</button>
</span>
</form>
);
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators( {fetchWeather}, dispatch);
}
export default connect(null, mapDispatchToProps)(SearchBar);
container에 Query 결과를 바인딩 할 수 있도록 모듈을 추가
Avoiding State Mutations in Reducers
reducers\reducer_weather.js
import { FETCH_WEATHER } from '../actions/index';
export default function(state = [], action) {
switch (action.type) {
case FETCH_WEATHER:
return [ action.payload.data, ...state ]
}
return state;
}
- 날씨 정보를 전달할 reducer를 생성
Building a List Container
container\weather_list.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
class WeatherList extends Component {
render() {
return (
<table className="table table-hover">
<thead>
<tr>
<th>City</th>
<th>Temperatures</th>
<th>Pressure</th>
<th>Humidity</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
);
}
}
function mapStateToProps({weather}) {
return { weather };
}
export default connect(mapStateToProps)(WeatherList);
- 온도, 습도 등을 나타낼 컴포넌트를 생성
Mapping Props to a Render Helper
npm install --save react-sparklines
npm install --save lodash
- 차트를 생성하기 위한 라이브러리를 추가
components\chart.js
import _ from 'lodash';
import React, { Component } from 'react';
import { Sparklines, SparklinesLine, SparklinesReferenceLine } from 'react-sparklines';
function average(data) {
return _.round(_.sum(data)/data.length);
}
export default (props) => {
return (
<div>
<Sparklines height={120} width={180} data={props.data}>
<SparklinesLine color={props.color} />
<SparklinesReferenceLine type="avg" />
</Sparklines>
<div>{average(props.data)} {props.units}</div>
</div>
);
}
- 차트 컴포넌트를 만듬
components\weather_list.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import Chart from '../components/chart';
class WeatherList extends Component {
renderWeather(cityData) {
const name = cityData.city.name;
const temps = cityData.list.map(weather => weather.main.temp);
const pressures = cityData.list.map(weather => weather.main.pressure);
const humidities = cityData.list.map(weather => weather.main.humidity);
return (
<tr key={name}>
<td>{name}</td>
<td><Chart data={temps} color="orange" units="K" /></td>
<td><Chart data={pressures} color="green" units="hPa" /></td>
<td><Chart data={humidities} color="black" units="%" /></td>
</tr>
);
}
render() {
return (
<table className="table table-hover">
<thead>
<tr>
<th>City</th>
<th>Temperatures</th>
<th>Pressure</th>
<th>Humidity</th>
</tr>
</thead>
<tbody>
{this.props.weather.map(this.renderWeather)}
</tbody>
</table>
);
}
}
function mapStateToProps({weather}) {
return { weather };
}
export default connect(mapStateToProps)(WeatherList);
Google Map
npm install --save react-google-maps
- 구글 지도를 사용하기 위한 리액트 라이브러리를 설치
components\google_maps.js
import React from 'react';
import { GoogleMapLoader, GoogleMap } from 'react-google-maps';
export default (props) => {
return (
<GoogleMapLoader
containerElement={ <div style={{height: '100%'}} /> }
googleMapElement={
<GoogleMap defaultZoom={12} defaultCenter={{lat: props.lat, lng: props.lon}} />
}
/>
);
}
- 구글 지도를 사용해서 도시 위치를 표시하기 위한 컴포넌트를 만듬
components\weather_list.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import Chart from '../components/chart';
import GoogleMap from '../components/google_map';
class WeatherList extends Component {
renderWeather(cityData) {
const name = cityData.city.name;
const temps = cityData.list.map(weather => weather.main.temp);
const pressures = cityData.list.map(weather => weather.main.pressure);
const humidities = cityData.list.map(weather => weather.main.humidity);
const { lon, lat } = cityData.city.coord;
return (
<tr key={name}>
<td><GoogleMap lon={lon} lat={lat} /></td>
<td><Chart data={temps} color="orange" units="K" /></td>
<td><Chart data={pressures} color="green" units="hPa" /></td>
<td><Chart data={humidities} color="black" units="%" /></td>
</tr>
);
}
render() {
return (
<table className="table table-hover">
<thead>
<tr>
<th>City</th>
<th>Temperatures</th>
<th>Pressure</th>
<th>Humidity</th>
</tr>
</thead>
<tbody>
{this.props.weather.map(this.renderWeather)}
</tbody>
</table>
);
}
}
function mapStateToProps({weather}) {
return { weather };
}
export default connect(mapStateToProps)(WeatherList);
- 구글 지도 컴포넌트를 추가
Array