상세 컨텐츠

본문 제목

리액트기반으로 공공데이터와 지도API 연동05

노드js·자바스크립트

by 김일국 2022. 8. 13. 17:21

본문

----주요실습 순서(아래)------------------------------------------------------------------------

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

관련글 더보기

댓글 영역