리액트로 Openlayers 라이브러리를 활용한 개발을 하려고하면 한번 생성한 ol/Map 객체에 대해 여러 컴포넌트에서 사용할 때 어떻게 처리해야할지 많은 고민을 하게 된다. (jsp 시절에 개발할 땐 위에 한번 선언만 해주면 주구장창 다른데서 사용했었다....^^)
리액트에서 맵 객체를 사용하는 모든 컴포넌트에 프로퍼티(props)로 매번 전달하기에는 너무 벅차다.
그래서 필자는 상태관리 라이브러리 중 리덕스에 맵 객체를 넣어서 사용했었지만 리덕스의 러닝커브를 감당하지 못하고 리코일로 넘어가버리는 바람에 맵 객체를 리코일 state에 넣으려고 시도했으나,,, 리코일에는 맵 객체가 들어가지를 않았다. ㅠㅠ ( 아마 개인적인 생각엔 리코일 객체로 들어가면서 read only 객체로 객체 내부에서의 변경도 허용하지 않아서 그런것 같다! )
고로 나는 다른 상태관리가 필요했고, 따로 라이브러리를 설치하지 않고도 사용할 수 있는 Context 를 통해 구성하였다. 현재 실무에서는 Openlayers 관련 객체만 Context 에 저장하고 다른 상태관리는 전부 Recoil 로 하고 있다.
그래서 일단 기본적인 React + Openlayers + Context 의 구성을 해보려고 한다.
기본 패키지 구조는 이렇다. 먼저 확인하고 넘어가자.
먼저 CRA를 통해 프로젝트를 구성한다.
npx create-react-app react-context-openlayer
cd react-context-openlayer
프로젝트에서 사용할 Openlayers 라이브러리를 설치한다.
yarn add ol
이 후 MapContext 를 생성한다.
// ./Map/MapContext.js
import React from "react";
const MapContext = new React.createContext({});
export default MapContext;
일종의 저장소를 생성했다고 보면 된다.
Provider 를 만든다. 이 때, Openlayers 객체를 통해 초기에 셋팅해야 할 것들을 해주고 저장을 원하는 객체 (Map) 를 Provider에 저장한다.
import React, { useState, useEffect } from 'react';
import MapContext from './MapContext';
import 'ol/ol.css';
import { Map as OlMap, View } from 'ol';
import { defaults as defaultControls } from 'ol/control';
import { fromLonLat, get as getProjection } from 'ol/proj';
import { Tile as TileLayer } from 'ol/layer';
import { OSM } from 'ol/source';
const Map = ({ children }) => {
const [mapObj, setMapObj] = useState({});
useEffect(() => {
// Map 객체 생성 및 OSM 배경지도 추가
const map = new OlMap({
controls: defaultControls({ zoom: false, rotate: false }).extend([]),
layers: [
new TileLayer({
source: new OSM(),
})
],
target: 'map', // 하위 요소 중 id 가 map 인 element가 있어야함.
view: new View({
projection: getProjection('EPSG:3857'),
center: fromLonLat([127.9745613, 37.3236563], getProjection('EPSG:3857')),
zoom: 15,
}),
});
setMapObj({ map });
return () => map.setTarget(undefined);
}, []);
// MapContext.Provider 에 객체 저장
return <MapContext.Provider value={mapObj}>{children}</MapContext.Provider>;
};
export default Map;
위의 Provider 를 import 시 경로만으로 가져오기 위한 작업을 해준다. ( 경로/index.js )
// index.js
export { default } from "./Map"
이 후 Provider 에서 저장한 객체를 사용하기 위해서는 Context.Provider 로 감싸져 있는 자식요소에서 사용 가능하다. ( 필자는 테스트 편의상 CRA 를 통해 만들어진 프로젝트 파일 중 index.js 에서 감쌌다. )
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import Map from './Map'; // context.Provider
ReactDOM.render(
<React.StrictMode>
<Map>
<App />
</Map>
</React.StrictMode>,
document.getElementById('root')
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
여섯번째 줄에서 context.Provider를 갖고 오고 열번째 줄에서 App 컴포넌트를 컨텍스트로 감싼다. ( 이제 App 컴포넌트에서는 context 에저장한 객체를 꺼내 사용할 수 있게 되었다. )
테스트를 위해 App.js 에서 Map 객체를 가져오고 ZoomIn , ZoomOut 기능을 하는 버튼을 구성해보았다.
import { useContext } from 'react';
import MapContext from './Map/MapContext';
function App() {
// 컨텍스트에 저장되어있는 객체를 가져옴
const { map } = useContext(MapContext);
const handleZoomInClick = () => {
map.getView().setZoom(map.getView().getZoom() + 1);
};
const handleZoomOutClick = () => {
map.getView().setZoom(map.getView().getZoom() - 1);
};
return (
<>
<button onClick={handleZoomInClick}>zoomIn</button>
<button onClick={handleZoomOutClick}>zoomOut</button>
<div id="map" style={{ width: '100%', height: 400 }}>
</div>
</>
);
}
export default App;
이상없이 지도가 렌더링 되고, context 에 저장한 map 객체를 App 컴포넌트에서 꺼내 쓸 수 있음을 확인할 수 있다. (Map Context Provider 하위 자식 컴포넌트들은 모두 Context 객체에 접근할 수 있다.)
※ 오타 수정이나 질문은 댓글로 부탁드립니다. 블로그 내용 및 소스는 마음껏 퍼가셔도 됩니다^^
전체소스: https://github.com/sbjang123456/react-context-openlayer
'React' 카테고리의 다른 글
React + Recoil 초기 셋팅 및 테스트 (0) | 2021.06.11 |
---|---|
React + Redux 초기 셋팅 및 테스트 (1) | 2021.06.09 |
React(+material-ui) draggable + resizing modal 컴포넌트 (0) | 2021.05.22 |
[React] Keyboard 이벤트 (Enter Key 검색) (0) | 2021.05.21 |
React 체크박스를 통한 배열 렌더링 및 Cleanup(with material-ui, notistack) (0) | 2021.05.18 |