----주요실습 순서(아래)------------------------------------------------------------------------
1 구름IDE에서 리액트 컨테이너 생성 후 npm install 로 기본 패키지 설치 후 리액트 홈 실행하기.
2 1).공공데이터 포털에서 API 활용신청하기 및 2).카카오 개발자센터에서 지도 API 애플리케이션 생성하기.
3 구름IDE에서 공공데이터 포털의 API 데이터를 노드js API의 서버기능 으로 데이터 가져오기.
4 리액트에서 카카오 맵 API 샘플코드를 사용하여 클래스형 컴포넌트 생성 후 라우터 기능으로 메뉴에 추가하기.
5 이전에 생성한 노드js API 데이터를 리액트 카카오 맵 클래스형 컴포넌트에 바인딩해서 지도에 표시하기.
6 클래스형 카카오 맵 컴포넌트를 참조하여 함수형 컴포넌트를 생성 후 노드js API 데이터를 바인딩해서 지도에 표시하기.
7 npm빌드 대신 yarn 빌드를 사용하기 위한 yarn 설치 및 빌드로 build 폴더의 index.html 생성하기.
8 컨테이너 나가기 후에도 노드js 서버 실행을 유지하기 위한 forever 설치(node 버전 업드레이드 필수) 및 forever 로 앱 실행하기.
------------------------------------------------------------------------------------------------------
5 이전에 생성한 노드js API 데이터를 리액트 카카오 맵 클래스형 컴포넌트에 바인딩해서 지도에 표시하기.
- 리액트JS 의 특징중 생명주기에 대해서 알아보고 코딩에 들어간다.(아래)
/* React 클래스형 컴포넌트의 생명 주기란? (이번시간에 사용 예정) */
영어로 라이프사이클(Life cycle)이라고도 표현.
컴포넌트가 실행되거나 업데이트되거나 제거될 때, 특정한 이벤트들이 자동으로 발생된다.
클래스 마운트(렌더링) 전: componentWillMount()
클래스 마운트(렌더링) 후: componentDidMount()
클래스 업데이트(리렌더링) 후: componentDidUpdate()
클래스 언마운트(컴포넌트 화면전환) 전: componentWillUnmount()
/* React 함수형 컴포넌트의 생명 주기란? (다음시간에 사용 예정) */
클래스형 컴포넌트처럼 명시적인 함수를 사용하지 않고ㅡ useEffect() 함수를 사용해서 위 4가지 상태를 구현한다.
예를 들어 keyword이라는 state가 있다고 가정하면, keyword가 바뀌는 것에 따라서 render함수가 재 실행되는 라이프사이클로 정할 수 있다.(우리 실습 코드 에서는 검색버튼이 클릭 할때 useEffect()함수를 재 실행 시키는 구현이라서 keyword를 제거한다.)
useEffect(() => {
console.log('keyword의 한 단어라도 바뀔때마다 useEffect함수가 재실행 된다.');
}, [keyword]);
위 코드는 컴포넌트가 첫 렌더링될 때 한 번 실행되고, 그 다음부터는 keyword의 한 단어라도 바뀔때마다 useEffect함수가 재실행 된다.
즉, componentDidMount와 componentDidUpdate가 합쳐진 셈이다.
componentWillUnmount의 역할은 아래처럼 return으로 함수에 반환값을 추가하면 된다.
useEffect(() => {
console.log('keyword의 한 단어라도 바뀔때마다 useEffect함수가 재실행 된다.');
return () => {
console.log('keyword변화 때문에 화면이 바뀔 예정이다.');
};
}, [keyword]);
- 클래스형 컴포넌트 ClassKakaoMap.js 파일을 추가하고, API json데이터 파싱 후 바인딩 하기.(아래)
/*global kakao*/
import React, {Component} from 'react';
import { Link } from "react-router-dom";
class KakaoMap extends Component {
constructor (props) {
//props(속성) 과 state(자료) 관계
super(props); //부모클래스-Component의 props속성을 사용하겠다고 선언, 이후 부터 this 키워드 사용가능
//부모클래스 props속성의 state값 초기화
this.state = {
keyword: '천안시', //검색어 상태 입력예
pageNo: 1,
totalCount: 0,
} //json 1차원 데이터 객체
this.onSearch = this.onSearch.bind(this);
this.onChange = this.onChange.bind(this);
this.getData = this.getData.bind(this);
this.removeAllChildNods = this.removeAllChildNods.bind(this);
this.repeatPage = this.repeatPage.bind(this);
this.onPage = this.onPage.bind(this);
};
repeatPage(totalCount) {
var pagingNo = Math.ceil(this.state.totalCount/10);
var arr = [];
for(var i=1; i<=pagingNo; i++) {
arr.push(
<option key={i} value={i}>{i}</option>
);
}
return arr;
}
onPage = (e) => { //페이지 선택 이벤트 함수
this.setState({[e.target.id]: e.target.value});//화면처리
this.state.pageNo = e.target.value;//js처리
var mapContainer = document.getElementById('map');
this.removeAllChildNods(mapContainer);//기존 카카오맵 겍체 지우기
this.getData();
};
removeAllChildNods(el) { //기존 지도 지우기
while (el.hasChildNodes()) {
el.removeChild (el.lastChild);
}//기술참조:https://apis.map.kakao.com/web/sample/keywordList/
}
onSearch() { // 검색 버튼 이벤트 함수
var mapContainer = document.getElementById('map');
this.removeAllChildNods(mapContainer);//기존 카카오맵 겍체 지우기
this.state.pageNo = 1;//js처리
this.getData();
}
onChange(e) { // 검색어 수정 이벤트 함수
this.setState({[e.target.id]: e.target.value});//화면처리-재랜더링
this.state.keyword = e.target.value;//js처리
}
getData() { // 지도 데이터 처리 + 출력
var url = 'https://server-basic-fekuw.run.goorm.io/openapi/getdata?keyword='+this.state.keyword+'&pageNo='+this.state.pageNo;;
fetch (url, {method:'get'})
.then (response => response.json()) //응답데이터를 json 형태로 변환
.then (contents => { //json으로 변환된 응답데이터인 contents 를 가지고 구현하는 내용
//this.state.totalCount = contents['response']['body']['totalCount']['_text'];//js처리
this.setState({totalCount: contents['response']['body']['totalCount']['_text']});//화면처리
var positions = [];//배열 선언
var jsonData;
jsonData=contents['response']['body']['items'];
console.log(jsonData);
jsonData['item'].forEach((element) => {//람다식 사용 function(element) {}
positions.push(
{
content: "<div>"+element["csNm"]['_text']+"</div>",//충전소 이름
latlng: new kakao.maps.LatLng(element["lat"]['_text'], element["longi"]['_text']) // 위도(latitude), 경도longitude)
}
);
});
var index = parseInt(positions.length/2);//배열은 인덱스순서 값을 필수로 가지고, 여기서는 반환 값의 개수로 구한다.
console.log(jsonData["item"][index]["lat"]['_text']);
//console.log(jsonData);
var mapContainer = document.getElementById('map'), // 지도를 표시할 div
mapOption = {
center: new kakao.maps.LatLng(jsonData["item"][index]["lat"]['_text'], jsonData["item"][index]["longi"]['_text']),
//center: new kakao.maps.LatLng(33.450701, 126.570667), // 지도의 중심좌표
level: 10 // 지도의 확대 레벨
};
var map = new kakao.maps.Map(mapContainer, mapOption); // 지도를 생성합니다
for (var i = 0; i < positions.length; i ++) {
// 마커를 생성합니다
var marker = new kakao.maps.Marker({
map: map, // 마커를 표시할 지도
position: positions[i].latlng // 마커의 위치
});
// 마커에 표시할 인포윈도우를 생성합니다
var infowindow = new kakao.maps.InfoWindow({
content: positions[i].content // 인포윈도우에 표시할 내용
});
// 마커에 mouseover 이벤트와 mouseout 이벤트를 등록합니다
// 이벤트 리스너로는 클로저를 만들어 등록합니다
// for문에서 클로저를 만들어 주지 않으면 마지막 마커에만 이벤트가 등록됩니다
kakao.maps.event.addListener(marker, 'mouseover', makeOverListener(map, marker, infowindow));
kakao.maps.event.addListener(marker, 'mouseout', makeOutListener(infowindow));
}
// 인포윈도우를 표시하는 클로저를 만드는 함수입니다
function makeOverListener(map, marker, infowindow) {
return function() {
infowindow.open(map, marker);
};
}
// 인포윈도우를 닫는 클로저를 만드는 함수입니다
function makeOutListener(infowindow) {
return function() {
infowindow.close();
};
}
})
.catch ((err) => console.log ('에러: ' + err + '때문에 접속할 수 없습니다.'));
}
componentDidMount () { // 생명주기 중 초기 화면 렌더링 후 실행함수
this.getData();
}
render() {
//props-state의 값이 바뀌면 html을 그리는 함수 render 자동으로 재 실행됨
//console.clear (); //콘솔 지저분한것 때문에... 디버그시 주석해제 필요.
console.log ('render()안에서 this는 App.js콤포넌트 모듈 자신을 가리킨다.', this);
//constructor (props) 부모클래스의 초기화한 값을 아래 태그의 속성(props)에 this값으로 전달한다.
return (
<div>
<h2>클래스형 전기차 충전소 위치</h2>
<span>충전소 도시 검색(아래 검색할 시를 입력하고 검색 버튼을 누른다.)</span>
<input className="form-control" type="text" id="keyword" onChange={this.onChange} value={this.state.keyword} />
<input className="form-control btn btn-primary" type="button" onClick={this.onSearch} value="검색" />
<span>페이지이동(아래 번호를 선택하면 화면이 전환된다.)</span><select className="form-select" id="pageNo" onChange={this.onPage} value={this.state.pageNo}>
{this.repeatPage(this.state.totalCount)}
</select>
<Link to="/"><button className="form-control btn btn-primary" id="btnHome">홈으로</button></Link>
<div id="map" style={{width:"100%",height:"70vh"}}></div>
</div>
);
}
}
export default KakaoMap;
- 부트스트랩 디자인 템플릿에서 input, button 속성사용은 배포 사이트에서 참조한다. (아래)
이 URL에서 html 태그의 class이름을 똑같이 사용하면 된다: https://getbootstrap.com/docs/5.2/forms/overview/
- render() 함수내애서 JSX 코드를 사용할 때, 기존 html 태그와 속성명을 고유한 이름으로 변경해야 하는 것에 주의 한다.
예를 들면, html에서는 input 태그의 class -> JSX 코드에서는 input 태그의 className 처럼 고유한 속성명을 갖는다.
- 실행 결과는 다음과 같다(아래)
- 이번 시간에 작업한 소스 위치: https://github.com/kimilguk/react-basic/tree/basic05
리액트기반으로 공공데이터와 지도API 연동07 (0) | 2022.08.13 |
---|---|
리액트기반으로 공공데이터와 지도API 연동06 (0) | 2022.08.13 |
리액트기반으로 공공데이터와 지도API 연동04 (0) | 2022.08.13 |
리액트기반으로 공공데이터와 지도API 연동03 (0) | 2022.08.13 |
리액트기반으로 공공데이터와 지도API 연동02 (0) | 2022.08.13 |
댓글 영역